UNPKG

@exadel/esl

Version:

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

230 lines (229 loc) 9.42 kB
--- name: esl description: Use when writing, reviewing, or refactoring consumer code that uses @exadel/esl / Exadel Smart Library. Covers ESLBaseElement, ESLMixinElement, decorators, host model, public module imports, event listeners, traversing queries, media rules, and idiomatic ESL web component patterns. package: '@exadel/esl' packageVersion: '6.2.0' --- # ESL Consumer Code You are generating or reviewing consumer code that uses `@exadel/esl`. Your goal is to produce idiomatic ESL code that uses the library primitives instead of raw DOM boilerplate. Use this skill for: - custom elements based on `ESLBaseElement` - mixins based on `ESLMixinElement` - code using `@exadel/esl/modules/...` - ESL decorators such as `@attr`, `@boolAttr`, `@jsonAttr`, `@listen` - ESL event, traversal, media, and utility APIs If available, use reference docs in this skill package for deeper API details: - `references/esl-core.md` - `references/esl-review.md` ## Public import rules For consumer projects, prefer public module entrypoints: ```ts import {ESLBaseElement} from '@exadel/esl/modules/esl-base-element/core'; import {ESLMixinElement} from '@exadel/esl/modules/esl-mixin-element/core'; import {ESLMediaQuery, ESLMediaRuleList} from '@exadel/esl/modules/esl-media-query/core'; import {attr, boolAttr, jsonAttr, prop, listen, ready} from '@exadel/esl/modules/esl-utils/decorators'; ``` Rules: 1. Prefer `@exadel/esl/modules/.../core` public entries. 2. Root import from `@exadel/esl` is acceptable only when the consumer setup supports tree-shaking appropriately. 3. Do not import from ESL internal implementation paths. 4. Do not invent private paths when a public `core` entry exists. ## Choose the correct host model first Before writing code, decide the host model: 1. Use `ESLBaseElement` for a new custom tag: ```html <my-element></my-element> ``` 2. Use `ESLMixinElement` for behavior attached to an existing element by attribute: ```html <div my-mixin></div> ``` Hard rule: - In `ESLBaseElement`, the DOM host is `this`. - In `ESLMixinElement`, the DOM host is `this.$host`. Do not treat a mixin instance as the DOM element. For mixins, read and mutate the real element through `this.$host` or host-aware ESL helpers. ## Registration and lifecycle Every ESL component or mixin must follow registration and lifecycle contracts: ```ts export class MyElement extends ESLBaseElement { static override is = 'my-element'; protected override connectedCallback(): void { super.connectedCallback(); } protected override disconnectedCallback(): void { super.disconnectedCallback(); } } MyElement.register(); ``` Rules: 1. Set `static is` before calling `register()`. 2. Call `register()` once for the class. 3. Always call `super.connectedCallback()`. 4. Always call `super.disconnectedCallback()`. 5. Do not mutate `static is` after registration. 6. Custom element and mixin names should contain a dash. ## Prefer ESL decorators Use decorators for attribute, property, and event wiring. Prefer: - `@attr` for string, number, inherited, custom, default-enabled, or tri-state values - `@boolAttr` for boolean presence attributes only - `@jsonAttr` for object-like config attributes - `@prop` for prototype-level constants or provider-backed values - `@listen` for stable class-owned event listeners - `@ready` only when logic must wait for DOM readiness Do not manually write repetitive `getAttribute`, `setAttribute`, `hasAttribute`, `addEventListener`, or `removeEventListener` logic when an ESL decorator or helper expresses the same behavior. Use `@attr`, not `@boolAttr`, when the value is not a simple presence boolean. ## Use host-aware ESL helpers Inside `ESLBaseElement` and `ESLMixinElement`, prefer host-aware shortcuts: - `this.$$find(selector)` - `this.$$findAll(selector)` - `this.$$cls(className, state?)` - `this.$$attr(name, value?)` - `this.$$fire(eventName, init?)` - `this.$$on(...)` - `this.$$off(...)` - `this.$$error(error, key?)` These helpers target the correct host: - `this` for `ESLBaseElement` - `this.$host` for `ESLMixinElement` Use `this.$$cls(...)` and `this.$$attr(...)` for host state reflection instead of direct `classList` or attribute operations on the host. ## Event handling Default choice: - use `@listen` for stable listeners that belong to the class ```ts @listen('click') protected _onClick(e: MouseEvent): void { } ``` Use `$$on` / `$$off` when: - the listener is conditional - the target changes at runtime - the event type changes at runtime - you need manual subscription control ```ts @listen({event: 'resize', target: 'window', auto: false}) protected _onResize(): void { } protected override connectedCallback(): void { super.connectedCallback(); this.$$on(this._onResize); } protected override disconnectedCallback(): void { this.$$off(this._onResize); super.disconnectedCallback(); } ``` Do not use raw `addEventListener` / `removeEventListener` for stable component-owned listeners when `@listen` can be used. ## Traversing and DOM lookup Use `$$find` / `$$findAll` when lookup is component-relative or user-configurable. ESL traversing syntax can express relationships such as: - `''` current host - `::parent` - `::closest(.selector)` - `::child(button)` - `::find(.item)` - `::next` - `::prev` - `::visible` Examples: ```ts this.$$find(''); this.$$find('::closest(esl-panel)'); this.$$find('::find(button, a)::not([hidden])'); this.$$findAll('::find(.item)::visible'); ``` Native `querySelector` is allowed when a simple element-scoped CSS lookup is enough, but prefer ESL traversing when the selector represents component relationships. ## Media and responsive behavior Prefer ESL media utilities over raw `matchMedia` wiring: - use `ESLMediaQuery` when the code needs to react to a media condition - use `ESLMediaRuleList` when a config value changes by media condition ```ts import {ESLMediaQuery} from '@exadel/esl/modules/esl-media-query/core'; @listen({event: 'change', target: ESLMediaQuery.for('@-sm')}) protected _onSmallViewportChange(): void { } ``` Use `ESLMediaRuleList` for values like: ```ts 'default | @xs => compact | @+md => full' ``` Do not manually implement breakpoint parsing or media listener cleanup when ESL media utilities fit the problem. ## Attribute change handling When overriding `attributeChangedCallback`, guard expensive logic: ```ts protected override attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void { if (oldValue === newValue) return; super.attributeChangedCallback(name, oldValue, newValue); } ``` Remember: - `ESLBaseElement` observes attributes listed in `static observedAttributes` - `ESLMixinElement` observes `static observedAttributes` plus its own activation attribute ## Common anti-patterns Avoid: 1. Importing from ESL internal implementation files. 2. Choosing `ESLBaseElement` when behavior should be a mixin. 3. Choosing `ESLMixinElement` but mutating `this` instead of `this.$host`. 4. Forgetting `register()`. 5. Forgetting `super.connectedCallback()` or `super.disconnectedCallback()`. 6. Using raw event listeners for stable class-owned events. 7. Using `@boolAttr` for default-enabled or tri-state values. 8. Assuming `@jsonAttr` accepts only strict JSON. 9. Reimplementing class, attribute, traversal, media, or event helpers already provided by ESL. 10. Making public API depend on internal ESL repository paths. ## Minimal good examples Custom element: ```ts import {ESLBaseElement} from '@exadel/esl/modules/esl-base-element/core'; import {attr, boolAttr, listen} from '@exadel/esl/modules/esl-utils/decorators'; export class AppCard extends ESLBaseElement { static override is = 'app-card'; @attr({defaultValue: ''}) public heading: string; @boolAttr() public selected: boolean; protected override connectedCallback(): void { super.connectedCallback(); this.$$cls('app-card-ready', true); } @listen('click') protected _onClick(): void { this.$$fire('app-card:click'); } } AppCard.register(); ``` Mixin: ```ts import {ESLMixinElement} from '@exadel/esl/modules/esl-mixin-element/core'; import {attr, listen} from '@exadel/esl/modules/esl-utils/decorators'; export class AppTrackClick extends ESLMixinElement { static override is = 'app-track-click'; @attr({defaultValue: ''}) public eventName: string; @listen('click') protected _onClick(): void { this.$$fire('app-track-click:trigger', { detail: {eventName: this.eventName} }); } } AppTrackClick.register(); ``` ## Final checklist Before returning ESL code, verify: 1. Correct host model is used: - custom tag → `ESLBaseElement` - attribute behavior → `ESLMixinElement` 2. Mixins use `this.$host` or host-aware `$$*` helpers for DOM host operations. 3. Imports use public `@exadel/esl/modules/...` entries. 4. `static is` is defined before `register()`. 5. `register()` is present. 6. Lifecycle overrides call `super`. 7. Attributes/properties use ESL decorators where appropriate. 8. Stable listeners use `@listen`. 9. Dynamic listeners use `$$on` / `$$off` with cleanup. 10. Component-relative lookup uses `$$find` / `$$findAll` where appropriate. 11. Responsive logic uses `ESLMediaQuery` or `ESLMediaRuleList` where appropriate. 12. No internal ESL implementation paths are used. 13. No raw DOM boilerplate duplicates an ESL primitive. 14. The code is small, readable, and easy for a consumer project to extend.