# @boolAttr Decorator

Maps a boolean property to the mere presence of an HTML (or `data-*`) attribute.

Presence → `true`, absence → `false`. No third state, no default override, no custom parser.

---
## Why
Frequently you need a simple feature toggle (e.g. `disabled`, `open`, `interactive`) whose truthiness depends solely on whether an attribute exists. Implementing manual getters/setters for each is repetitive:
```ts
get disabled() { return this.hasAttribute('disabled'); }
set disabled(v: boolean) { v ? this.setAttribute('disabled', '') : this.removeAttribute('disabled'); }
```
`@boolAttr` encapsulates this pattern:
- Minimal logic, zero configuration in common cases
- Guarantees consistent DOM reflection semantics
- Readability & reduced boilerplate

Use `@attr({parser: parseBoolean, serializer: toBooleanAttribute, defaultValue: ...})` instead if you need a tri‑state boolean with a default value (see Comparison section).

---
## Quick Start
```ts
import {boolAttr} from '@exadel/esl/modules/esl-utils/decorators';

class TogglePanel extends HTMLElement {
  @boolAttr() disabled!: boolean;        // <toggle-panel disabled>
  @boolAttr({name: 'no-close'}) noclose!: boolean; // custom attribute name
  @boolAttr({dataAttr: true}) active!: boolean;    // maps to data-active
}

const p = new TogglePanel();
p.disabled = true;      // sets attribute disabled=""
p.disabled = false;     // removes attribute
```

---
## API
```ts
boolAttr(config?: BoolAttrConfig): PropertyDecorator;
```
```ts
interface BoolAttrConfig {
  name?: string;      // custom attribute name (kebab-cased by default)
  readonly?: boolean; // getter only (writes ignored)
  dataAttr?: boolean; // prefix with data-
}
```

### Behavior
| Action | Effect |
|--------|--------|
| Read property | `hasAttribute(attrName)` (true/false) |
| Write truthy (not readonly) | `setAttribute(attrName, '')` (empty string) |
| Write falsy (not readonly) | `removeAttribute(attrName)` |
| `readonly: true` | Only getter defined; assignments do nothing |
| `dataAttr: true` | Attribute name becomes `data-{kebab}` |

---
## Host Resolution ($host Support)
`@boolAttr` works not only on direct `HTMLElement` subclasses. If applied to an object exposing a `$host: HTMLElement` property (e.g. mixin / controller / behavior pattern), it resolves and manipulates the host element's attribute.

```ts
class FocusBehaviour { // not an element
  constructor(public $host: HTMLElement) {}
  @boolAttr() focused!: boolean;
}

const el = document.createElement('div');
const behavior = new FocusBehaviour(el);
behavior.focused = true;  // sets attribute on el
```
If `$host` is missing or null, reads return `false` and writes are effectively ignored (attribute helpers are no-ops for invalid host).

---
## Comparison with Related Decorators
| Decorator | True Condition | False Condition | Default w/o Attribute | Third State? | Inheritance | Custom Parse/Serialize |
|-----------|----------------|-----------------|-----------------------|--------------|-------------|-------------------------|
| `@boolAttr` | Attribute present | Attribute absent | `false` | No | No | No |
| Tri-state `@attr` | Parser truth mapping | Absent fallback (e.g. `defaultValue`) or explicit false text | Configurable (`defaultValue`) | Yes (default vs explicit) | Yes (`inherit`) | Yes |
| `@attr` + simple parser | Parser result | Parser result | Configurable | Depends | Yes | Yes |

Use `@boolAttr` when you only care about presence. Switch to `@attr` when you need:
- A default `true` without writing the attribute
- Inter-element inheritance (`inherit`)
- Distinguishing explicit false vs absence
- Custom parse/serialize formats

---
## Examples
### Custom Attribute Name
```ts
class Component extends HTMLElement {
  @boolAttr({name: 'no-shadow'}) noShadow!: boolean; // <component no-shadow>
}
```

### Data Attribute
```ts
class FeatureFlag extends HTMLElement {
  @boolAttr({dataAttr: true}) experimental!: boolean; // data-experimental
}
```

### Readonly View of a Marker
```ts
class View extends HTMLElement {
  @boolAttr({readonly: true}) hydrated!: boolean; // code can read, not set
  connectedCallback() { this.toggleAttribute('hydrated', true); }
}
```

### Override Attribute Mapping via Subclass
If you need to lock a boolean to `true` (ignoring attribute removal) use `@prop` in a subclass:
```ts
class BaseEl extends HTMLElement { @boolAttr() interactive!: boolean; }
class LockedEl extends BaseEl { @prop(true, {readonly: true}) override interactive!: boolean; }
```

---
## Edge Cases
| Case | Result |
|------|--------|
| Assign `null` / `undefined` | Treated as falsy -> attribute removed |
| Assign number `0` | Falsy -> removed |
| Assign string `'false'` | Truthy (non-empty) -> attribute present |
| Need explicit textual false | Not supported; use tri-state `@attr` pattern |

---
## Performance Notes
The getter is O(1) DOM `hasAttribute` call. No parsing / serialization overhead. Suitable for high-frequency reads.

---
## Best Practices
- Prefer `@boolAttr` for pure presence toggles—keeps intent clear.
- Use `@attr` tri-state pattern for default-enabled features that can be explicitly disabled.
- Combine with `@prop` to freeze or preconfigure booleans in subclasses.
- Keep attribute names semantic and kebab-cased (default behavior already handles conversion).
