@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:

get disabled() { return this.hasAttribute('disabled'); }
set disabled(v: boolean) { v ? this.setAttribute('disabled', '') : this.removeAttribute('disabled'); }

@boolAttr encapsulates this pattern:

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


Quick Start

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

boolAttr(config?: BoolAttrConfig): PropertyDecorator;
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.

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:


Examples

Custom Attribute Name

class Component extends HTMLElement {
  @boolAttr({name: 'no-shadow'}) noShadow!: boolean; // <component no-shadow>
}

Data Attribute

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

Readonly View of a Marker

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:

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