@aihu/primitives 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +7 -7
  2. package/dist/{button-Cha1gKlr.js → button-RgkdWi4q.js} +2 -2
  3. package/dist/{button-Cha1gKlr.js.map → button-RgkdWi4q.js.map} +1 -1
  4. package/dist/button.d.ts +1 -1
  5. package/dist/button.js +1 -1
  6. package/dist/checkbox.js +1 -1
  7. package/dist/collection.d.ts.map +1 -1
  8. package/dist/collection.js +11 -7
  9. package/dist/collection.js.map +1 -1
  10. package/dist/composed-tree-OMbp4NQn.js +297 -0
  11. package/dist/composed-tree-OMbp4NQn.js.map +1 -0
  12. package/dist/{dialog-VDL-W3Vy.js → dialog-BHtBlVHN.js} +12 -14
  13. package/dist/dialog-BHtBlVHN.js.map +1 -0
  14. package/dist/dialog.d.ts +1 -1
  15. package/dist/dialog.js +1 -1
  16. package/dist/{form-control-B_BO9j7_.js → form-control-CRF3PWcK.js} +12 -7
  17. package/dist/form-control-CRF3PWcK.js.map +1 -0
  18. package/dist/form-control.d.ts +1 -1
  19. package/dist/form-control.js +1 -1
  20. package/dist/{index-BvFa1Y-Z.d.ts → index-BODOUcDC.d.ts} +4 -2
  21. package/dist/{index-BvFa1Y-Z.d.ts.map → index-BODOUcDC.d.ts.map} +1 -1
  22. package/dist/{index-D7hJLfnb.d.ts → index-CyG_gCVH.d.ts} +6 -4
  23. package/dist/index-CyG_gCVH.d.ts.map +1 -0
  24. package/dist/{index-yPv3StRL.d.ts → index-Dgli1Q03.d.ts} +1 -1
  25. package/dist/{index-yPv3StRL.d.ts.map → index-Dgli1Q03.d.ts.map} +1 -1
  26. package/dist/index.d.ts +4 -4
  27. package/dist/index.js +4 -4
  28. package/dist/input.d.ts +1 -1
  29. package/dist/input.js +1 -1
  30. package/dist/label.d.ts.map +1 -1
  31. package/dist/label.js +9 -7
  32. package/dist/label.js.map +1 -1
  33. package/dist/radio-group.js +1 -1
  34. package/dist/switch.js +1 -1
  35. package/dist/{text-control-BBX7s8Oe.js → text-control-CUbTuk5L.js} +2 -2
  36. package/dist/{text-control-BBX7s8Oe.js.map → text-control-CUbTuk5L.js.map} +1 -1
  37. package/dist/{text-control-Brv5fUEX.d.ts → text-control-wKoKU_--.d.ts} +1 -1
  38. package/dist/{text-control-Brv5fUEX.d.ts.map → text-control-wKoKU_--.d.ts.map} +1 -1
  39. package/dist/textarea.d.ts +1 -1
  40. package/dist/textarea.js +1 -1
  41. package/package.json +3 -3
  42. package/dist/dialog-VDL-W3Vy.js.map +0 -1
  43. package/dist/form-control-B_BO9j7_.js.map +0 -1
  44. package/dist/index-D7hJLfnb.d.ts.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog-BHtBlVHN.js","names":[],"sources":["../src/dialog/focus-trap.ts","../src/dialog/index.ts"],"sourcesContent":["/**\n * `createFocusTrap` — a tiny native-DOM focus trap (no library). Queries the\n * focusable descendants of a container via the composed-tree substrate (so it\n * correctly descends into any nested custom element's OPEN shadow root), wraps\n * Tab at the edges, moves focus to the first focusable (or the container) on\n * activate, and restores focus to the previously-active element on deactivate.\n * Used by `dialog-content`.\n */\n\nimport { composedActiveElement, composedContains, queryTabbables } from '../composed-tree.ts'\n\nexport interface FocusTrap {\n activate(): void\n deactivate(): void\n}\n\nfunction focusables(container: Element): HTMLElement[] {\n // Include the currently-focused element even if it reports zero layout\n // (e.g. mid-transition) — same carve-out the old implementation had, now\n // resolved via the composed-tree activeElement so it still applies when the\n // focused node is nested inside a shadow root.\n const active = composedActiveElement(container.getRootNode() as Document | ShadowRoot)\n return queryTabbables(container, { includeElement: active })\n}\n\nexport function createFocusTrap(container: Element): FocusTrap {\n let previouslyFocused: HTMLElement | null = null\n let active = false\n\n const onKeydown = (ev: KeyboardEvent): void => {\n if (!active || ev.key !== 'Tab') return\n const items = focusables(container)\n const first = items[0]\n const last = items[items.length - 1]\n if (!first || !last) {\n ev.preventDefault()\n return\n }\n const current = composedActiveElement(container.getRootNode() as Document | ShadowRoot)\n\n if (ev.shiftKey && (current === first || !composedContains(container, current))) {\n ev.preventDefault()\n last.focus()\n } else if (!ev.shiftKey && (current === last || !composedContains(container, current))) {\n ev.preventDefault()\n first.focus()\n }\n }\n\n return {\n activate(): void {\n if (active) return\n active = true\n previouslyFocused = composedActiveElement(document) as HTMLElement | null\n const items = focusables(container)\n const target = items[0] ?? (container as HTMLElement)\n // Ensure the container itself is focusable as a fallback.\n if (!items.length && !(container as HTMLElement).hasAttribute('tabindex')) {\n ;(container as HTMLElement).setAttribute('tabindex', '-1')\n }\n target.focus()\n document.addEventListener('keydown', onKeydown, true)\n },\n deactivate(): void {\n if (!active) return\n active = false\n document.removeEventListener('keydown', onKeydown, true)\n // Return focus to whatever opened the trap.\n previouslyFocused?.focus?.()\n previouslyFocused = null\n },\n }\n}\n","/**\n * Headless dialog — `<aihu-dialog-root>` (state owner) + pieces\n * `<aihu-dialog-trigger>`, `<aihu-dialog-content>`, `<aihu-dialog-backdrop>`,\n * `<aihu-dialog-close>`, `<aihu-dialog-title>`, `<aihu-dialog-description>`.\n * Implements the WAI-ARIA APG **Dialog (Modal)** pattern: focus-trap +\n * return-focus, Escape-to-close, `role=dialog` / `aria-modal` /\n * `aria-labelledby` / `aria-describedby`, trigger `aria-haspopup` /\n * `aria-expanded` / `aria-controls`. Emits NO CSS — every piece reflects\n * `data-state=\"open\"|\"closed\"` for the consumer to style.\n */\n\nimport { effect, type Read, signal } from '@aihu/signals'\nimport { createDomContext, injectValue, provideContext } from '../dom-context.ts'\nimport { createFocusTrap, type FocusTrap } from './focus-trap.ts'\n\nexport interface DialogContextValue {\n readonly open: Read<boolean>\n readonly modal: Read<boolean>\n readonly contentId: Read<string>\n readonly titleId: Read<string | null>\n readonly descriptionId: Read<string | null>\n setTitleId(id: string): void\n setDescriptionId(id: string): void\n setOpen(next: boolean): void\n close(): void\n toggle(): void\n}\n\nexport const dialogContext = createDomContext<DialogContextValue>('dialog')\n\nlet _idSeq = 0\nconst uid = (p: string): string => `aihu-${p}-${(_idSeq += 1)}`\n\nexport class AihuDialogRoot extends HTMLElement {\n static readonly observedAttributes = ['open', 'modal']\n\n private readonly _open = signal(false)\n private readonly _modal = signal(true)\n private readonly _contentId = signal(uid('dialog'))\n private readonly _titleId = signal<string | null>(null)\n private readonly _descriptionId = signal<string | null>(null)\n private _disposers: Array<() => void> = []\n private _ctx: DialogContextValue\n\n constructor() {\n super()\n this._ctx = {\n open: this._open[0],\n modal: this._modal[0],\n contentId: this._contentId[0],\n titleId: this._titleId[0],\n descriptionId: this._descriptionId[0],\n setTitleId: (id) => this._titleId[1](id),\n setDescriptionId: (id) => this._descriptionId[1](id),\n setOpen: (next) => this.setOpen(next),\n close: () => this.setOpen(false),\n toggle: () => this.setOpen(!this._open[0]()),\n }\n provideContext(this, dialogContext, this._ctx)\n }\n\n get open(): Read<boolean> {\n return this._open[0]\n }\n\n setOpen(next: boolean): void {\n if (next === this._open[0]()) return\n this._open[1](next)\n if (next) this.setAttribute('open', '')\n else this.removeAttribute('open')\n }\n\n connectedCallback(): void {\n if (this.hasAttribute('modal')) this._modal[1](this.getAttribute('modal') !== 'false')\n this._open[1](this.hasAttribute('open'))\n this._disposers.push(\n effect(() => {\n this.setAttribute('data-state', this._open[0]() ? 'open' : 'closed')\n }),\n )\n }\n\n disconnectedCallback(): void {\n for (const d of this._disposers) d()\n this._disposers = []\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (name === 'open') this._open[1](value !== null)\n if (name === 'modal') this._modal[1](value !== 'false')\n }\n}\n\n/** Base for pieces that inject the dialog context lazily on connect. */\nabstract class DialogPiece extends HTMLElement {\n protected ctx!: DialogContextValue\n protected disposers: Array<() => void> = []\n\n connectedCallback(): void {\n this.ctx = injectValue(this, dialogContext)\n this.onConnect()\n }\n\n disconnectedCallback(): void {\n for (const d of this.disposers) d()\n this.disposers = []\n }\n\n protected reflectState(): void {\n this.disposers.push(\n effect(() => {\n this.setAttribute('data-state', this.ctx.open() ? 'open' : 'closed')\n }),\n )\n }\n\n protected abstract onConnect(): void\n}\n\nexport class AihuDialogTrigger extends DialogPiece {\n protected onConnect(): void {\n if (this.tagName !== 'BUTTON') {\n if (!this.hasAttribute('role')) this.setAttribute('role', 'button')\n if (!this.hasAttribute('tabindex')) this.setAttribute('tabindex', '0')\n }\n this.setAttribute('aria-haspopup', 'dialog')\n this.addEventListener('click', this._onClick)\n this.disposers.push(\n effect(() => {\n const open = this.ctx.open()\n this.setAttribute('aria-expanded', String(open))\n this.setAttribute('aria-controls', this.ctx.contentId())\n }),\n )\n this.reflectState()\n }\n\n private readonly _onClick = (): void => {\n this.ctx.toggle()\n }\n}\n\nexport class AihuDialogContent extends DialogPiece {\n private _trap: FocusTrap | null = null\n\n protected onConnect(): void {\n this.setAttribute('role', this.getAttribute('role') ?? 'dialog')\n this.addEventListener('keydown', this._onKeydown)\n this.disposers.push(\n effect(() => {\n const open = this.ctx.open()\n this.id = this.ctx.contentId()\n if (this.ctx.modal()) this.setAttribute('aria-modal', 'true')\n const titleId = this.ctx.titleId()\n if (titleId) this.setAttribute('aria-labelledby', titleId)\n const descId = this.ctx.descriptionId()\n if (descId) this.setAttribute('aria-describedby', descId)\n this.setAttribute('data-state', open ? 'open' : 'closed')\n if (open) this._activateTrap()\n else this._deactivateTrap()\n }),\n )\n }\n\n override disconnectedCallback(): void {\n this._deactivateTrap()\n this.removeEventListener('keydown', this._onKeydown)\n super.disconnectedCallback()\n }\n\n private _activateTrap(): void {\n if (this._trap) return\n this._trap = createFocusTrap(this)\n this._trap.activate()\n }\n\n private _deactivateTrap(): void {\n this._trap?.deactivate()\n this._trap = null\n }\n\n private readonly _onKeydown = (ev: KeyboardEvent): void => {\n if (ev.key === 'Escape' && this.getAttribute('data-dismissable-escape') !== 'false') {\n ev.stopPropagation()\n this.ctx.close()\n }\n }\n}\n\nexport class AihuDialogBackdrop extends DialogPiece {\n protected onConnect(): void {\n this.addEventListener('click', this._onClick)\n this.reflectState()\n }\n\n private readonly _onClick = (): void => {\n if (this.ctx.modal() && this.getAttribute('data-dismissable-outside') !== 'false') {\n this.ctx.close()\n }\n }\n}\n\nexport class AihuDialogClose extends DialogPiece {\n protected onConnect(): void {\n if (this.tagName !== 'BUTTON') {\n if (!this.hasAttribute('role')) this.setAttribute('role', 'button')\n if (!this.hasAttribute('tabindex')) this.setAttribute('tabindex', '0')\n }\n this.addEventListener('click', this._onClick)\n this.reflectState()\n }\n\n private readonly _onClick = (): void => {\n this.ctx.close()\n }\n}\n\nexport class AihuDialogTitle extends DialogPiece {\n protected onConnect(): void {\n if (!this.id) this.id = uid('dialog-title')\n this.ctx.setTitleId(this.id)\n }\n}\n\nexport class AihuDialogDescription extends DialogPiece {\n protected onConnect(): void {\n if (!this.id) this.id = uid('dialog-desc')\n this.ctx.setDescriptionId(this.id)\n }\n}\n\nconst REGISTRY: Array<[string, CustomElementConstructor]> = [\n ['aihu-dialog-root', AihuDialogRoot],\n ['aihu-dialog-trigger', AihuDialogTrigger],\n ['aihu-dialog-content', AihuDialogContent],\n ['aihu-dialog-backdrop', AihuDialogBackdrop],\n ['aihu-dialog-close', AihuDialogClose],\n ['aihu-dialog-title', AihuDialogTitle],\n ['aihu-dialog-description', AihuDialogDescription],\n]\n\nconst _definedPrefixes = new Set<string>()\n/**\n * Register all dialog custom elements under `<prefix>-dialog-*` (idempotent\n * per prefix). Non-default prefixes register a fresh trivial subclass per\n * piece — a constructor can only be `customElements.define`d once, so the\n * original classes stay reserved for the default tags. Demos/stories use a\n * non-`aihu` prefix so styled recipes own the `aihu-dialog-*` namespace\n * (spec §9.4).\n */\nexport function defineDialog(prefix = 'aihu'): void {\n if (_definedPrefixes.has(prefix)) return\n for (const [tag, ctor] of REGISTRY) {\n const name = prefix === 'aihu' ? tag : tag.replace(/^aihu-/, `${prefix}-`)\n if (!customElements.get(name)) {\n customElements.define(name, prefix === 'aihu' ? ctor : class extends ctor {})\n }\n }\n _definedPrefixes.add(prefix)\n}\n\nexport { createFocusTrap, type FocusTrap } from './focus-trap.ts'\n"],"mappings":"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"}
package/dist/dialog.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as AihuDialogRoot, c as DialogContextValue, d as FocusTrap, f as createFocusTrap, i as AihuDialogDescription, l as defineDialog, n as AihuDialogClose, o as AihuDialogTitle, r as AihuDialogContent, s as AihuDialogTrigger, t as AihuDialogBackdrop, u as dialogContext } from "./index-D7hJLfnb.js";
1
+ import { a as AihuDialogRoot, c as DialogContextValue, d as FocusTrap, f as createFocusTrap, i as AihuDialogDescription, l as defineDialog, n as AihuDialogClose, o as AihuDialogTitle, r as AihuDialogContent, s as AihuDialogTrigger, t as AihuDialogBackdrop, u as dialogContext } from "./index-CyG_gCVH.js";
2
2
  export { AihuDialogBackdrop, AihuDialogClose, AihuDialogContent, AihuDialogDescription, AihuDialogRoot, AihuDialogTitle, AihuDialogTrigger, DialogContextValue, FocusTrap, createFocusTrap, defineDialog, dialogContext };
package/dist/dialog.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as AihuDialogRoot, c as defineDialog, i as AihuDialogDescription, l as dialogContext, n as AihuDialogClose, o as AihuDialogTitle, r as AihuDialogContent, s as AihuDialogTrigger, t as AihuDialogBackdrop, u as createFocusTrap } from "./dialog-VDL-W3Vy.js";
1
+ import { a as AihuDialogRoot, c as defineDialog, i as AihuDialogDescription, l as dialogContext, n as AihuDialogClose, o as AihuDialogTitle, r as AihuDialogContent, s as AihuDialogTrigger, t as AihuDialogBackdrop, u as createFocusTrap } from "./dialog-BHtBlVHN.js";
2
2
  export { AihuDialogBackdrop, AihuDialogClose, AihuDialogContent, AihuDialogDescription, AihuDialogRoot, AihuDialogTitle, AihuDialogTrigger, createFocusTrap, defineDialog, dialogContext };
@@ -1,4 +1,5 @@
1
1
  import { createDomContext, provideContext } from "./dom-context.js";
2
+ import { n as composedClosest, o as composedQuerySelector, s as composedQuerySelectorAll } from "./composed-tree-OMbp4NQn.js";
2
3
  import { effect, signal } from "@aihu/signals";
3
4
  //#region src/form-control/hidden-input.ts
4
5
  /**
@@ -8,7 +9,9 @@ import { effect, signal } from "@aihu/signals";
8
9
  * native `FormData` / form submission with zero re-implementation.
9
10
  *
10
11
  * The input only exists while BOTH conditions hold: the host is inside a
11
- * `<form>` (`host.closest('form')`) AND `name()` is non-null. When `name()`
12
+ * `<form>` (composed-tree `closest('form')` crosses shadow boundaries so an
13
+ * intervening shadow-DOM'd wrapper doesn't hide the form) AND `name()` is
14
+ * non-null. When `name()`
12
15
  * becomes null the input is removed (reactively, inside the effect). The
13
16
  * inline visually-hidden styles are behavioral plumbing on a functional
14
17
  * element, not appearance — they do not violate the zero-CSS contract.
@@ -37,7 +40,7 @@ function attachHiddenInput(host, opts) {
37
40
  const checked = opts.checked();
38
41
  const required = opts.required();
39
42
  const disabled = opts.disabled();
40
- if (name === null || host.closest("form") === null) {
43
+ if (name === null || composedClosest(host, "form") === null) {
41
44
  remove();
42
45
  return;
43
46
  }
@@ -177,15 +180,17 @@ var AihuFormControl = class extends HTMLElement {
177
180
  this._required[1](this.hasAttribute("required"));
178
181
  this._invalid[1](this.hasAttribute("invalid"));
179
182
  }
180
- /** The slotted control element (input/select/textarea/[data-fc-control]). */
183
+ /** The slotted control element (input/select/textarea/[data-fc-control]).
184
+ * Composed-tree aware: a control nested behind an intervening shadow-DOM'd
185
+ * wrapper (legitimate opt-in shadow composition) is still found. */
181
186
  _control() {
182
- return this.querySelector("[data-fc-control], input, select, textarea, [role=\"textbox\"]");
187
+ return composedQuerySelector(this, "[data-fc-control], input, select, textarea, [role=\"textbox\"]");
183
188
  }
184
189
  _wireAssociations() {
185
190
  const control = this._control();
186
191
  if (control) if (!control.id) control.id = this._controlId[0]();
187
192
  else this._controlId[1](control.id);
188
- const label = this.querySelector("label, [data-fc-label]");
193
+ const label = composedQuerySelector(this, "label, [data-fc-label]");
189
194
  if (label && control) if (label instanceof HTMLLabelElement) {
190
195
  label.htmlFor = control.id;
191
196
  this._labelId[1](label.id || null);
@@ -196,7 +201,7 @@ var AihuFormControl = class extends HTMLElement {
196
201
  this._labelId[1](label.id);
197
202
  }
198
203
  const described = [];
199
- for (const el of this.querySelectorAll("[data-fc-description], [data-fc-error]")) {
204
+ for (const el of composedQuerySelectorAll(this, "[data-fc-description], [data-fc-error]")) {
200
205
  if (!el.id) el.id = nextId();
201
206
  described.push(el.id);
202
207
  }
@@ -220,4 +225,4 @@ function defineFormControl(tag = "aihu-form-control") {
220
225
  //#endregion
221
226
  export { attachHiddenInput as i, defineFormControl as n, formControlContext as r, AihuFormControl as t };
222
227
 
223
- //# sourceMappingURL=form-control-B_BO9j7_.js.map
228
+ //# sourceMappingURL=form-control-CRF3PWcK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-control-CRF3PWcK.js","names":[],"sources":["../src/form-control/hidden-input.ts","../src/form-control/index.ts"],"sourcesContent":["/**\n * `attachHiddenInput` — form-association substrate for custom form controls\n * (checkbox/switch/radio hosts). Mirrors a host's reactive state onto a\n * visually-hidden native `<input>` in the host's LIGHT DOM so the value rides\n * native `FormData` / form submission with zero re-implementation.\n *\n * The input only exists while BOTH conditions hold: the host is inside a\n * `<form>` (composed-tree `closest('form')` — crosses shadow boundaries so an\n * intervening shadow-DOM'd wrapper doesn't hide the form) AND `name()` is\n * non-null. When `name()`\n * becomes null the input is removed (reactively, inside the effect). The\n * inline visually-hidden styles are behavioral plumbing on a functional\n * element, not appearance — they do not violate the zero-CSS contract.\n *\n * The input is inserted as the host's NEXT SIBLING, not a child: hosts like\n * checkbox/switch carry an interactive ARIA role (`role=\"checkbox\"` etc.), and\n * a nested native form control would be an `aria-hidden`-not-withstanding\n * `nested-interactive` violation. Sibling placement keeps it in the same form\n * (so `FormData` is unchanged) while staying out of the host's subtree. When\n * the host has no parent yet (pre-insertion), it falls back to a child.\n */\n\nimport { effect, type Read } from '@aihu/signals'\nimport { composedClosest } from '../composed-tree.ts'\n\nexport interface HiddenInputOptions {\n type: 'checkbox' | 'radio'\n name: Read<string | null>\n value: Read<string>\n checked: Read<boolean>\n required: Read<boolean>\n disabled: Read<boolean>\n}\n\n/**\n * Attach a visually-hidden native input to `host`, kept in sync with the\n * provided signals by a single effect. Returns a disposer that stops the\n * effect and removes the input.\n */\nexport function attachHiddenInput(host: HTMLElement, opts: HiddenInputOptions): () => void {\n let input: HTMLInputElement | null = null\n\n const remove = (): void => {\n input?.remove()\n input = null\n }\n\n const stop = effect(() => {\n // Read every signal unconditionally so the effect re-runs on any change\n // (including `name` flipping back from null).\n const name = opts.name()\n const value = opts.value()\n const checked = opts.checked()\n const required = opts.required()\n const disabled = opts.disabled()\n\n if (name === null || composedClosest(host, 'form') === null) {\n remove()\n return\n }\n\n if (input === null) {\n input = document.createElement('input')\n input.type = opts.type\n input.setAttribute('aria-hidden', 'true')\n input.setAttribute('tabindex', '-1')\n // Visually hidden, but still form-associated. Behavior, not appearance.\n input.style.position = 'absolute'\n input.style.opacity = '0'\n input.style.pointerEvents = 'none'\n input.style.margin = '0'\n input.style.transform = 'translateX(-100%)'\n // Sibling-after-host (see header): avoids nested-interactive on roled\n // hosts. Fall back to a child if the host isn't inserted yet.\n if (host.parentNode !== null) host.after(input)\n else host.appendChild(input)\n }\n input.name = name\n input.value = value\n input.checked = checked\n input.required = required\n input.disabled = disabled\n })\n\n return () => {\n stop()\n remove()\n }\n}\n","/**\n * `<aihu-form-control>` — headless form-field coordinator. Owns the\n * disabled/required/invalid state for a field and wires the ARIA association\n * between a slotted control, its label, and its error/description text. Emits\n * NO CSS.\n *\n * Reflected attributes: `disabled`, `required`, `invalid` (boolean); `name`,\n * `control-id` (string).\n * Owned signals: `disabled`, `required`, `invalid`.\n * ARIA emitted onto the slotted control: `aria-required`, `aria-invalid`,\n * `aria-disabled`, `aria-describedby` (wired to a slotted `[data-fc-description]`\n * / `[data-fc-error]` element's id); a slotted `<label>` is associated via\n * `for`/`id`.\n * Context: provides `formControlContext` carrying\n * `{ disabled, required, invalid, controlId, describedById, labelId }` signals\n * so descendant pieces consume the shared state without prop-drilling.\n */\n\nimport { effect, type Read, signal } from '@aihu/signals'\nimport { composedQuerySelector, composedQuerySelectorAll } from '../composed-tree.ts'\nimport { createDomContext, provideContext } from '../dom-context.ts'\n\nexport { attachHiddenInput, type HiddenInputOptions } from './hidden-input.ts'\n\nexport interface FormControlContextValue {\n readonly disabled: Read<boolean>\n readonly required: Read<boolean>\n readonly invalid: Read<boolean>\n readonly controlId: Read<string>\n readonly describedById: Read<string | null>\n readonly labelId: Read<string | null>\n}\n\nexport const formControlContext = createDomContext<FormControlContextValue>('form-control')\n\nlet _idCounter = 0\nfunction nextId(): string {\n _idCounter += 1\n return `aihu-fc-${_idCounter}`\n}\n\nexport class AihuFormControl extends HTMLElement {\n static readonly observedAttributes = ['disabled', 'required', 'invalid', 'name', 'control-id']\n\n private readonly _disabled = signal(false)\n private readonly _required = signal(false)\n private readonly _invalid = signal(false)\n private readonly _controlId = signal('')\n private readonly _describedById = signal<string | null>(null)\n private readonly _labelId = signal<string | null>(null)\n\n private _disposers: Array<() => void> = []\n\n get disabled(): Read<boolean> {\n return this._disabled[0]\n }\n get required(): Read<boolean> {\n return this._required[0]\n }\n get invalid(): Read<boolean> {\n return this._invalid[0]\n }\n get controlId(): Read<string> {\n return this._controlId[0]\n }\n get labelId(): Read<string | null> {\n return this._labelId[0]\n }\n\n constructor() {\n super()\n provideContext(this, formControlContext, {\n disabled: this._disabled[0],\n required: this._required[0],\n invalid: this._invalid[0],\n controlId: this._controlId[0],\n describedById: this._describedById[0],\n labelId: this._labelId[0],\n })\n }\n\n connectedCallback(): void {\n // Stable control id (generated if not supplied).\n const supplied = this.getAttribute('control-id')\n this._controlId[1](supplied ?? nextId())\n\n this._syncBooleans()\n this._wireAssociations()\n\n // Reflect ARIA onto the slotted control reactively.\n this._disposers.push(\n effect(() => {\n const control = this._control()\n if (!control) return\n reflectAria(control, 'aria-disabled', this._disabled[0]())\n reflectAria(control, 'aria-required', this._required[0]())\n reflectAria(control, 'aria-invalid', this._invalid[0]())\n const describedBy = this._describedById[0]()\n if (describedBy) control.setAttribute('aria-describedby', describedBy)\n else control.removeAttribute('aria-describedby')\n }),\n )\n }\n\n disconnectedCallback(): void {\n for (const d of this._disposers) d()\n this._disposers = []\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n switch (name) {\n case 'disabled':\n this._disabled[1](value !== null)\n break\n case 'required':\n this._required[1](value !== null)\n break\n case 'invalid':\n this._invalid[1](value !== null)\n break\n case 'control-id':\n if (value) this._controlId[1](value)\n break\n }\n }\n\n /** Re-derive `aria-describedby` from the slotted description/error pieces.\n * Call after a piece mounts/unmounts. */\n recomputeDescribedBy(): void {\n this._wireAssociations()\n }\n\n private _syncBooleans(): void {\n this._disabled[1](this.hasAttribute('disabled'))\n this._required[1](this.hasAttribute('required'))\n this._invalid[1](this.hasAttribute('invalid'))\n }\n\n /** The slotted control element (input/select/textarea/[data-fc-control]).\n * Composed-tree aware: a control nested behind an intervening shadow-DOM'd\n * wrapper (legitimate opt-in shadow composition) is still found. */\n private _control(): HTMLElement | null {\n return composedQuerySelector<HTMLElement>(\n this,\n '[data-fc-control], input, select, textarea, [role=\"textbox\"]',\n )\n }\n\n private _wireAssociations(): void {\n const control = this._control()\n if (control) {\n if (!control.id) control.id = this._controlId[0]()\n else this._controlId[1](control.id)\n }\n\n // Associate a slotted label (composed-tree aware — see `_control` above).\n const label = composedQuerySelector<HTMLElement>(this, 'label, [data-fc-label]')\n if (label && control) {\n if (label instanceof HTMLLabelElement) {\n label.htmlFor = control.id\n this._labelId[1](label.id || null)\n } else {\n // Non-native label ([data-fc-label]): `for` has no native semantics,\n // so associate via aria-labelledby pointing at the label's id.\n label.setAttribute('for', control.id)\n if (!label.id) label.id = nextId()\n control.setAttribute('aria-labelledby', label.id)\n this._labelId[1](label.id)\n }\n }\n\n // Collect description + error ids into aria-describedby (composed-tree\n // aware — a shadow-wrapped description/error node is still discovered).\n const described: string[] = []\n for (const el of composedQuerySelectorAll<HTMLElement>(\n this,\n '[data-fc-description], [data-fc-error]',\n )) {\n if (!el.id) el.id = nextId()\n described.push(el.id)\n }\n this._describedById[1](described.length ? described.join(' ') : null)\n }\n}\n\nfunction reflectAria(el: HTMLElement, attr: string, on: boolean): void {\n if (on) el.setAttribute(attr, 'true')\n else el.removeAttribute(attr)\n}\n\nlet _defined = false\n/** Register `<aihu-form-control>` (idempotent). */\nexport function defineFormControl(tag = 'aihu-form-control'): void {\n if (_defined || customElements.get(tag)) {\n _defined = true\n return\n }\n customElements.define(tag, AihuFormControl)\n _defined = true\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { a as HiddenInputOptions, i as formControlContext, n as FormControlContextValue, o as attachHiddenInput, r as defineFormControl, t as AihuFormControl } from "./index-BvFa1Y-Z.js";
1
+ import { a as HiddenInputOptions, i as formControlContext, n as FormControlContextValue, o as attachHiddenInput, r as defineFormControl, t as AihuFormControl } from "./index-BODOUcDC.js";
2
2
  export { AihuFormControl, FormControlContextValue, HiddenInputOptions, attachHiddenInput, defineFormControl, formControlContext };
@@ -1,2 +1,2 @@
1
- import { i as attachHiddenInput, n as defineFormControl, r as formControlContext, t as AihuFormControl } from "./form-control-B_BO9j7_.js";
1
+ import { i as attachHiddenInput, n as defineFormControl, r as formControlContext, t as AihuFormControl } from "./form-control-CRF3PWcK.js";
2
2
  export { AihuFormControl, attachHiddenInput, defineFormControl, formControlContext };
@@ -49,7 +49,9 @@ declare class AihuFormControl extends HTMLElement {
49
49
  * Call after a piece mounts/unmounts. */
50
50
  recomputeDescribedBy(): void;
51
51
  private _syncBooleans;
52
- /** The slotted control element (input/select/textarea/[data-fc-control]). */
52
+ /** The slotted control element (input/select/textarea/[data-fc-control]).
53
+ * Composed-tree aware: a control nested behind an intervening shadow-DOM'd
54
+ * wrapper (legitimate opt-in shadow composition) is still found. */
53
55
  private _control;
54
56
  private _wireAssociations;
55
57
  }
@@ -57,4 +59,4 @@ declare class AihuFormControl extends HTMLElement {
57
59
  declare function defineFormControl(tag?: string): void;
58
60
  //#endregion
59
61
  export { HiddenInputOptions as a, formControlContext as i, FormControlContextValue as n, attachHiddenInput as o, defineFormControl as r, AihuFormControl as t };
60
- //# sourceMappingURL=index-BvFa1Y-Z.d.ts.map
62
+ //# sourceMappingURL=index-BODOUcDC.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-BvFa1Y-Z.d.ts","names":[],"sources":["../src/form-control/hidden-input.ts","../src/form-control/index.ts"],"mappings":";;;;UAsBiB,kBAAA;EACf,IAAA;EACA,IAAA,EAAM,IAAA;EACN,KAAA,EAAO,IAAA;EACP,OAAA,EAAS,IAAA;EACT,QAAA,EAAU,IAAA;EACV,QAAA,EAAU,IAAA;AAAA;;;;;;iBAQI,iBAAA,CAAkB,IAAA,EAAM,WAAA,EAAa,IAAA,EAAM,kBAAA;;;UCb1C,uBAAA;EAAA,SACN,QAAA,EAAU,IAAA;EAAA,SACV,QAAA,EAAU,IAAA;EAAA,SACV,OAAA,EAAS,IAAA;EAAA,SACT,SAAA,EAAW,IAAA;EAAA,SACX,aAAA,EAAe,IAAA;EAAA,SACf,OAAA,EAAS,IAAA;AAAA;AAAA,cAGP,kBAAA,EAAkB,UAAA,CAAA,uBAAA;AAAA,cAQlB,eAAA,SAAwB,WAAA;EAAA,gBACnB,kBAAA;EAAA,iBAEC,SAAA;EAAA,iBACA,SAAA;EAAA,iBACA,QAAA;EAAA,iBACA,UAAA;EAAA,iBACA,cAAA;EAAA,iBACA,QAAA;EAAA,QAET,UAAA;EAAA,IAEJ,QAAA,CAAA,GAAY,IAAA;EAAA,IAGZ,QAAA,CAAA,GAAY,IAAA;EAAA,IAGZ,OAAA,CAAA,GAAW,IAAA;EAAA,IAGX,SAAA,CAAA,GAAa,IAAA;EAAA,IAGb,OAAA,CAAA,GAAW,IAAA;;EAgBf,iBAAA,CAAA;EAuBA,oBAAA,CAAA;EAKA,wBAAA,CAAyB,IAAA,UAAc,IAAA,iBAAqB,KAAA;EAjFxC;;EAoGpB,oBAAA,CAAA;EAAA,QAIQ,aAAA;EAtGc;EAAA,QA6Gd,QAAA;EAAA,QAMA,iBAAA;AAAA;;iBAwCM,iBAAA,CAAkB,GAAA"}
1
+ {"version":3,"file":"index-BODOUcDC.d.ts","names":[],"sources":["../src/form-control/hidden-input.ts","../src/form-control/index.ts"],"mappings":";;;;UAyBiB,kBAAA;EACf,IAAA;EACA,IAAA,EAAM,IAAA;EACN,KAAA,EAAO,IAAA;EACP,OAAA,EAAS,IAAA;EACT,QAAA,EAAU,IAAA;EACV,QAAA,EAAU,IAAA;AAAA;;;;;;iBAQI,iBAAA,CAAkB,IAAA,EAAM,WAAA,EAAa,IAAA,EAAM,kBAAA;;;UCf1C,uBAAA;EAAA,SACN,QAAA,EAAU,IAAA;EAAA,SACV,QAAA,EAAU,IAAA;EAAA,SACV,OAAA,EAAS,IAAA;EAAA,SACT,SAAA,EAAW,IAAA;EAAA,SACX,aAAA,EAAe,IAAA;EAAA,SACf,OAAA,EAAS,IAAA;AAAA;AAAA,cAGP,kBAAA,EAAkB,UAAA,CAAA,uBAAA;AAAA,cAQlB,eAAA,SAAwB,WAAA;EAAA,gBACnB,kBAAA;EAAA,iBAEC,SAAA;EAAA,iBACA,SAAA;EAAA,iBACA,QAAA;EAAA,iBACA,UAAA;EAAA,iBACA,cAAA;EAAA,iBACA,QAAA;EAAA,QAET,UAAA;EAAA,IAEJ,QAAA,CAAA,GAAY,IAAA;EAAA,IAGZ,QAAA,CAAA,GAAY,IAAA;EAAA,IAGZ,OAAA,CAAA,GAAW,IAAA;EAAA,IAGX,SAAA,CAAA,GAAa,IAAA;EAAA,IAGb,OAAA,CAAA,GAAW,IAAA;;EAgBf,iBAAA,CAAA;EAuBA,oBAAA,CAAA;EAKA,wBAAA,CAAyB,IAAA,UAAc,IAAA,iBAAqB,KAAA;EAjFxC;;EAoGpB,oBAAA,CAAA;EAAA,QAIQ,aAAA;EAtGc;;;EAAA,QA+Gd,QAAA;EAAA,QAOA,iBAAA;AAAA;;iBA4CM,iBAAA,CAAkB,GAAA"}
@@ -4,9 +4,11 @@ import { Read } from "@aihu/signals";
4
4
  //#region src/dialog/focus-trap.d.ts
5
5
  /**
6
6
  * `createFocusTrap` — a tiny native-DOM focus trap (no library). Queries the
7
- * focusable descendants of a container, wraps Tab at the edges, moves focus to
8
- * the first focusable (or the container) on activate, and restores focus to the
9
- * previously-active element on deactivate. Used by `dialog-content`.
7
+ * focusable descendants of a container via the composed-tree substrate (so it
8
+ * correctly descends into any nested custom element's OPEN shadow root), wraps
9
+ * Tab at the edges, moves focus to the first focusable (or the container) on
10
+ * activate, and restores focus to the previously-active element on deactivate.
11
+ * Used by `dialog-content`.
10
12
  */
11
13
  interface FocusTrap {
12
14
  activate(): void;
@@ -90,4 +92,4 @@ declare class AihuDialogDescription extends DialogPiece {
90
92
  declare function defineDialog(prefix?: string): void;
91
93
  //#endregion
92
94
  export { AihuDialogRoot as a, DialogContextValue as c, FocusTrap as d, createFocusTrap as f, AihuDialogDescription as i, defineDialog as l, AihuDialogClose as n, AihuDialogTitle as o, AihuDialogContent as r, AihuDialogTrigger as s, AihuDialogBackdrop as t, dialogContext as u };
93
- //# sourceMappingURL=index-D7hJLfnb.d.ts.map
95
+ //# sourceMappingURL=index-CyG_gCVH.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-CyG_gCVH.d.ts","names":[],"sources":["../src/dialog/focus-trap.ts","../src/dialog/index.ts"],"mappings":";;;;;;;;AAWA;;;;UAAiB,SAAA;EACf,QAAA;EACA,UAAA;AAAA;AAAA,iBAYc,eAAA,CAAgB,SAAA,EAAW,OAAA,GAAU,SAAA;;;UCVpC,kBAAA;EAAA,SACN,IAAA,EAAM,IAAA;EAAA,SACN,KAAA,EAAO,IAAA;EAAA,SACP,SAAA,EAAW,IAAA;EAAA,SACX,OAAA,EAAS,IAAA;EAAA,SACT,aAAA,EAAe,IAAA;EACxB,UAAA,CAAW,EAAA;EACX,gBAAA,CAAiB,EAAA;EACjB,OAAA,CAAQ,IAAA;EACR,KAAA;EACA,MAAA;AAAA;AAAA,cAGW,aAAA,EAAa,UAAA,CAAA,kBAAA;AAAA,cAKb,cAAA,SAAuB,WAAA;EAAA,gBAClB,kBAAA;EAAA,iBAEC,KAAA;EAAA,iBACA,MAAA;EAAA,iBACA,UAAA;EAAA,iBACA,QAAA;EAAA,iBACA,cAAA;EAAA,QACT,UAAA;EAAA,QACA,IAAA;;MAmBJ,IAAA,CAAA,GAAQ,IAAA;EAIZ,OAAA,CAAQ,IAAA;EAOR,iBAAA,CAAA;EAUA,oBAAA,CAAA;EAKA,wBAAA,CAAyB,IAAA,UAAc,IAAA,iBAAqB,KAAA;AAAA;;uBAO/C,WAAA,SAAoB,WAAA;EAAA,UACvB,GAAA,EAAM,kBAAA;EAAA,UACN,SAAA,EAAW,KAAA;EAErB,iBAAA,CAAA;EAKA,oBAAA,CAAA;EAAA,UAKU,YAAA,CAAA;EAAA,mBAQS,SAAA,CAAA;AAAA;AAAA,cAGR,iBAAA,SAA0B,WAAA;EAAA,UAC3B,SAAA,CAAA;EAAA,iBAiBO,QAAA;AAAA;AAAA,cAKN,iBAAA,SAA0B,WAAA;EAAA,QAC7B,KAAA;EAAA,UAEE,SAAA,CAAA;EAmBD,oBAAA,CAAA;EAAA,QAMD,aAAA;EAAA,QAMA,eAAA;EAAA,iBAKS,UAAA;AAAA;AAAA,cAQN,kBAAA,SAA2B,WAAA;EAAA,UAC5B,SAAA,CAAA;EAAA,iBAKO,QAAA;AAAA;AAAA,cAON,eAAA,SAAwB,WAAA;EAAA,UACzB,SAAA,CAAA;EAAA,iBASO,QAAA;AAAA;AAAA,cAKN,eAAA,SAAwB,WAAA;EAAA,UACzB,SAAA,CAAA;AAAA;AAAA,cAMC,qBAAA,SAA8B,WAAA;EAAA,UAC/B,SAAA,CAAA;AAAA;;;;;;;;AAtIX;iBA+Je,YAAA,CAAa,MAAA"}
@@ -28,4 +28,4 @@ declare class AihuButton extends HTMLElement {
28
28
  declare function defineButton(tag: string): typeof AihuButton;
29
29
  //#endregion
30
30
  export { ButtonType as n, defineButton as r, AihuButton as t };
31
- //# sourceMappingURL=index-yPv3StRL.d.ts.map
31
+ //# sourceMappingURL=index-Dgli1Q03.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-yPv3StRL.d.ts","names":[],"sources":["../src/button/button.ts"],"mappings":";;;KAqBY,UAAA;AAAA,cAEC,UAAA,SAAmB,WAAA;EAAA,gBACd,kBAAA;EAAA,iBAEC,SAAA;EAAA,iBACA,QAAA;EAAA,QACT,kBAAA;EAAA,QACA,UAAA;EAKQ;EAAA,QAFR,SAAA;EAAA,IAEJ,QAAA,CAAA,GAAY,IAAA;EAAA,IAGZ,OAAA,CAAA,GAAW,IAAA;EAAA,YAIH,eAAA,CAAA;EAIZ,iBAAA,CAAA;EAiCA,oBAAA,CAAA;EAOA,wBAAA,CAAyB,IAAA,UAAc,IAAA,iBAAqB,KAAA;EAArB;EAavC,MAAA,CAAA;EAAA,QAKQ,kBAAA;EAAA,QAKA,YAAA;EAAA,iBAMS,UAAA;EAAA,iBAQA,eAAA;EAAA,QAST,cAAA;AAAA;;iBAOM,YAAA,CAAa,GAAA,kBAAqB,UAAA"}
1
+ {"version":3,"file":"index-Dgli1Q03.d.ts","names":[],"sources":["../src/button/button.ts"],"mappings":";;;KAqBY,UAAA;AAAA,cAEC,UAAA,SAAmB,WAAA;EAAA,gBACd,kBAAA;EAAA,iBAEC,SAAA;EAAA,iBACA,QAAA;EAAA,QACT,kBAAA;EAAA,QACA,UAAA;EAKQ;EAAA,QAFR,SAAA;EAAA,IAEJ,QAAA,CAAA,GAAY,IAAA;EAAA,IAGZ,OAAA,CAAA,GAAW,IAAA;EAAA,YAIH,eAAA,CAAA;EAIZ,iBAAA,CAAA;EAiCA,oBAAA,CAAA;EAOA,wBAAA,CAAyB,IAAA,UAAc,IAAA,iBAAqB,KAAA;EAArB;EAavC,MAAA,CAAA;EAAA,QAKQ,kBAAA;EAAA,QAKA,YAAA;EAAA,iBAMS,UAAA;EAAA,iBAQA,eAAA;EAAA,QAST,cAAA;AAAA;;iBAOM,YAAA,CAAa,GAAA,kBAAqB,UAAA"}
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { n as ButtonType, r as defineButton, t as AihuButton } from "./index-yPv3StRL.js";
1
+ import { n as ButtonType, r as defineButton, t as AihuButton } from "./index-Dgli1Q03.js";
2
2
  import { DomContext, MissingContextError, createDomContext, injectContext, provideContext } from "./dom-context.js";
3
3
  import { AihuCheckboxIndicator, AihuCheckboxRoot, CheckboxContextValue, CheckboxState, checkboxContext, defineCheckbox } from "./checkbox.js";
4
4
  import { AihuCollection, CollectionContextValue, collectionContext, createCollection, defineCollection } from "./collection.js";
5
5
  import { AihuConfigProvider, ColorScheme, ConfigContextValue, Density, Direction, configContext, defineConfigProvider } from "./config-provider.js";
6
- import { a as AihuDialogRoot, c as DialogContextValue, d as FocusTrap, f as createFocusTrap, i as AihuDialogDescription, l as defineDialog, n as AihuDialogClose, o as AihuDialogTitle, r as AihuDialogContent, s as AihuDialogTrigger, t as AihuDialogBackdrop, u as dialogContext } from "./index-D7hJLfnb.js";
7
- import { a as HiddenInputOptions, i as formControlContext, n as FormControlContextValue, o as attachHiddenInput, r as defineFormControl, t as AihuFormControl } from "./index-BvFa1Y-Z.js";
8
- import { t as AihuTextControlBase } from "./text-control-Brv5fUEX.js";
6
+ import { a as AihuDialogRoot, c as DialogContextValue, d as FocusTrap, f as createFocusTrap, i as AihuDialogDescription, l as defineDialog, n as AihuDialogClose, o as AihuDialogTitle, r as AihuDialogContent, s as AihuDialogTrigger, t as AihuDialogBackdrop, u as dialogContext } from "./index-CyG_gCVH.js";
7
+ import { a as HiddenInputOptions, i as formControlContext, n as FormControlContextValue, o as attachHiddenInput, r as defineFormControl, t as AihuFormControl } from "./index-BODOUcDC.js";
8
+ import { t as AihuTextControlBase } from "./text-control-wKoKU_--.js";
9
9
  import { AihuInput, defineInput } from "./input.js";
10
10
  import { AihuLabel, defineLabel } from "./label.js";
11
11
  import { AihuPresenceGate, definePresenceGate, presenceContext } from "./presence-gate.js";
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { MissingContextError, createDomContext, injectContext, provideContext } from "./dom-context.js";
2
- import { i as attachHiddenInput, n as defineFormControl, r as formControlContext, t as AihuFormControl } from "./form-control-B_BO9j7_.js";
3
- import { n as defineButton, t as AihuButton } from "./button-Cha1gKlr.js";
2
+ import { i as attachHiddenInput, n as defineFormControl, r as formControlContext, t as AihuFormControl } from "./form-control-CRF3PWcK.js";
3
+ import { n as defineButton, t as AihuButton } from "./button-RgkdWi4q.js";
4
4
  import { AihuCheckboxIndicator, AihuCheckboxRoot, checkboxContext, defineCheckbox } from "./checkbox.js";
5
5
  import { AihuCollection, collectionContext, createCollection, defineCollection } from "./collection.js";
6
6
  import { AihuConfigProvider, configContext, defineConfigProvider } from "./config-provider.js";
7
- import { a as AihuDialogRoot, c as defineDialog, i as AihuDialogDescription, l as dialogContext, n as AihuDialogClose, o as AihuDialogTitle, r as AihuDialogContent, s as AihuDialogTrigger, t as AihuDialogBackdrop, u as createFocusTrap } from "./dialog-VDL-W3Vy.js";
8
- import { t as AihuTextControlBase } from "./text-control-BBX7s8Oe.js";
7
+ import { a as AihuDialogRoot, c as defineDialog, i as AihuDialogDescription, l as dialogContext, n as AihuDialogClose, o as AihuDialogTitle, r as AihuDialogContent, s as AihuDialogTrigger, t as AihuDialogBackdrop, u as createFocusTrap } from "./dialog-BHtBlVHN.js";
8
+ import { t as AihuTextControlBase } from "./text-control-CUbTuk5L.js";
9
9
  import { AihuInput, defineInput } from "./input.js";
10
10
  import { AihuLabel, defineLabel } from "./label.js";
11
11
  import { AihuPresenceGate, definePresenceGate, presenceContext } from "./presence-gate.js";
package/dist/input.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as AihuTextControlBase } from "./text-control-Brv5fUEX.js";
1
+ import { t as AihuTextControlBase } from "./text-control-wKoKU_--.js";
2
2
 
3
3
  //#region src/input/index.d.ts
4
4
  declare class AihuInput extends AihuTextControlBase {
package/dist/input.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as TEXT_CONTROL_OBSERVED, t as AihuTextControlBase } from "./text-control-BBX7s8Oe.js";
1
+ import { n as TEXT_CONTROL_OBSERVED, t as AihuTextControlBase } from "./text-control-CUbTuk5L.js";
2
2
  //#region src/input/index.ts
3
3
  /**
4
4
  * `<aihu-input>` — headless single-line text control. Wraps (or creates) a
@@ -1 +1 @@
1
- {"version":3,"file":"label.d.ts","names":[],"sources":["../src/label/index.ts"],"mappings":";;;cA2Ca,SAAA,SAAkB,WAAA;EAAA,gBACb,kBAAA;EAAA,iBAEC,IAAA;EAAA,QACT,GAAA;EAAA,QACA,UAAA;;MAGJ,KAAA,CAAA,GAAS,IAAA;EAAA,YAID,cAAA,CAAA;EAIZ,iBAAA,CAAA;EA4CA,oBAAA,CAAA;EAOA,wBAAA,CAAyB,IAAA,UAAc,IAAA,iBAAqB,KAAA;;UAKpD,cAAA;EAAA,iBAWS,YAAA;EAAA,iBAKA,QAAA;AAAA;;iBAwCH,WAAA,CAAY,GAAA"}
1
+ {"version":3,"file":"label.d.ts","names":[],"sources":["../src/label/index.ts"],"mappings":";;;cA4Ca,SAAA,SAAkB,WAAA;EAAA,gBACb,kBAAA;EAAA,iBAEC,IAAA;EAAA,QACT,GAAA;EAAA,QACA,UAAA;;MAGJ,KAAA,CAAA,GAAS,IAAA;EAAA,YAID,cAAA,CAAA;EAIZ,iBAAA,CAAA;EA8CA,oBAAA,CAAA;EAOA,wBAAA,CAAyB,IAAA,UAAc,IAAA,iBAAqB,KAAA;;UAKpD,cAAA;EAAA,iBAWS,YAAA;EAAA,iBAKA,QAAA;AAAA;;iBA+CH,WAAA,CAAY,GAAA"}
package/dist/label.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { injectValue } from "./dom-context.js";
2
- import { r as formControlContext, t as AihuFormControl } from "./form-control-B_BO9j7_.js";
2
+ import { a as composedParent, i as composedContains, n as composedClosest } from "./composed-tree-OMbp4NQn.js";
3
+ import { r as formControlContext, t as AihuFormControl } from "./form-control-CRF3PWcK.js";
3
4
  import { effect, signal } from "@aihu/signals";
4
5
  //#region src/label/index.ts
5
6
  /**
@@ -57,13 +58,13 @@ var AihuLabel = class extends HTMLElement {
57
58
  this._fc = null;
58
59
  }
59
60
  if (this._fc) {
60
- let node = this.parentElement;
61
- while (node) {
61
+ let node = composedParent(this);
62
+ while (node !== null) {
62
63
  if (node instanceof AihuFormControl) {
63
64
  node.recomputeDescribedBy();
64
65
  break;
65
66
  }
66
- node = node.parentElement;
67
+ node = composedParent(node);
67
68
  }
68
69
  } else if (!this._isNativeLabel) this._disposers.push(effect(() => {
69
70
  this._for[0]();
@@ -101,10 +102,11 @@ var AihuLabel = class extends HTMLElement {
101
102
  _onClick = (ev) => {
102
103
  const target = this._resolveTarget();
103
104
  if (!target) return;
104
- const origin = ev.target instanceof Element ? ev.target : null;
105
- const interactive = origin?.closest("button,input,select,textarea,a") ?? null;
105
+ const path = ev.composedPath();
106
+ const origin = path[0] instanceof Element ? path[0] : ev.target instanceof Element ? ev.target : null;
107
+ const interactive = origin ? composedClosest(origin, "button,input,select,textarea,a") : null;
106
108
  if (interactive !== null && interactive !== target) return;
107
- if (origin !== null && (origin === target || target.contains(origin))) return;
109
+ if (origin !== null && (origin === target || composedContains(target, origin))) return;
108
110
  if (target.getAttribute("aria-disabled") === "true" || target.disabled === true) return;
109
111
  const tag = target.tagName;
110
112
  const inputType = tag === "INPUT" ? target.type : null;
package/dist/label.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"label.js","names":[],"sources":["../src/label/index.ts"],"sourcesContent":["/**\n * `<aihu-label>` — headless label (the Radix Label parity primitive). Wires\n * the label↔control association and forwards interactions the way a native\n * `<label>` does, for targets that native labels cannot reference (custom\n * hosts, role=checkbox/switch/radio elements). Ships NO CSS.\n *\n * Behavior:\n * - On connect: ensures a stable `id`, stamps `data-fc-label` on itself so an\n * ancestor `<aihu-form-control>` discovers it, and (when inside one) asks\n * that ancestor to re-wire so the control gains\n * `aria-labelledby=\"<labelId>\"`.\n * - Target resolution (re-resolved per interaction, never cached): the `for`\n * attribute is looked up via `getElementById` in the label's root; with no\n * `for`, a `formControlContext` ancestor's `controlId` is used.\n * - Standalone (no form-control ancestor): sets `aria-labelledby` = own id on\n * the resolved target reactively. Skipped when the target lives in a\n * different root — ARIA IDREFs cannot cross shadow boundaries (see\n * accessibility.md; use `aria-label` on the target for cross-root cases).\n * - Click forwarding (non-native hosts only): double-click `mousedown` is\n * prevented (no accidental text selection); `click` focuses native text\n * controls, forwards `click()` to native checkbox/radio or custom\n * checkbox/switch/radio hosts, and does nothing for disabled targets or\n * clicks originating on nested interactive children.\n * - If the host IS a native `<label>` (tagName `LABEL`), click forwarding is\n * native — only the context wiring above applies.\n *\n * Reflected attributes: `for` (optional explicit target id).\n */\n\nimport { effect, type Read, signal } from '@aihu/signals'\nimport { injectValue } from '../dom-context.ts'\nimport {\n AihuFormControl,\n type FormControlContextValue,\n formControlContext,\n} from '../form-control/index.ts'\n\nlet _idCounter = 0\nfunction nextId(): string {\n _idCounter += 1\n return `aihu-label-${_idCounter}`\n}\n\nexport class AihuLabel extends HTMLElement {\n static readonly observedAttributes = ['for']\n\n private readonly _for = signal<string | null>(null)\n private _fc: FormControlContextValue | null = null\n private _disposers: Array<() => void> = []\n\n /** The explicit target id (`for` attribute) as a signal; null when unset. */\n get forId(): Read<string | null> {\n return this._for[0]\n }\n\n private get _isNativeLabel(): boolean {\n return this.tagName === 'LABEL'\n }\n\n connectedCallback(): void {\n this._for[1](this.getAttribute('for'))\n if (!this.id) this.id = nextId()\n // Make this label discoverable by an ancestor <aihu-form-control>.\n this.setAttribute('data-fc-label', '')\n\n try {\n this._fc = injectValue(this, formControlContext)\n } catch {\n this._fc = null\n }\n\n if (this._fc) {\n // The form-control connected (and wired) before this label existed in\n // its eyes — ask it to re-derive associations so the control gains\n // aria-labelledby pointing at this label.\n let node: Element | null = this.parentElement\n while (node) {\n if (node instanceof AihuFormControl) {\n node.recomputeDescribedBy()\n break\n }\n node = node.parentElement\n }\n } else if (!this._isNativeLabel) {\n // Standalone: stamp aria-labelledby on the resolved target reactively.\n // Skip cross-root targets — IDREFs cannot cross shadow boundaries.\n this._disposers.push(\n effect(() => {\n this._for[0]() // track `for` changes\n const target = this._resolveTarget()\n if (target && target.getRootNode() === this.getRootNode()) {\n target.setAttribute('aria-labelledby', this.id)\n }\n }),\n )\n }\n\n if (!this._isNativeLabel) {\n this.addEventListener('mousedown', this._onMousedown)\n this.addEventListener('click', this._onClick)\n }\n }\n\n disconnectedCallback(): void {\n this.removeEventListener('mousedown', this._onMousedown)\n this.removeEventListener('click', this._onClick)\n for (const d of this._disposers) d()\n this._disposers = []\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (name === 'for') this._for[1](value)\n }\n\n /** Resolve the labelled target NOW (per interaction — never cached). */\n private _resolveTarget(): HTMLElement | null {\n const root = this.getRootNode() as Document | ShadowRoot\n const forId = this._for[0]()\n if (forId) return (root.getElementById(forId) as HTMLElement | null) ?? null\n if (this._fc) {\n const id = this._fc.controlId()\n if (id) return (root.getElementById(id) as HTMLElement | null) ?? null\n }\n return null\n }\n\n private readonly _onMousedown = (ev: MouseEvent): void => {\n // Prevent text selection on double-click (native label parity).\n if (ev.detail > 1) ev.preventDefault()\n }\n\n private readonly _onClick = (ev: MouseEvent): void => {\n const target = this._resolveTarget()\n if (!target) return\n\n const origin = ev.target instanceof Element ? ev.target : null\n // Clicks originating on a nested interactive child (that is not the\n // labelled target) are the child's business — don't forward.\n const interactive = origin?.closest('button,input,select,textarea,a') ?? null\n if (interactive !== null && interactive !== target) return\n // Clicks already on/inside the target need no forwarding (and forwarding\n // a click back to the target would recurse).\n if (origin !== null && (origin === target || target.contains(origin))) return\n\n // Disabled targets don't get forwarded interactions.\n if (\n target.getAttribute('aria-disabled') === 'true' ||\n (target as HTMLInputElement).disabled === true\n ) {\n return\n }\n\n const tag = target.tagName\n const inputType = tag === 'INPUT' ? (target as HTMLInputElement).type : null\n if (inputType === 'checkbox' || inputType === 'radio') {\n target.click()\n return\n }\n if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') {\n target.focus()\n return\n }\n const role = target.getAttribute('role')\n if (role === 'checkbox' || role === 'switch' || role === 'radio') {\n target.click()\n }\n }\n}\n\nlet _defined = false\n/** Register `<aihu-label>` (idempotent). */\nexport function defineLabel(tag = 'aihu-label'): void {\n if (_defined || customElements.get(tag)) {\n _defined = true\n return\n }\n customElements.define(tag, AihuLabel)\n _defined = true\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"label.js","names":[],"sources":["../src/label/index.ts"],"sourcesContent":["/**\n * `<aihu-label>` — headless label (the Radix Label parity primitive). Wires\n * the label↔control association and forwards interactions the way a native\n * `<label>` does, for targets that native labels cannot reference (custom\n * hosts, role=checkbox/switch/radio elements). Ships NO CSS.\n *\n * Behavior:\n * - On connect: ensures a stable `id`, stamps `data-fc-label` on itself so an\n * ancestor `<aihu-form-control>` discovers it, and (when inside one) asks\n * that ancestor to re-wire so the control gains\n * `aria-labelledby=\"<labelId>\"`.\n * - Target resolution (re-resolved per interaction, never cached): the `for`\n * attribute is looked up via `getElementById` in the label's root; with no\n * `for`, a `formControlContext` ancestor's `controlId` is used.\n * - Standalone (no form-control ancestor): sets `aria-labelledby` = own id on\n * the resolved target reactively. Skipped when the target lives in a\n * different root — ARIA IDREFs cannot cross shadow boundaries (see\n * accessibility.md; use `aria-label` on the target for cross-root cases).\n * - Click forwarding (non-native hosts only): double-click `mousedown` is\n * prevented (no accidental text selection); `click` focuses native text\n * controls, forwards `click()` to native checkbox/radio or custom\n * checkbox/switch/radio hosts, and does nothing for disabled targets or\n * clicks originating on nested interactive children.\n * - If the host IS a native `<label>` (tagName `LABEL`), click forwarding is\n * native — only the context wiring above applies.\n *\n * Reflected attributes: `for` (optional explicit target id).\n */\n\nimport { effect, type Read, signal } from '@aihu/signals'\nimport { composedClosest, composedContains, composedParent } from '../composed-tree.ts'\nimport { injectValue } from '../dom-context.ts'\nimport {\n AihuFormControl,\n type FormControlContextValue,\n formControlContext,\n} from '../form-control/index.ts'\n\nlet _idCounter = 0\nfunction nextId(): string {\n _idCounter += 1\n return `aihu-label-${_idCounter}`\n}\n\nexport class AihuLabel extends HTMLElement {\n static readonly observedAttributes = ['for']\n\n private readonly _for = signal<string | null>(null)\n private _fc: FormControlContextValue | null = null\n private _disposers: Array<() => void> = []\n\n /** The explicit target id (`for` attribute) as a signal; null when unset. */\n get forId(): Read<string | null> {\n return this._for[0]\n }\n\n private get _isNativeLabel(): boolean {\n return this.tagName === 'LABEL'\n }\n\n connectedCallback(): void {\n this._for[1](this.getAttribute('for'))\n if (!this.id) this.id = nextId()\n // Make this label discoverable by an ancestor <aihu-form-control>.\n this.setAttribute('data-fc-label', '')\n\n try {\n this._fc = injectValue(this, formControlContext)\n } catch {\n this._fc = null\n }\n\n if (this._fc) {\n // The form-control connected (and wired) before this label existed in\n // its eyes — ask it to re-derive associations so the control gains\n // aria-labelledby pointing at this label. Composed-tree ancestor walk\n // (crosses shadow boundaries via `composedParent`'s ShadowRoot -> .host\n // hop) — a plain `.parentElement` loop stops dead at a shadow root.\n let node: Node | null = composedParent(this)\n while (node !== null) {\n if (node instanceof AihuFormControl) {\n node.recomputeDescribedBy()\n break\n }\n node = composedParent(node)\n }\n } else if (!this._isNativeLabel) {\n // Standalone: stamp aria-labelledby on the resolved target reactively.\n // Skip cross-root targets — IDREFs cannot cross shadow boundaries.\n this._disposers.push(\n effect(() => {\n this._for[0]() // track `for` changes\n const target = this._resolveTarget()\n if (target && target.getRootNode() === this.getRootNode()) {\n target.setAttribute('aria-labelledby', this.id)\n }\n }),\n )\n }\n\n if (!this._isNativeLabel) {\n this.addEventListener('mousedown', this._onMousedown)\n this.addEventListener('click', this._onClick)\n }\n }\n\n disconnectedCallback(): void {\n this.removeEventListener('mousedown', this._onMousedown)\n this.removeEventListener('click', this._onClick)\n for (const d of this._disposers) d()\n this._disposers = []\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (name === 'for') this._for[1](value)\n }\n\n /** Resolve the labelled target NOW (per interaction — never cached). */\n private _resolveTarget(): HTMLElement | null {\n const root = this.getRootNode() as Document | ShadowRoot\n const forId = this._for[0]()\n if (forId) return (root.getElementById(forId) as HTMLElement | null) ?? null\n if (this._fc) {\n const id = this._fc.controlId()\n if (id) return (root.getElementById(id) as HTMLElement | null) ?? null\n }\n return null\n }\n\n private readonly _onMousedown = (ev: MouseEvent): void => {\n // Prevent text selection on double-click (native label parity).\n if (ev.detail > 1) ev.preventDefault()\n }\n\n private readonly _onClick = (ev: MouseEvent): void => {\n const target = this._resolveTarget()\n if (!target) return\n\n // `composedPath()[0]` is the true originating target, pre-retargeting —\n // native `ev.target` is already retargeted to the outermost host visible\n // from this label's own root, which would hide a nested interactive child\n // living inside a further-nested shadow root.\n const path = ev.composedPath()\n const origin =\n path[0] instanceof Element ? path[0] : ev.target instanceof Element ? ev.target : null\n // Clicks originating on a nested interactive child (that is not the\n // labelled target) are the child's business — don't forward. Composed-\n // tree `closest`/`contains` so this still holds across shadow boundaries.\n const interactive = origin ? composedClosest(origin, 'button,input,select,textarea,a') : null\n if (interactive !== null && interactive !== target) return\n // Clicks already on/inside the target need no forwarding (and forwarding\n // a click back to the target would recurse).\n if (origin !== null && (origin === target || composedContains(target, origin))) return\n\n // Disabled targets don't get forwarded interactions.\n if (\n target.getAttribute('aria-disabled') === 'true' ||\n (target as HTMLInputElement).disabled === true\n ) {\n return\n }\n\n const tag = target.tagName\n const inputType = tag === 'INPUT' ? (target as HTMLInputElement).type : null\n if (inputType === 'checkbox' || inputType === 'radio') {\n target.click()\n return\n }\n if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') {\n target.focus()\n return\n }\n const role = target.getAttribute('role')\n if (role === 'checkbox' || role === 'switch' || role === 'radio') {\n target.click()\n }\n }\n}\n\nlet _defined = false\n/** Register `<aihu-label>` (idempotent). */\nexport function defineLabel(tag = 'aihu-label'): void {\n if (_defined || customElements.get(tag)) {\n _defined = true\n return\n }\n customElements.define(tag, AihuLabel)\n _defined = true\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { createDomContext, injectValue, provideContext } from "./dom-context.js";
2
- import { i as attachHiddenInput, r as formControlContext } from "./form-control-B_BO9j7_.js";
2
+ import { i as attachHiddenInput, r as formControlContext } from "./form-control-CRF3PWcK.js";
3
3
  import { collectionContext } from "./collection.js";
4
4
  import { AihuRovingFocus } from "./roving-focus.js";
5
5
  import { effect, signal, untrack } from "@aihu/signals";
package/dist/switch.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createDomContext, injectValue, provideContext } from "./dom-context.js";
2
- import { i as attachHiddenInput, r as formControlContext } from "./form-control-B_BO9j7_.js";
2
+ import { i as attachHiddenInput, r as formControlContext } from "./form-control-CRF3PWcK.js";
3
3
  import { effect, signal } from "@aihu/signals";
4
4
  //#region src/switch/index.ts
5
5
  /**
@@ -1,5 +1,5 @@
1
1
  import { injectValue } from "./dom-context.js";
2
- import { r as formControlContext } from "./form-control-B_BO9j7_.js";
2
+ import { r as formControlContext } from "./form-control-CRF3PWcK.js";
3
3
  import { effect, signal } from "@aihu/signals";
4
4
  //#region src/input/text-control.ts
5
5
  /**
@@ -178,4 +178,4 @@ var AihuTextControlBase = class extends HTMLElement {
178
178
  //#endregion
179
179
  export { TEXT_CONTROL_OBSERVED as n, AihuTextControlBase as t };
180
180
 
181
- //# sourceMappingURL=text-control-BBX7s8Oe.js.map
181
+ //# sourceMappingURL=text-control-CUbTuk5L.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"text-control-BBX7s8Oe.js","names":[],"sources":["../src/input/text-control.ts"],"sourcesContent":["/**\n * `AihuTextControlBase` — shared base for `<aihu-input>` / `<aihu-textarea>`\n * (the native-handoff text controls). The host wraps a REAL light-DOM native\n * element (`<input>` / `<textarea>`) and delegates editing, focus, and form\n * participation to it — the Dialog-wraps-native principle. Because the native\n * element lives in the light DOM, `closest('form')` association is free and\n * NO hidden input is needed. Ships zero CSS.\n *\n * Behavior:\n * - On connect, the first light-DOM child matching the native tag is adopted;\n * one is created and appended when absent. The native element is the source\n * of truth for the text value.\n * - `value` is exposed as a read signal; `setValue()` writes the native value\n * + signal and reflects the host `value` attribute (the dialog open-attr\n * reflection pattern). Native `input` events sync the signal and dispatch a\n * `value-change` CustomEvent (detail `{ value }`, bubbles, composed) from\n * the HOST. Typing does NOT rewrite the host attribute (native parity — the\n * attribute mirrors programmatic state, not keystrokes).\n * - `default-value` seeds the native value ONCE on first connect, only when\n * the native value is empty. Never reflected.\n * - Per-subclass `FORWARDED` host attributes are copied onto the native child\n * when present on the host (and kept in sync on change). Attributes the\n * consumer pre-set on a pre-supplied native child are NOT clobbered unless\n * the host attribute is present.\n * - Ownership rule: `<aihu-form-control>` owns `aria-*` on the control (its\n * selector finds the inner native element); Input/Textarea own the NATIVE\n * PROPS — merged disabled/required (own attribute ∥ inherited\n * formControlContext) are written as `native.disabled` / `native.required`,\n * never as `aria-*`.\n * - `data-state` on the host reflects `'disabled' | 'readonly' | 'idle'`.\n */\n\nimport { effect, type Read, signal } from '@aihu/signals'\nimport { injectValue } from '../dom-context.ts'\nimport { type FormControlContextValue, formControlContext } from '../form-control/index.ts'\n\n/** Labelling ARIA forwarded host → native control (and stripped from host). */\nconst ARIA_LABELLING = ['aria-label', 'aria-labelledby', 'aria-describedby'] as const\n\n/** Host attributes every text control observes (subclasses append FORWARDED). */\nexport const TEXT_CONTROL_OBSERVED: readonly string[] = [\n 'value',\n 'default-value',\n 'disabled',\n 'required',\n]\n\nexport abstract class AihuTextControlBase extends HTMLElement {\n /** Host attributes forwarded to the native child — supplied per subclass. */\n protected static readonly FORWARDED: readonly string[] = []\n\n /** The native tag this control wraps. */\n protected abstract readonly nativeTag: 'input' | 'textarea'\n\n private readonly _value = signal('')\n private readonly _disabled = signal(false)\n private readonly _required = signal(false)\n private readonly _readonly = signal(false)\n private _fc: FormControlContextValue | null = null\n private _disposers: Array<() => void> = []\n private _native: HTMLInputElement | HTMLTextAreaElement | null = null\n private _connected = false\n private _defaultSeeded = false\n\n /** The current text value as a read signal. */\n get value(): Read<string> {\n return this._value[0]\n }\n\n /** The wrapped native element (null before first connect). */\n get nativeControl(): HTMLInputElement | HTMLTextAreaElement | null {\n return this._native\n }\n\n /** Programmatic write: native value + signal + reflected `value` attribute. */\n setValue(next: string): void {\n if (this._native) this._native.value = next\n this._value[1](next)\n // Reflect (dialog open-attr pattern). attributeChangedCallback sees an\n // already-equal signal and no-ops, so this cannot loop.\n this.setAttribute('value', next)\n }\n\n /** Focus delegates to the native child (the real interactive element). */\n override focus(options?: FocusOptions): void {\n this._native?.focus(options)\n }\n\n connectedCallback(): void {\n this._connected = true\n const native = this._findOrCreateNative()\n this._native = native\n\n // Forward host attributes that are PRESENT. A pre-supplied native child's\n // own attributes are respected when the host attribute is absent.\n for (const attr of this._forwarded()) {\n const v = this.getAttribute(attr)\n if (v !== null) native.setAttribute(attr, v)\n }\n\n // Labelling ARIA belongs on the NATIVE control: a roleless host carrying\n // `aria-label` is itself prohibited (axe `aria-prohibited-attr`), and the\n // name would never reach the real form element (axe `label`). Move\n // aria-label / aria-labelledby / aria-describedby host → native and strip\n // from the host so the accessible name lands where the control lives.\n for (const attr of ARIA_LABELLING) {\n const v = this.getAttribute(attr)\n if (v !== null) {\n native.setAttribute(attr, v)\n this.removeAttribute(attr)\n }\n }\n\n // Value precedence: host `value` attribute wins; otherwise `default-value`\n // seeds ONCE, and only when the native value is empty.\n const valueAttr = this.getAttribute('value')\n if (valueAttr !== null) {\n native.value = valueAttr\n } else if (!this._defaultSeeded) {\n const dv = this.getAttribute('default-value')\n if (dv !== null && native.value === '') native.value = dv\n }\n this._defaultSeeded = true\n this._value[1](native.value)\n\n this._syncFromAttrs()\n\n // Inherit disabled/required from a FormControlContext ancestor, if any.\n try {\n this._fc = injectValue(this, formControlContext)\n } catch {\n this._fc = null\n }\n\n native.addEventListener('input', this._onNativeInput)\n\n this._disposers.push(\n effect(() => {\n const disabled = this._effectiveDisabled()\n // Ownership: form-control owns aria-* on the control; Input/Textarea\n // own the native props (real semantics + native form behavior).\n native.disabled = disabled\n native.required = this._effectiveRequired()\n this.setAttribute(\n 'data-state',\n disabled ? 'disabled' : this._readonly[0]() ? 'readonly' : 'idle',\n )\n }),\n )\n }\n\n disconnectedCallback(): void {\n this._connected = false\n this._native?.removeEventListener('input', this._onNativeInput)\n for (const d of this._disposers) d()\n this._disposers = []\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n switch (name) {\n case 'disabled':\n this._disabled[1](value !== null)\n return\n case 'required':\n this._required[1](value !== null)\n return\n case 'default-value':\n // Read once on connect — never reactive, never reflected.\n return\n case 'value':\n // Before connect, connectedCallback derives the value from attributes.\n if (!this._connected) return\n if (value !== null && value !== this._value[0]()) {\n if (this._native) this._native.value = value\n this._value[1](value)\n }\n return\n }\n // Forwarded attributes (includes `readonly`, which also drives data-state).\n if (name === 'readonly') this._readonly[1](value !== null)\n if (!this._connected || !this._native) return\n if (this._forwarded().includes(name)) {\n if (value !== null) this._native.setAttribute(name, value)\n else this._native.removeAttribute(name)\n }\n }\n\n private _forwarded(): readonly string[] {\n return (this.constructor as typeof AihuTextControlBase).FORWARDED\n }\n\n private _effectiveDisabled(): boolean {\n if (this._disabled[0]()) return true\n return this._fc ? this._fc.disabled() : false\n }\n\n private _effectiveRequired(): boolean {\n if (this._required[0]()) return true\n return this._fc ? this._fc.required() : false\n }\n\n private _syncFromAttrs(): void {\n this._disabled[1](this.hasAttribute('disabled'))\n this._required[1](this.hasAttribute('required'))\n this._readonly[1](this.hasAttribute('readonly'))\n }\n\n private _findOrCreateNative(): HTMLInputElement | HTMLTextAreaElement {\n const tag = this.nativeTag.toUpperCase()\n for (const child of Array.from(this.children)) {\n if (child.tagName === tag) return child as HTMLInputElement | HTMLTextAreaElement\n }\n const created = document.createElement(this.nativeTag)\n this.appendChild(created)\n return created\n }\n\n private readonly _onNativeInput = (): void => {\n const native = this._native\n if (!native) return\n this._value[1](native.value)\n this.dispatchEvent(\n new CustomEvent<{ value: string }>('value-change', {\n detail: { value: native.value },\n bubbles: true,\n composed: true,\n }),\n )\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"text-control-CUbTuk5L.js","names":[],"sources":["../src/input/text-control.ts"],"sourcesContent":["/**\n * `AihuTextControlBase` — shared base for `<aihu-input>` / `<aihu-textarea>`\n * (the native-handoff text controls). The host wraps a REAL light-DOM native\n * element (`<input>` / `<textarea>`) and delegates editing, focus, and form\n * participation to it — the Dialog-wraps-native principle. Because the native\n * element lives in the light DOM, `closest('form')` association is free and\n * NO hidden input is needed. Ships zero CSS.\n *\n * Behavior:\n * - On connect, the first light-DOM child matching the native tag is adopted;\n * one is created and appended when absent. The native element is the source\n * of truth for the text value.\n * - `value` is exposed as a read signal; `setValue()` writes the native value\n * + signal and reflects the host `value` attribute (the dialog open-attr\n * reflection pattern). Native `input` events sync the signal and dispatch a\n * `value-change` CustomEvent (detail `{ value }`, bubbles, composed) from\n * the HOST. Typing does NOT rewrite the host attribute (native parity — the\n * attribute mirrors programmatic state, not keystrokes).\n * - `default-value` seeds the native value ONCE on first connect, only when\n * the native value is empty. Never reflected.\n * - Per-subclass `FORWARDED` host attributes are copied onto the native child\n * when present on the host (and kept in sync on change). Attributes the\n * consumer pre-set on a pre-supplied native child are NOT clobbered unless\n * the host attribute is present.\n * - Ownership rule: `<aihu-form-control>` owns `aria-*` on the control (its\n * selector finds the inner native element); Input/Textarea own the NATIVE\n * PROPS — merged disabled/required (own attribute ∥ inherited\n * formControlContext) are written as `native.disabled` / `native.required`,\n * never as `aria-*`.\n * - `data-state` on the host reflects `'disabled' | 'readonly' | 'idle'`.\n */\n\nimport { effect, type Read, signal } from '@aihu/signals'\nimport { injectValue } from '../dom-context.ts'\nimport { type FormControlContextValue, formControlContext } from '../form-control/index.ts'\n\n/** Labelling ARIA forwarded host → native control (and stripped from host). */\nconst ARIA_LABELLING = ['aria-label', 'aria-labelledby', 'aria-describedby'] as const\n\n/** Host attributes every text control observes (subclasses append FORWARDED). */\nexport const TEXT_CONTROL_OBSERVED: readonly string[] = [\n 'value',\n 'default-value',\n 'disabled',\n 'required',\n]\n\nexport abstract class AihuTextControlBase extends HTMLElement {\n /** Host attributes forwarded to the native child — supplied per subclass. */\n protected static readonly FORWARDED: readonly string[] = []\n\n /** The native tag this control wraps. */\n protected abstract readonly nativeTag: 'input' | 'textarea'\n\n private readonly _value = signal('')\n private readonly _disabled = signal(false)\n private readonly _required = signal(false)\n private readonly _readonly = signal(false)\n private _fc: FormControlContextValue | null = null\n private _disposers: Array<() => void> = []\n private _native: HTMLInputElement | HTMLTextAreaElement | null = null\n private _connected = false\n private _defaultSeeded = false\n\n /** The current text value as a read signal. */\n get value(): Read<string> {\n return this._value[0]\n }\n\n /** The wrapped native element (null before first connect). */\n get nativeControl(): HTMLInputElement | HTMLTextAreaElement | null {\n return this._native\n }\n\n /** Programmatic write: native value + signal + reflected `value` attribute. */\n setValue(next: string): void {\n if (this._native) this._native.value = next\n this._value[1](next)\n // Reflect (dialog open-attr pattern). attributeChangedCallback sees an\n // already-equal signal and no-ops, so this cannot loop.\n this.setAttribute('value', next)\n }\n\n /** Focus delegates to the native child (the real interactive element). */\n override focus(options?: FocusOptions): void {\n this._native?.focus(options)\n }\n\n connectedCallback(): void {\n this._connected = true\n const native = this._findOrCreateNative()\n this._native = native\n\n // Forward host attributes that are PRESENT. A pre-supplied native child's\n // own attributes are respected when the host attribute is absent.\n for (const attr of this._forwarded()) {\n const v = this.getAttribute(attr)\n if (v !== null) native.setAttribute(attr, v)\n }\n\n // Labelling ARIA belongs on the NATIVE control: a roleless host carrying\n // `aria-label` is itself prohibited (axe `aria-prohibited-attr`), and the\n // name would never reach the real form element (axe `label`). Move\n // aria-label / aria-labelledby / aria-describedby host → native and strip\n // from the host so the accessible name lands where the control lives.\n for (const attr of ARIA_LABELLING) {\n const v = this.getAttribute(attr)\n if (v !== null) {\n native.setAttribute(attr, v)\n this.removeAttribute(attr)\n }\n }\n\n // Value precedence: host `value` attribute wins; otherwise `default-value`\n // seeds ONCE, and only when the native value is empty.\n const valueAttr = this.getAttribute('value')\n if (valueAttr !== null) {\n native.value = valueAttr\n } else if (!this._defaultSeeded) {\n const dv = this.getAttribute('default-value')\n if (dv !== null && native.value === '') native.value = dv\n }\n this._defaultSeeded = true\n this._value[1](native.value)\n\n this._syncFromAttrs()\n\n // Inherit disabled/required from a FormControlContext ancestor, if any.\n try {\n this._fc = injectValue(this, formControlContext)\n } catch {\n this._fc = null\n }\n\n native.addEventListener('input', this._onNativeInput)\n\n this._disposers.push(\n effect(() => {\n const disabled = this._effectiveDisabled()\n // Ownership: form-control owns aria-* on the control; Input/Textarea\n // own the native props (real semantics + native form behavior).\n native.disabled = disabled\n native.required = this._effectiveRequired()\n this.setAttribute(\n 'data-state',\n disabled ? 'disabled' : this._readonly[0]() ? 'readonly' : 'idle',\n )\n }),\n )\n }\n\n disconnectedCallback(): void {\n this._connected = false\n this._native?.removeEventListener('input', this._onNativeInput)\n for (const d of this._disposers) d()\n this._disposers = []\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n switch (name) {\n case 'disabled':\n this._disabled[1](value !== null)\n return\n case 'required':\n this._required[1](value !== null)\n return\n case 'default-value':\n // Read once on connect — never reactive, never reflected.\n return\n case 'value':\n // Before connect, connectedCallback derives the value from attributes.\n if (!this._connected) return\n if (value !== null && value !== this._value[0]()) {\n if (this._native) this._native.value = value\n this._value[1](value)\n }\n return\n }\n // Forwarded attributes (includes `readonly`, which also drives data-state).\n if (name === 'readonly') this._readonly[1](value !== null)\n if (!this._connected || !this._native) return\n if (this._forwarded().includes(name)) {\n if (value !== null) this._native.setAttribute(name, value)\n else this._native.removeAttribute(name)\n }\n }\n\n private _forwarded(): readonly string[] {\n return (this.constructor as typeof AihuTextControlBase).FORWARDED\n }\n\n private _effectiveDisabled(): boolean {\n if (this._disabled[0]()) return true\n return this._fc ? this._fc.disabled() : false\n }\n\n private _effectiveRequired(): boolean {\n if (this._required[0]()) return true\n return this._fc ? this._fc.required() : false\n }\n\n private _syncFromAttrs(): void {\n this._disabled[1](this.hasAttribute('disabled'))\n this._required[1](this.hasAttribute('required'))\n this._readonly[1](this.hasAttribute('readonly'))\n }\n\n private _findOrCreateNative(): HTMLInputElement | HTMLTextAreaElement {\n const tag = this.nativeTag.toUpperCase()\n for (const child of Array.from(this.children)) {\n if (child.tagName === tag) return child as HTMLInputElement | HTMLTextAreaElement\n }\n const created = document.createElement(this.nativeTag)\n this.appendChild(created)\n return created\n }\n\n private readonly _onNativeInput = (): void => {\n const native = this._native\n if (!native) return\n this._value[1](native.value)\n this.dispatchEvent(\n new CustomEvent<{ value: string }>('value-change', {\n detail: { value: native.value },\n bubbles: true,\n composed: true,\n }),\n )\n }\n}\n"],"mappings":"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"}
@@ -35,4 +35,4 @@ declare abstract class AihuTextControlBase extends HTMLElement {
35
35
  }
36
36
  //#endregion
37
37
  export { AihuTextControlBase as t };
38
- //# sourceMappingURL=text-control-Brv5fUEX.d.ts.map
38
+ //# sourceMappingURL=text-control-wKoKU_--.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"text-control-Brv5fUEX.d.ts","names":[],"sources":["../src/input/text-control.ts"],"mappings":";;;uBA+CsB,mBAAA,SAA4B,WAAA;EAyChD;EAAA,0BAvC0B,SAAA;EA6G1B;EAAA,4BA1G4B,SAAA;EAAA,iBAEX,MAAA;EAAA,iBACA,SAAA;EAAA,iBACA,SAAA;EAAA,iBACA,SAAA;EAAA,QACT,GAAA;EAAA,QACA,UAAA;EAAA,QACA,OAAA;EAAA,QACA,UAAA;EAAA,QACA,cAAA;EA2JuB;EAAA,IAxJ3B,KAAA,CAAA,GAAS,IAAA;;MAKT,aAAA,CAAA,GAAiB,gBAAA,GAAmB,mBAAA;;EAKxC,QAAA,CAAS,IAAA;;EASA,KAAA,CAAM,OAAA,GAAU,YAAA;EAIzB,iBAAA,CAAA;EA+DA,oBAAA,CAAA;EAOA,wBAAA,CAAyB,IAAA,UAAc,IAAA,iBAAqB,KAAA;EAAA,QA6BpD,UAAA;EAAA,QAIA,kBAAA;EAAA,QAKA,kBAAA;EAAA,QAKA,cAAA;EAAA,QAMA,mBAAA;EAAA,iBAUS,cAAA;AAAA"}
1
+ {"version":3,"file":"text-control-wKoKU_--.d.ts","names":[],"sources":["../src/input/text-control.ts"],"mappings":";;;uBA+CsB,mBAAA,SAA4B,WAAA;EAyChD;EAAA,0BAvC0B,SAAA;EA6G1B;EAAA,4BA1G4B,SAAA;EAAA,iBAEX,MAAA;EAAA,iBACA,SAAA;EAAA,iBACA,SAAA;EAAA,iBACA,SAAA;EAAA,QACT,GAAA;EAAA,QACA,UAAA;EAAA,QACA,OAAA;EAAA,QACA,UAAA;EAAA,QACA,cAAA;EA2JuB;EAAA,IAxJ3B,KAAA,CAAA,GAAS,IAAA;;MAKT,aAAA,CAAA,GAAiB,gBAAA,GAAmB,mBAAA;;EAKxC,QAAA,CAAS,IAAA;;EASA,KAAA,CAAM,OAAA,GAAU,YAAA;EAIzB,iBAAA,CAAA;EA+DA,oBAAA,CAAA;EAOA,wBAAA,CAAyB,IAAA,UAAc,IAAA,iBAAqB,KAAA;EAAA,QA6BpD,UAAA;EAAA,QAIA,kBAAA;EAAA,QAKA,kBAAA;EAAA,QAKA,cAAA;EAAA,QAMA,mBAAA;EAAA,iBAUS,cAAA;AAAA"}
@@ -1,4 +1,4 @@
1
- import { t as AihuTextControlBase } from "./text-control-Brv5fUEX.js";
1
+ import { t as AihuTextControlBase } from "./text-control-wKoKU_--.js";
2
2
 
3
3
  //#region src/textarea/index.d.ts
4
4
  declare class AihuTextarea extends AihuTextControlBase {
package/dist/textarea.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as TEXT_CONTROL_OBSERVED, t as AihuTextControlBase } from "./text-control-BBX7s8Oe.js";
1
+ import { n as TEXT_CONTROL_OBSERVED, t as AihuTextControlBase } from "./text-control-CUbTuk5L.js";
2
2
  //#region src/textarea/index.ts
3
3
  /**
4
4
  * `<aihu-textarea>` — headless multi-line text control. Wraps (or creates) a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aihu/primitives",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -83,8 +83,8 @@
83
83
  ],
84
84
  "sideEffects": false,
85
85
  "dependencies": {
86
- "@aihu/signals": "0.3.0",
87
- "@aihu/arbor": "2.0.0",
86
+ "@aihu/signals": "0.5.0",
87
+ "@aihu/arbor": "4.0.0",
88
88
  "@aihu/css-engine": "0.4.6"
89
89
  },
90
90
  "scripts": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"dialog-VDL-W3Vy.js","names":[],"sources":["../src/dialog/focus-trap.ts","../src/dialog/index.ts"],"sourcesContent":["/**\n * `createFocusTrap` — a tiny native-DOM focus trap (no library). Queries the\n * focusable descendants of a container, wraps Tab at the edges, moves focus to\n * the first focusable (or the container) on activate, and restores focus to the\n * previously-active element on deactivate. Used by `dialog-content`.\n */\n\nconst FOCUSABLE =\n 'a[href], area[href], button:not([disabled]), input:not([disabled]):not([type=\"hidden\"]), ' +\n 'select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"]), ' +\n '[contenteditable=\"true\"], audio[controls], video[controls], details>summary:first-of-type'\n\nexport interface FocusTrap {\n activate(): void\n deactivate(): void\n}\n\nfunction focusables(container: Element): HTMLElement[] {\n return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\n (el) => el.offsetParent !== null || el === document.activeElement || isInJsdom(el),\n )\n}\n\n/** jsdom does not lay out, so `offsetParent` is always null — treat all as visible there. */\nfunction isInJsdom(_el: HTMLElement): boolean {\n return typeof navigator !== 'undefined' && navigator.userAgent.includes('jsdom')\n}\n\nexport function createFocusTrap(container: Element): FocusTrap {\n let previouslyFocused: HTMLElement | null = null\n let active = false\n\n const onKeydown = (ev: KeyboardEvent): void => {\n if (!active || ev.key !== 'Tab') return\n const items = focusables(container)\n const first = items[0]\n const last = items[items.length - 1]\n if (!first || !last) {\n ev.preventDefault()\n return\n }\n const current = (container.getRootNode() as Document | ShadowRoot).activeElement as HTMLElement\n\n if (ev.shiftKey && (current === first || !container.contains(current))) {\n ev.preventDefault()\n last.focus()\n } else if (!ev.shiftKey && (current === last || !container.contains(current))) {\n ev.preventDefault()\n first.focus()\n }\n }\n\n return {\n activate(): void {\n if (active) return\n active = true\n previouslyFocused = document.activeElement as HTMLElement | null\n const items = focusables(container)\n const target = items[0] ?? (container as HTMLElement)\n // Ensure the container itself is focusable as a fallback.\n if (!items.length && !(container as HTMLElement).hasAttribute('tabindex')) {\n ;(container as HTMLElement).setAttribute('tabindex', '-1')\n }\n target.focus()\n document.addEventListener('keydown', onKeydown, true)\n },\n deactivate(): void {\n if (!active) return\n active = false\n document.removeEventListener('keydown', onKeydown, true)\n // Return focus to whatever opened the trap.\n previouslyFocused?.focus?.()\n previouslyFocused = null\n },\n }\n}\n","/**\n * Headless dialog — `<aihu-dialog-root>` (state owner) + pieces\n * `<aihu-dialog-trigger>`, `<aihu-dialog-content>`, `<aihu-dialog-backdrop>`,\n * `<aihu-dialog-close>`, `<aihu-dialog-title>`, `<aihu-dialog-description>`.\n * Implements the WAI-ARIA APG **Dialog (Modal)** pattern: focus-trap +\n * return-focus, Escape-to-close, `role=dialog` / `aria-modal` /\n * `aria-labelledby` / `aria-describedby`, trigger `aria-haspopup` /\n * `aria-expanded` / `aria-controls`. Emits NO CSS — every piece reflects\n * `data-state=\"open\"|\"closed\"` for the consumer to style.\n */\n\nimport { effect, type Read, signal } from '@aihu/signals'\nimport { createDomContext, injectValue, provideContext } from '../dom-context.ts'\nimport { createFocusTrap, type FocusTrap } from './focus-trap.ts'\n\nexport interface DialogContextValue {\n readonly open: Read<boolean>\n readonly modal: Read<boolean>\n readonly contentId: Read<string>\n readonly titleId: Read<string | null>\n readonly descriptionId: Read<string | null>\n setTitleId(id: string): void\n setDescriptionId(id: string): void\n setOpen(next: boolean): void\n close(): void\n toggle(): void\n}\n\nexport const dialogContext = createDomContext<DialogContextValue>('dialog')\n\nlet _idSeq = 0\nconst uid = (p: string): string => `aihu-${p}-${(_idSeq += 1)}`\n\nexport class AihuDialogRoot extends HTMLElement {\n static readonly observedAttributes = ['open', 'modal']\n\n private readonly _open = signal(false)\n private readonly _modal = signal(true)\n private readonly _contentId = signal(uid('dialog'))\n private readonly _titleId = signal<string | null>(null)\n private readonly _descriptionId = signal<string | null>(null)\n private _disposers: Array<() => void> = []\n private _ctx: DialogContextValue\n\n constructor() {\n super()\n this._ctx = {\n open: this._open[0],\n modal: this._modal[0],\n contentId: this._contentId[0],\n titleId: this._titleId[0],\n descriptionId: this._descriptionId[0],\n setTitleId: (id) => this._titleId[1](id),\n setDescriptionId: (id) => this._descriptionId[1](id),\n setOpen: (next) => this.setOpen(next),\n close: () => this.setOpen(false),\n toggle: () => this.setOpen(!this._open[0]()),\n }\n provideContext(this, dialogContext, this._ctx)\n }\n\n get open(): Read<boolean> {\n return this._open[0]\n }\n\n setOpen(next: boolean): void {\n if (next === this._open[0]()) return\n this._open[1](next)\n if (next) this.setAttribute('open', '')\n else this.removeAttribute('open')\n }\n\n connectedCallback(): void {\n if (this.hasAttribute('modal')) this._modal[1](this.getAttribute('modal') !== 'false')\n this._open[1](this.hasAttribute('open'))\n this._disposers.push(\n effect(() => {\n this.setAttribute('data-state', this._open[0]() ? 'open' : 'closed')\n }),\n )\n }\n\n disconnectedCallback(): void {\n for (const d of this._disposers) d()\n this._disposers = []\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (name === 'open') this._open[1](value !== null)\n if (name === 'modal') this._modal[1](value !== 'false')\n }\n}\n\n/** Base for pieces that inject the dialog context lazily on connect. */\nabstract class DialogPiece extends HTMLElement {\n protected ctx!: DialogContextValue\n protected disposers: Array<() => void> = []\n\n connectedCallback(): void {\n this.ctx = injectValue(this, dialogContext)\n this.onConnect()\n }\n\n disconnectedCallback(): void {\n for (const d of this.disposers) d()\n this.disposers = []\n }\n\n protected reflectState(): void {\n this.disposers.push(\n effect(() => {\n this.setAttribute('data-state', this.ctx.open() ? 'open' : 'closed')\n }),\n )\n }\n\n protected abstract onConnect(): void\n}\n\nexport class AihuDialogTrigger extends DialogPiece {\n protected onConnect(): void {\n if (this.tagName !== 'BUTTON') {\n if (!this.hasAttribute('role')) this.setAttribute('role', 'button')\n if (!this.hasAttribute('tabindex')) this.setAttribute('tabindex', '0')\n }\n this.setAttribute('aria-haspopup', 'dialog')\n this.addEventListener('click', this._onClick)\n this.disposers.push(\n effect(() => {\n const open = this.ctx.open()\n this.setAttribute('aria-expanded', String(open))\n this.setAttribute('aria-controls', this.ctx.contentId())\n }),\n )\n this.reflectState()\n }\n\n private readonly _onClick = (): void => {\n this.ctx.toggle()\n }\n}\n\nexport class AihuDialogContent extends DialogPiece {\n private _trap: FocusTrap | null = null\n\n protected onConnect(): void {\n this.setAttribute('role', this.getAttribute('role') ?? 'dialog')\n this.addEventListener('keydown', this._onKeydown)\n this.disposers.push(\n effect(() => {\n const open = this.ctx.open()\n this.id = this.ctx.contentId()\n if (this.ctx.modal()) this.setAttribute('aria-modal', 'true')\n const titleId = this.ctx.titleId()\n if (titleId) this.setAttribute('aria-labelledby', titleId)\n const descId = this.ctx.descriptionId()\n if (descId) this.setAttribute('aria-describedby', descId)\n this.setAttribute('data-state', open ? 'open' : 'closed')\n if (open) this._activateTrap()\n else this._deactivateTrap()\n }),\n )\n }\n\n override disconnectedCallback(): void {\n this._deactivateTrap()\n this.removeEventListener('keydown', this._onKeydown)\n super.disconnectedCallback()\n }\n\n private _activateTrap(): void {\n if (this._trap) return\n this._trap = createFocusTrap(this)\n this._trap.activate()\n }\n\n private _deactivateTrap(): void {\n this._trap?.deactivate()\n this._trap = null\n }\n\n private readonly _onKeydown = (ev: KeyboardEvent): void => {\n if (ev.key === 'Escape' && this.getAttribute('data-dismissable-escape') !== 'false') {\n ev.stopPropagation()\n this.ctx.close()\n }\n }\n}\n\nexport class AihuDialogBackdrop extends DialogPiece {\n protected onConnect(): void {\n this.addEventListener('click', this._onClick)\n this.reflectState()\n }\n\n private readonly _onClick = (): void => {\n if (this.ctx.modal() && this.getAttribute('data-dismissable-outside') !== 'false') {\n this.ctx.close()\n }\n }\n}\n\nexport class AihuDialogClose extends DialogPiece {\n protected onConnect(): void {\n if (this.tagName !== 'BUTTON') {\n if (!this.hasAttribute('role')) this.setAttribute('role', 'button')\n if (!this.hasAttribute('tabindex')) this.setAttribute('tabindex', '0')\n }\n this.addEventListener('click', this._onClick)\n this.reflectState()\n }\n\n private readonly _onClick = (): void => {\n this.ctx.close()\n }\n}\n\nexport class AihuDialogTitle extends DialogPiece {\n protected onConnect(): void {\n if (!this.id) this.id = uid('dialog-title')\n this.ctx.setTitleId(this.id)\n }\n}\n\nexport class AihuDialogDescription extends DialogPiece {\n protected onConnect(): void {\n if (!this.id) this.id = uid('dialog-desc')\n this.ctx.setDescriptionId(this.id)\n }\n}\n\nconst REGISTRY: Array<[string, CustomElementConstructor]> = [\n ['aihu-dialog-root', AihuDialogRoot],\n ['aihu-dialog-trigger', AihuDialogTrigger],\n ['aihu-dialog-content', AihuDialogContent],\n ['aihu-dialog-backdrop', AihuDialogBackdrop],\n ['aihu-dialog-close', AihuDialogClose],\n ['aihu-dialog-title', AihuDialogTitle],\n ['aihu-dialog-description', AihuDialogDescription],\n]\n\nconst _definedPrefixes = new Set<string>()\n/**\n * Register all dialog custom elements under `<prefix>-dialog-*` (idempotent\n * per prefix). Non-default prefixes register a fresh trivial subclass per\n * piece — a constructor can only be `customElements.define`d once, so the\n * original classes stay reserved for the default tags. Demos/stories use a\n * non-`aihu` prefix so styled recipes own the `aihu-dialog-*` namespace\n * (spec §9.4).\n */\nexport function defineDialog(prefix = 'aihu'): void {\n if (_definedPrefixes.has(prefix)) return\n for (const [tag, ctor] of REGISTRY) {\n const name = prefix === 'aihu' ? tag : tag.replace(/^aihu-/, `${prefix}-`)\n if (!customElements.get(name)) {\n customElements.define(name, prefix === 'aihu' ? ctor : class extends ctor {})\n }\n }\n _definedPrefixes.add(prefix)\n}\n\nexport { createFocusTrap, type FocusTrap } from './focus-trap.ts'\n"],"mappings":"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"}