UNPKG

@exadel/esl

Version:

Exadel Smart Library (ESL) is the lightweight custom elements library that provide a set of super-flexible components

139 lines (117 loc) 5.35 kB
# @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).