@aihu/primitives 0.0.12 → 0.1.1
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.
- package/README.md +14 -7
- package/dist/{button-C-8c-A17.js → button-Cha1gKlr.js} +2 -2
- package/dist/{button-C-8c-A17.js.map → button-Cha1gKlr.js.map} +1 -1
- package/dist/button.d.ts +1 -1
- package/dist/button.js +1 -1
- package/dist/checkbox.d.ts +45 -0
- package/dist/checkbox.d.ts.map +1 -0
- package/dist/checkbox.js +190 -0
- package/dist/checkbox.js.map +1 -0
- package/dist/{dialog-y7MHc6vf.js → dialog-VDL-W3Vy.js} +17 -7
- package/dist/{dialog-y7MHc6vf.js.map → dialog-VDL-W3Vy.js.map} +1 -1
- package/dist/dialog.d.ts +1 -1
- package/dist/dialog.js +1 -1
- package/dist/form-control-B_BO9j7_.js +223 -0
- package/dist/form-control-B_BO9j7_.js.map +1 -0
- package/dist/form-control.d.ts +2 -41
- package/dist/form-control.js +2 -145
- package/dist/index-BvFa1Y-Z.d.ts +60 -0
- package/dist/index-BvFa1Y-Z.d.ts.map +1 -0
- package/dist/{index-D9kf9rVU.d.ts → index-D7hJLfnb.d.ts} +10 -3
- package/dist/{index-D9kf9rVU.d.ts.map → index-D7hJLfnb.d.ts.map} +1 -1
- package/dist/{index-DPD4L6Nj.d.ts → index-yPv3StRL.d.ts} +1 -1
- package/dist/{index-DPD4L6Nj.d.ts.map → index-yPv3StRL.d.ts.map} +1 -1
- package/dist/index.d.ts +12 -4
- package/dist/index.js +12 -4
- package/dist/input.d.ts +13 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/input.js +42 -0
- package/dist/input.js.map +1 -0
- package/dist/label.d.ts +24 -0
- package/dist/label.d.ts.map +1 -0
- package/dist/label.js +136 -0
- package/dist/label.js.map +1 -0
- package/dist/radio-group.d.ts +82 -0
- package/dist/radio-group.d.ts.map +1 -0
- package/dist/radio-group.js +298 -0
- package/dist/radio-group.js.map +1 -0
- package/dist/roving-focus.d.ts +4 -2
- package/dist/roving-focus.d.ts.map +1 -1
- package/dist/roving-focus.js +5 -3
- package/dist/roving-focus.js.map +1 -1
- package/dist/separator.d.ts +25 -0
- package/dist/separator.d.ts.map +1 -0
- package/dist/separator.js +85 -0
- package/dist/separator.js.map +1 -0
- package/dist/switch.d.ts +44 -0
- package/dist/switch.d.ts.map +1 -0
- package/dist/switch.js +176 -0
- package/dist/switch.js.map +1 -0
- package/dist/text-control-BBX7s8Oe.js +181 -0
- package/dist/text-control-BBX7s8Oe.js.map +1 -0
- package/dist/text-control-Brv5fUEX.d.ts +38 -0
- package/dist/text-control-Brv5fUEX.d.ts.map +1 -0
- package/dist/textarea.d.ts +13 -0
- package/dist/textarea.d.ts.map +1 -0
- package/dist/textarea.js +38 -0
- package/dist/textarea.js.map +1 -0
- package/dist/tooltip.d.ts +8 -2
- package/dist/tooltip.d.ts.map +1 -1
- package/dist/tooltip.js +15 -6
- package/dist/tooltip.js.map +1 -1
- package/package.json +33 -2
- package/dist/form-control.d.ts.map +0 -1
- package/dist/form-control.js.map +0 -1
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { injectValue } from "./dom-context.js";
|
|
2
|
+
import { r as formControlContext } from "./form-control-B_BO9j7_.js";
|
|
3
|
+
import { effect, signal } from "@aihu/signals";
|
|
4
|
+
//#region src/input/text-control.ts
|
|
5
|
+
/**
|
|
6
|
+
* `AihuTextControlBase` — shared base for `<aihu-input>` / `<aihu-textarea>`
|
|
7
|
+
* (the native-handoff text controls). The host wraps a REAL light-DOM native
|
|
8
|
+
* element (`<input>` / `<textarea>`) and delegates editing, focus, and form
|
|
9
|
+
* participation to it — the Dialog-wraps-native principle. Because the native
|
|
10
|
+
* element lives in the light DOM, `closest('form')` association is free and
|
|
11
|
+
* NO hidden input is needed. Ships zero CSS.
|
|
12
|
+
*
|
|
13
|
+
* Behavior:
|
|
14
|
+
* - On connect, the first light-DOM child matching the native tag is adopted;
|
|
15
|
+
* one is created and appended when absent. The native element is the source
|
|
16
|
+
* of truth for the text value.
|
|
17
|
+
* - `value` is exposed as a read signal; `setValue()` writes the native value
|
|
18
|
+
* + signal and reflects the host `value` attribute (the dialog open-attr
|
|
19
|
+
* reflection pattern). Native `input` events sync the signal and dispatch a
|
|
20
|
+
* `value-change` CustomEvent (detail `{ value }`, bubbles, composed) from
|
|
21
|
+
* the HOST. Typing does NOT rewrite the host attribute (native parity — the
|
|
22
|
+
* attribute mirrors programmatic state, not keystrokes).
|
|
23
|
+
* - `default-value` seeds the native value ONCE on first connect, only when
|
|
24
|
+
* the native value is empty. Never reflected.
|
|
25
|
+
* - Per-subclass `FORWARDED` host attributes are copied onto the native child
|
|
26
|
+
* when present on the host (and kept in sync on change). Attributes the
|
|
27
|
+
* consumer pre-set on a pre-supplied native child are NOT clobbered unless
|
|
28
|
+
* the host attribute is present.
|
|
29
|
+
* - Ownership rule: `<aihu-form-control>` owns `aria-*` on the control (its
|
|
30
|
+
* selector finds the inner native element); Input/Textarea own the NATIVE
|
|
31
|
+
* PROPS — merged disabled/required (own attribute ∥ inherited
|
|
32
|
+
* formControlContext) are written as `native.disabled` / `native.required`,
|
|
33
|
+
* never as `aria-*`.
|
|
34
|
+
* - `data-state` on the host reflects `'disabled' | 'readonly' | 'idle'`.
|
|
35
|
+
*/
|
|
36
|
+
/** Labelling ARIA forwarded host → native control (and stripped from host). */
|
|
37
|
+
const ARIA_LABELLING = [
|
|
38
|
+
"aria-label",
|
|
39
|
+
"aria-labelledby",
|
|
40
|
+
"aria-describedby"
|
|
41
|
+
];
|
|
42
|
+
/** Host attributes every text control observes (subclasses append FORWARDED). */
|
|
43
|
+
const TEXT_CONTROL_OBSERVED = [
|
|
44
|
+
"value",
|
|
45
|
+
"default-value",
|
|
46
|
+
"disabled",
|
|
47
|
+
"required"
|
|
48
|
+
];
|
|
49
|
+
var AihuTextControlBase = class extends HTMLElement {
|
|
50
|
+
/** Host attributes forwarded to the native child — supplied per subclass. */
|
|
51
|
+
static FORWARDED = [];
|
|
52
|
+
_value = signal("");
|
|
53
|
+
_disabled = signal(false);
|
|
54
|
+
_required = signal(false);
|
|
55
|
+
_readonly = signal(false);
|
|
56
|
+
_fc = null;
|
|
57
|
+
_disposers = [];
|
|
58
|
+
_native = null;
|
|
59
|
+
_connected = false;
|
|
60
|
+
_defaultSeeded = false;
|
|
61
|
+
/** The current text value as a read signal. */
|
|
62
|
+
get value() {
|
|
63
|
+
return this._value[0];
|
|
64
|
+
}
|
|
65
|
+
/** The wrapped native element (null before first connect). */
|
|
66
|
+
get nativeControl() {
|
|
67
|
+
return this._native;
|
|
68
|
+
}
|
|
69
|
+
/** Programmatic write: native value + signal + reflected `value` attribute. */
|
|
70
|
+
setValue(next) {
|
|
71
|
+
if (this._native) this._native.value = next;
|
|
72
|
+
this._value[1](next);
|
|
73
|
+
this.setAttribute("value", next);
|
|
74
|
+
}
|
|
75
|
+
/** Focus delegates to the native child (the real interactive element). */
|
|
76
|
+
focus(options) {
|
|
77
|
+
this._native?.focus(options);
|
|
78
|
+
}
|
|
79
|
+
connectedCallback() {
|
|
80
|
+
this._connected = true;
|
|
81
|
+
const native = this._findOrCreateNative();
|
|
82
|
+
this._native = native;
|
|
83
|
+
for (const attr of this._forwarded()) {
|
|
84
|
+
const v = this.getAttribute(attr);
|
|
85
|
+
if (v !== null) native.setAttribute(attr, v);
|
|
86
|
+
}
|
|
87
|
+
for (const attr of ARIA_LABELLING) {
|
|
88
|
+
const v = this.getAttribute(attr);
|
|
89
|
+
if (v !== null) {
|
|
90
|
+
native.setAttribute(attr, v);
|
|
91
|
+
this.removeAttribute(attr);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const valueAttr = this.getAttribute("value");
|
|
95
|
+
if (valueAttr !== null) native.value = valueAttr;
|
|
96
|
+
else if (!this._defaultSeeded) {
|
|
97
|
+
const dv = this.getAttribute("default-value");
|
|
98
|
+
if (dv !== null && native.value === "") native.value = dv;
|
|
99
|
+
}
|
|
100
|
+
this._defaultSeeded = true;
|
|
101
|
+
this._value[1](native.value);
|
|
102
|
+
this._syncFromAttrs();
|
|
103
|
+
try {
|
|
104
|
+
this._fc = injectValue(this, formControlContext);
|
|
105
|
+
} catch {
|
|
106
|
+
this._fc = null;
|
|
107
|
+
}
|
|
108
|
+
native.addEventListener("input", this._onNativeInput);
|
|
109
|
+
this._disposers.push(effect(() => {
|
|
110
|
+
const disabled = this._effectiveDisabled();
|
|
111
|
+
native.disabled = disabled;
|
|
112
|
+
native.required = this._effectiveRequired();
|
|
113
|
+
this.setAttribute("data-state", disabled ? "disabled" : this._readonly[0]() ? "readonly" : "idle");
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
disconnectedCallback() {
|
|
117
|
+
this._connected = false;
|
|
118
|
+
this._native?.removeEventListener("input", this._onNativeInput);
|
|
119
|
+
for (const d of this._disposers) d();
|
|
120
|
+
this._disposers = [];
|
|
121
|
+
}
|
|
122
|
+
attributeChangedCallback(name, _old, value) {
|
|
123
|
+
switch (name) {
|
|
124
|
+
case "disabled":
|
|
125
|
+
this._disabled[1](value !== null);
|
|
126
|
+
return;
|
|
127
|
+
case "required":
|
|
128
|
+
this._required[1](value !== null);
|
|
129
|
+
return;
|
|
130
|
+
case "default-value": return;
|
|
131
|
+
case "value":
|
|
132
|
+
if (!this._connected) return;
|
|
133
|
+
if (value !== null && value !== this._value[0]()) {
|
|
134
|
+
if (this._native) this._native.value = value;
|
|
135
|
+
this._value[1](value);
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (name === "readonly") this._readonly[1](value !== null);
|
|
140
|
+
if (!this._connected || !this._native) return;
|
|
141
|
+
if (this._forwarded().includes(name)) if (value !== null) this._native.setAttribute(name, value);
|
|
142
|
+
else this._native.removeAttribute(name);
|
|
143
|
+
}
|
|
144
|
+
_forwarded() {
|
|
145
|
+
return this.constructor.FORWARDED;
|
|
146
|
+
}
|
|
147
|
+
_effectiveDisabled() {
|
|
148
|
+
if (this._disabled[0]()) return true;
|
|
149
|
+
return this._fc ? this._fc.disabled() : false;
|
|
150
|
+
}
|
|
151
|
+
_effectiveRequired() {
|
|
152
|
+
if (this._required[0]()) return true;
|
|
153
|
+
return this._fc ? this._fc.required() : false;
|
|
154
|
+
}
|
|
155
|
+
_syncFromAttrs() {
|
|
156
|
+
this._disabled[1](this.hasAttribute("disabled"));
|
|
157
|
+
this._required[1](this.hasAttribute("required"));
|
|
158
|
+
this._readonly[1](this.hasAttribute("readonly"));
|
|
159
|
+
}
|
|
160
|
+
_findOrCreateNative() {
|
|
161
|
+
const tag = this.nativeTag.toUpperCase();
|
|
162
|
+
for (const child of Array.from(this.children)) if (child.tagName === tag) return child;
|
|
163
|
+
const created = document.createElement(this.nativeTag);
|
|
164
|
+
this.appendChild(created);
|
|
165
|
+
return created;
|
|
166
|
+
}
|
|
167
|
+
_onNativeInput = () => {
|
|
168
|
+
const native = this._native;
|
|
169
|
+
if (!native) return;
|
|
170
|
+
this._value[1](native.value);
|
|
171
|
+
this.dispatchEvent(new CustomEvent("value-change", {
|
|
172
|
+
detail: { value: native.value },
|
|
173
|
+
bubbles: true,
|
|
174
|
+
composed: true
|
|
175
|
+
}));
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
//#endregion
|
|
179
|
+
export { TEXT_CONTROL_OBSERVED as n, AihuTextControlBase as t };
|
|
180
|
+
|
|
181
|
+
//# sourceMappingURL=text-control-BBX7s8Oe.js.map
|
|
@@ -0,0 +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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,MAAM,iBAAiB;CAAC;CAAc;CAAmB;CAAmB;;AAG5E,MAAa,wBAA2C;CACtD;CACA;CACA;CACA;CACD;AAED,IAAsB,sBAAtB,cAAkD,YAAY;;CAE5D,OAA0B,YAA+B,EAAE;CAK3D,SAA0B,OAAO,GAAG;CACpC,YAA6B,OAAO,MAAM;CAC1C,YAA6B,OAAO,MAAM;CAC1C,YAA6B,OAAO,MAAM;CAC1C,MAA8C;CAC9C,aAAwC,EAAE;CAC1C,UAAiE;CACjE,aAAqB;CACrB,iBAAyB;;CAGzB,IAAI,QAAsB;EACxB,OAAO,KAAK,OAAO;;;CAIrB,IAAI,gBAA+D;EACjE,OAAO,KAAK;;;CAId,SAAS,MAAoB;EAC3B,IAAI,KAAK,SAAS,KAAK,QAAQ,QAAQ;EACvC,KAAK,OAAO,GAAG,KAAK;EAGpB,KAAK,aAAa,SAAS,KAAK;;;CAIlC,MAAe,SAA8B;EAC3C,KAAK,SAAS,MAAM,QAAQ;;CAG9B,oBAA0B;EACxB,KAAK,aAAa;EAClB,MAAM,SAAS,KAAK,qBAAqB;EACzC,KAAK,UAAU;EAIf,KAAK,MAAM,QAAQ,KAAK,YAAY,EAAE;GACpC,MAAM,IAAI,KAAK,aAAa,KAAK;GACjC,IAAI,MAAM,MAAM,OAAO,aAAa,MAAM,EAAE;;EAQ9C,KAAK,MAAM,QAAQ,gBAAgB;GACjC,MAAM,IAAI,KAAK,aAAa,KAAK;GACjC,IAAI,MAAM,MAAM;IACd,OAAO,aAAa,MAAM,EAAE;IAC5B,KAAK,gBAAgB,KAAK;;;EAM9B,MAAM,YAAY,KAAK,aAAa,QAAQ;EAC5C,IAAI,cAAc,MAChB,OAAO,QAAQ;OACV,IAAI,CAAC,KAAK,gBAAgB;GAC/B,MAAM,KAAK,KAAK,aAAa,gBAAgB;GAC7C,IAAI,OAAO,QAAQ,OAAO,UAAU,IAAI,OAAO,QAAQ;;EAEzD,KAAK,iBAAiB;EACtB,KAAK,OAAO,GAAG,OAAO,MAAM;EAE5B,KAAK,gBAAgB;EAGrB,IAAI;GACF,KAAK,MAAM,YAAY,MAAM,mBAAmB;UAC1C;GACN,KAAK,MAAM;;EAGb,OAAO,iBAAiB,SAAS,KAAK,eAAe;EAErD,KAAK,WAAW,KACd,aAAa;GACX,MAAM,WAAW,KAAK,oBAAoB;GAG1C,OAAO,WAAW;GAClB,OAAO,WAAW,KAAK,oBAAoB;GAC3C,KAAK,aACH,cACA,WAAW,aAAa,KAAK,UAAU,IAAI,GAAG,aAAa,OAC5D;IACD,CACH;;CAGH,uBAA6B;EAC3B,KAAK,aAAa;EAClB,KAAK,SAAS,oBAAoB,SAAS,KAAK,eAAe;EAC/D,KAAK,MAAM,KAAK,KAAK,YAAY,GAAG;EACpC,KAAK,aAAa,EAAE;;CAGtB,yBAAyB,MAAc,MAAqB,OAA4B;EACtF,QAAQ,MAAR;GACE,KAAK;IACH,KAAK,UAAU,GAAG,UAAU,KAAK;IACjC;GACF,KAAK;IACH,KAAK,UAAU,GAAG,UAAU,KAAK;IACjC;GACF,KAAK,iBAEH;GACF,KAAK;IAEH,IAAI,CAAC,KAAK,YAAY;IACtB,IAAI,UAAU,QAAQ,UAAU,KAAK,OAAO,IAAI,EAAE;KAChD,IAAI,KAAK,SAAS,KAAK,QAAQ,QAAQ;KACvC,KAAK,OAAO,GAAG,MAAM;;IAEvB;;EAGJ,IAAI,SAAS,YAAY,KAAK,UAAU,GAAG,UAAU,KAAK;EAC1D,IAAI,CAAC,KAAK,cAAc,CAAC,KAAK,SAAS;EACvC,IAAI,KAAK,YAAY,CAAC,SAAS,KAAK,EAClC,IAAI,UAAU,MAAM,KAAK,QAAQ,aAAa,MAAM,MAAM;OACrD,KAAK,QAAQ,gBAAgB,KAAK;;CAI3C,aAAwC;EACtC,OAAQ,KAAK,YAA2C;;CAG1D,qBAAsC;EACpC,IAAI,KAAK,UAAU,IAAI,EAAE,OAAO;EAChC,OAAO,KAAK,MAAM,KAAK,IAAI,UAAU,GAAG;;CAG1C,qBAAsC;EACpC,IAAI,KAAK,UAAU,IAAI,EAAE,OAAO;EAChC,OAAO,KAAK,MAAM,KAAK,IAAI,UAAU,GAAG;;CAG1C,iBAA+B;EAC7B,KAAK,UAAU,GAAG,KAAK,aAAa,WAAW,CAAC;EAChD,KAAK,UAAU,GAAG,KAAK,aAAa,WAAW,CAAC;EAChD,KAAK,UAAU,GAAG,KAAK,aAAa,WAAW,CAAC;;CAGlD,sBAAsE;EACpE,MAAM,MAAM,KAAK,UAAU,aAAa;EACxC,KAAK,MAAM,SAAS,MAAM,KAAK,KAAK,SAAS,EAC3C,IAAI,MAAM,YAAY,KAAK,OAAO;EAEpC,MAAM,UAAU,SAAS,cAAc,KAAK,UAAU;EACtD,KAAK,YAAY,QAAQ;EACzB,OAAO;;CAGT,uBAA8C;EAC5C,MAAM,SAAS,KAAK;EACpB,IAAI,CAAC,QAAQ;EACb,KAAK,OAAO,GAAG,OAAO,MAAM;EAC5B,KAAK,cACH,IAAI,YAA+B,gBAAgB;GACjD,QAAQ,EAAE,OAAO,OAAO,OAAO;GAC/B,SAAS;GACT,UAAU;GACX,CAAC,CACH"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Read } from "@aihu/signals";
|
|
2
|
+
|
|
3
|
+
//#region src/input/text-control.d.ts
|
|
4
|
+
declare abstract class AihuTextControlBase extends HTMLElement {
|
|
5
|
+
/** Host attributes forwarded to the native child — supplied per subclass. */
|
|
6
|
+
protected static readonly FORWARDED: readonly string[];
|
|
7
|
+
/** The native tag this control wraps. */
|
|
8
|
+
protected abstract readonly nativeTag: 'input' | 'textarea';
|
|
9
|
+
private readonly _value;
|
|
10
|
+
private readonly _disabled;
|
|
11
|
+
private readonly _required;
|
|
12
|
+
private readonly _readonly;
|
|
13
|
+
private _fc;
|
|
14
|
+
private _disposers;
|
|
15
|
+
private _native;
|
|
16
|
+
private _connected;
|
|
17
|
+
private _defaultSeeded;
|
|
18
|
+
/** The current text value as a read signal. */
|
|
19
|
+
get value(): Read<string>;
|
|
20
|
+
/** The wrapped native element (null before first connect). */
|
|
21
|
+
get nativeControl(): HTMLInputElement | HTMLTextAreaElement | null;
|
|
22
|
+
/** Programmatic write: native value + signal + reflected `value` attribute. */
|
|
23
|
+
setValue(next: string): void;
|
|
24
|
+
/** Focus delegates to the native child (the real interactive element). */
|
|
25
|
+
focus(options?: FocusOptions): void;
|
|
26
|
+
connectedCallback(): void;
|
|
27
|
+
disconnectedCallback(): void;
|
|
28
|
+
attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
|
|
29
|
+
private _forwarded;
|
|
30
|
+
private _effectiveDisabled;
|
|
31
|
+
private _effectiveRequired;
|
|
32
|
+
private _syncFromAttrs;
|
|
33
|
+
private _findOrCreateNative;
|
|
34
|
+
private readonly _onNativeInput;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { AihuTextControlBase as t };
|
|
38
|
+
//# sourceMappingURL=text-control-Brv5fUEX.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { t as AihuTextControlBase } from "./text-control-Brv5fUEX.js";
|
|
2
|
+
|
|
3
|
+
//#region src/textarea/index.d.ts
|
|
4
|
+
declare class AihuTextarea extends AihuTextControlBase {
|
|
5
|
+
static readonly observedAttributes: string[];
|
|
6
|
+
protected static readonly FORWARDED: readonly string[];
|
|
7
|
+
protected readonly nativeTag: "textarea";
|
|
8
|
+
}
|
|
9
|
+
/** Register `<aihu-textarea>` (idempotent). */
|
|
10
|
+
declare function defineTextarea(tag?: string): void;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { AihuTextarea, defineTextarea };
|
|
13
|
+
//# sourceMappingURL=textarea.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textarea.d.ts","names":[],"sources":["../src/textarea/index.ts"],"mappings":";;;cAqBa,YAAA,SAAqB,mBAAA;EAAA,gBAChB,kBAAA;EAAA,0BACmB,SAAA;EAAA,mBAEP,SAAA;AAAA;AAK9B;AAAA,iBAAgB,cAAA,CAAe,GAAA"}
|
package/dist/textarea.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { n as TEXT_CONTROL_OBSERVED, t as AihuTextControlBase } from "./text-control-BBX7s8Oe.js";
|
|
2
|
+
//#region src/textarea/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* `<aihu-textarea>` — headless multi-line text control. Wraps (or creates) a
|
|
5
|
+
* real light-DOM `<textarea>` and delegates editing/focus/form participation
|
|
6
|
+
* to it (the native-handoff principle — see `../input/text-control.ts`).
|
|
7
|
+
* Ships zero CSS: the host reflects `data-state="disabled" | "readonly" |
|
|
8
|
+
* "idle"` and emits `value-change` CustomEvents for the consumer.
|
|
9
|
+
*/
|
|
10
|
+
const TEXTAREA_FORWARDED = [
|
|
11
|
+
"name",
|
|
12
|
+
"placeholder",
|
|
13
|
+
"rows",
|
|
14
|
+
"cols",
|
|
15
|
+
"minlength",
|
|
16
|
+
"maxlength",
|
|
17
|
+
"readonly",
|
|
18
|
+
"autocomplete"
|
|
19
|
+
];
|
|
20
|
+
var AihuTextarea = class extends AihuTextControlBase {
|
|
21
|
+
static observedAttributes = [...TEXT_CONTROL_OBSERVED, ...TEXTAREA_FORWARDED];
|
|
22
|
+
static FORWARDED = TEXTAREA_FORWARDED;
|
|
23
|
+
nativeTag = "textarea";
|
|
24
|
+
};
|
|
25
|
+
let _defined = false;
|
|
26
|
+
/** Register `<aihu-textarea>` (idempotent). */
|
|
27
|
+
function defineTextarea(tag = "aihu-textarea") {
|
|
28
|
+
if (_defined || customElements.get(tag)) {
|
|
29
|
+
_defined = true;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
customElements.define(tag, AihuTextarea);
|
|
33
|
+
_defined = true;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { AihuTextarea, defineTextarea };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=textarea.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textarea.js","names":[],"sources":["../src/textarea/index.ts"],"sourcesContent":["/**\n * `<aihu-textarea>` — headless multi-line text control. Wraps (or creates) a\n * real light-DOM `<textarea>` and delegates editing/focus/form participation\n * to it (the native-handoff principle — see `../input/text-control.ts`).\n * Ships zero CSS: the host reflects `data-state=\"disabled\" | \"readonly\" |\n * \"idle\"` and emits `value-change` CustomEvents for the consumer.\n */\n\nimport { AihuTextControlBase, TEXT_CONTROL_OBSERVED } from '../input/text-control.ts'\n\nconst TEXTAREA_FORWARDED: readonly string[] = [\n 'name',\n 'placeholder',\n 'rows',\n 'cols',\n 'minlength',\n 'maxlength',\n 'readonly',\n 'autocomplete',\n]\n\nexport class AihuTextarea extends AihuTextControlBase {\n static readonly observedAttributes = [...TEXT_CONTROL_OBSERVED, ...TEXTAREA_FORWARDED]\n protected static override readonly FORWARDED = TEXTAREA_FORWARDED\n\n protected override readonly nativeTag = 'textarea' as const\n}\n\nlet _defined = false\n/** Register `<aihu-textarea>` (idempotent). */\nexport function defineTextarea(tag = 'aihu-textarea'): void {\n if (_defined || customElements.get(tag)) {\n _defined = true\n return\n }\n customElements.define(tag, AihuTextarea)\n _defined = true\n}\n"],"mappings":";;;;;;;;;AAUA,MAAM,qBAAwC;CAC5C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,IAAa,eAAb,cAAkC,oBAAoB;CACpD,OAAgB,qBAAqB,CAAC,GAAG,uBAAuB,GAAG,mBAAmB;CACtF,OAAmC,YAAY;CAE/C,YAAwC;;AAG1C,IAAI,WAAW;;AAEf,SAAgB,eAAe,MAAM,iBAAuB;CAC1D,IAAI,YAAY,eAAe,IAAI,IAAI,EAAE;EACvC,WAAW;EACX;;CAEF,eAAe,OAAO,KAAK,aAAa;CACxC,WAAW"}
|
package/dist/tooltip.d.ts
CHANGED
|
@@ -65,8 +65,14 @@ declare class AihuTooltipContent extends HTMLElement {
|
|
|
65
65
|
private _position;
|
|
66
66
|
private readonly _onKeydown;
|
|
67
67
|
}
|
|
68
|
-
/**
|
|
69
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Register all tooltip custom elements under `<prefix>-tooltip-*` (idempotent
|
|
70
|
+
* per prefix). Non-default prefixes register a fresh trivial subclass per
|
|
71
|
+
* piece — a constructor can only be `customElements.define`d once. Demos/
|
|
72
|
+
* stories use a non-`aihu` prefix so styled recipes own the `aihu-tooltip-*`
|
|
73
|
+
* namespace (spec §9.4).
|
|
74
|
+
*/
|
|
75
|
+
declare function defineTooltip(prefix?: string): void;
|
|
70
76
|
//#endregion
|
|
71
77
|
export { AihuTooltipContent, AihuTooltipRoot, AihuTooltipTrigger, TooltipContextValue, TooltipCoords, defineTooltip, tooltipContext };
|
|
72
78
|
//# sourceMappingURL=tooltip.d.ts.map
|
package/dist/tooltip.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip.d.ts","names":[],"sources":["../src/tooltip/index.ts"],"mappings":";;;;;UAoBiB,aAAA;EACf,CAAA;EACA,CAAA;EACA,SAAA,EAAW,SAAA;AAAA;AAAA,UAGI,mBAAA;EAAA,SACN,IAAA,EAAM,IAAA;EAAA,SACN,SAAA,EAAW,IAAA;EAAA,SACX,SAAA,EAAW,IAAA,CAAK,SAAA;EAAA,SAChB,SAAA,EAAW,IAAA;EAAA,SACX,UAAA,EAAY,IAAA;EACrB,OAAA,CAAQ,IAAA;EALO;EAOf,YAAA;EACA,aAAA;EANS;EAQT,OAAA;EACA,eAAA,CAAgB,EAAA,EAAI,OAAA;EACpB,SAAA,IAAa,OAAA;AAAA;AAAA,cAGF,cAAA,EAAc,UAAA,CAAA,mBAAA;AAAA,cAKd,eAAA,SAAwB,WAAA;EAAA,gBACnB,kBAAA;EAAA,iBAEC,KAAA;EAAA,iBACA,UAAA;EAAA,iBACA,UAAA;EAAA,iBACA,UAAA;EAAA,iBACA,WAAA;EAAA,QACT,QAAA;EAAA,QACA,UAAA;EAAA,QACA,WAAA;EAAA,QACA,UAAA;EAAA,QACA,IAAA;;MAsBJ,IAAA,CAAA,GAAQ,IAAA;EAIZ,iBAAA,CAAA;EASA,oBAAA,CAAA;EAMA,wBAAA,CAAyB,IAAA,UAAc,IAAA,iBAAqB,KAAA;EAAA,QAiBpD,QAAA;EAAA,QAQA,aAAA;EAAA,QAKA,cAAA;EAAA,QAKA,YAAA;EAAA,QAOA,cAAA;AAAA;AAAA,cAcG,kBAAA,SAA2B,WAAA;EAAA,QAC9B,GAAA;EAAA,QACA,SAAA;EAER,iBAAA,CAAA;EAiBA,oBAAA,CAAA;EAAA,iBAKiB,QAAA;EAAA,iBAGA,QAAA;EAAA,iBAGA,UAAA;AAAA;AAAA,cAKN,kBAAA,SAA2B,WAAA;EAAA,QAC9B,GAAA;EAAA,QACA,SAAA;EAER,iBAAA,CAAA;EAeA,oBAAA,CAAA;EA/HA;EAAA,QAsIQ,SAAA;EAAA,iBAMS,UAAA;AAAA
|
|
1
|
+
{"version":3,"file":"tooltip.d.ts","names":[],"sources":["../src/tooltip/index.ts"],"mappings":";;;;;UAoBiB,aAAA;EACf,CAAA;EACA,CAAA;EACA,SAAA,EAAW,SAAA;AAAA;AAAA,UAGI,mBAAA;EAAA,SACN,IAAA,EAAM,IAAA;EAAA,SACN,SAAA,EAAW,IAAA;EAAA,SACX,SAAA,EAAW,IAAA,CAAK,SAAA;EAAA,SAChB,SAAA,EAAW,IAAA;EAAA,SACX,UAAA,EAAY,IAAA;EACrB,OAAA,CAAQ,IAAA;EALO;EAOf,YAAA;EACA,aAAA;EANS;EAQT,OAAA;EACA,eAAA,CAAgB,EAAA,EAAI,OAAA;EACpB,SAAA,IAAa,OAAA;AAAA;AAAA,cAGF,cAAA,EAAc,UAAA,CAAA,mBAAA;AAAA,cAKd,eAAA,SAAwB,WAAA;EAAA,gBACnB,kBAAA;EAAA,iBAEC,KAAA;EAAA,iBACA,UAAA;EAAA,iBACA,UAAA;EAAA,iBACA,UAAA;EAAA,iBACA,WAAA;EAAA,QACT,QAAA;EAAA,QACA,UAAA;EAAA,QACA,WAAA;EAAA,QACA,UAAA;EAAA,QACA,IAAA;;MAsBJ,IAAA,CAAA,GAAQ,IAAA;EAIZ,iBAAA,CAAA;EASA,oBAAA,CAAA;EAMA,wBAAA,CAAyB,IAAA,UAAc,IAAA,iBAAqB,KAAA;EAAA,QAiBpD,QAAA;EAAA,QAQA,aAAA;EAAA,QAKA,cAAA;EAAA,QAKA,YAAA;EAAA,QAOA,cAAA;AAAA;AAAA,cAcG,kBAAA,SAA2B,WAAA;EAAA,QAC9B,GAAA;EAAA,QACA,SAAA;EAER,iBAAA,CAAA;EAiBA,oBAAA,CAAA;EAAA,iBAKiB,QAAA;EAAA,iBAGA,QAAA;EAAA,iBAGA,UAAA;AAAA;AAAA,cAKN,kBAAA,SAA2B,WAAA;EAAA,QAC9B,GAAA;EAAA,QACA,SAAA;EAER,iBAAA,CAAA;EAeA,oBAAA,CAAA;EA/HA;EAAA,QAsIQ,SAAA;EAAA,iBAMS,UAAA;AAAA;;;;;;;;iBAmBH,aAAA,CAAc,MAAA"}
|
package/dist/tooltip.js
CHANGED
|
@@ -182,12 +182,21 @@ const REGISTRY = [
|
|
|
182
182
|
["aihu-tooltip-trigger", AihuTooltipTrigger],
|
|
183
183
|
["aihu-tooltip-content", AihuTooltipContent]
|
|
184
184
|
];
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
185
|
+
const _definedPrefixes = /* @__PURE__ */ new Set();
|
|
186
|
+
/**
|
|
187
|
+
* Register all tooltip custom elements under `<prefix>-tooltip-*` (idempotent
|
|
188
|
+
* per prefix). Non-default prefixes register a fresh trivial subclass per
|
|
189
|
+
* piece — a constructor can only be `customElements.define`d once. Demos/
|
|
190
|
+
* stories use a non-`aihu` prefix so styled recipes own the `aihu-tooltip-*`
|
|
191
|
+
* namespace (spec §9.4).
|
|
192
|
+
*/
|
|
193
|
+
function defineTooltip(prefix = "aihu") {
|
|
194
|
+
if (_definedPrefixes.has(prefix)) return;
|
|
195
|
+
for (const [tag, ctor] of REGISTRY) {
|
|
196
|
+
const name = prefix === "aihu" ? tag : tag.replace(/^aihu-/, `${prefix}-`);
|
|
197
|
+
if (!customElements.get(name)) customElements.define(name, prefix === "aihu" ? ctor : class extends ctor {});
|
|
198
|
+
}
|
|
199
|
+
_definedPrefixes.add(prefix);
|
|
191
200
|
}
|
|
192
201
|
//#endregion
|
|
193
202
|
export { AihuTooltipContent, AihuTooltipRoot, AihuTooltipTrigger, defineTooltip, tooltipContext };
|
package/dist/tooltip.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip.js","names":[],"sources":["../src/tooltip/index.ts"],"sourcesContent":["/**\n * Headless tooltip — `<aihu-tooltip-root>` + `<aihu-tooltip-trigger>` +\n * `<aihu-tooltip-content>`. Implements the WAI-ARIA APG\n * [Tooltip](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/) pattern: the\n * trigger is `aria-describedby` the tooltip (NOT labelled-by), the content has\n * `role=\"tooltip\"` and is not focusable, and Escape dismisses.\n *\n * Placement REUSES the Plan 3 `position()` shim from\n * `@aihu/css-engine/runtime/progressive` — tooltip contains NO positioning math\n * of its own and adds NO floating-ui dependency.\n *\n * Root attributes (reflected): `open` (boolean), `open-delay`/`close-delay`\n * (ms, default 700/300), `placement` (`\"top\"|\"bottom\"|\"left\"|\"right\"`).\n * Root signals: `open`, `coords` (computed position written by the shim).\n */\n\nimport { type Placement, position } from '@aihu/css-engine/runtime/progressive'\nimport { effect, type Read, signal } from '@aihu/signals'\nimport { createDomContext, injectValue, provideContext } from '../dom-context.ts'\n\nexport interface TooltipCoords {\n x: number\n y: number\n placement: Placement\n}\n\nexport interface TooltipContextValue {\n readonly open: Read<boolean>\n readonly contentId: Read<string>\n readonly placement: Read<Placement>\n readonly openDelay: Read<number>\n readonly closeDelay: Read<number>\n setOpen(next: boolean): void\n /** Schedule open/close honoring the configured delays. */\n scheduleOpen(): void\n scheduleClose(): void\n /** Immediate dismiss (Escape). */\n dismiss(): void\n registerTrigger(el: Element): void\n triggerEl(): Element | null\n}\n\nexport const tooltipContext = createDomContext<TooltipContextValue>('tooltip')\n\nlet _idSeq = 0\nconst uid = (): string => `aihu-tooltip-${(_idSeq += 1)}`\n\nexport class AihuTooltipRoot extends HTMLElement {\n static readonly observedAttributes = ['open', 'open-delay', 'close-delay', 'placement']\n\n private readonly _open = signal(false)\n private readonly _contentId = signal(uid())\n private readonly _placement = signal<Placement>('bottom')\n private readonly _openDelay = signal(700)\n private readonly _closeDelay = signal(300)\n private _trigger: Element | null = null\n private _openTimer: ReturnType<typeof setTimeout> | null = null\n private _closeTimer: ReturnType<typeof setTimeout> | null = null\n private _disposers: Array<() => void> = []\n private _ctx: TooltipContextValue\n\n constructor() {\n super()\n this._ctx = {\n open: this._open[0],\n contentId: this._contentId[0],\n placement: this._placement[0],\n openDelay: this._openDelay[0],\n closeDelay: this._closeDelay[0],\n setOpen: (n) => this._setOpen(n),\n scheduleOpen: () => this._scheduleOpen(),\n scheduleClose: () => this._scheduleClose(),\n dismiss: () => this._setOpen(false),\n registerTrigger: (el) => {\n this._trigger = el\n },\n triggerEl: () => this._trigger,\n }\n provideContext(this, tooltipContext, this._ctx)\n }\n\n get open(): Read<boolean> {\n return this._open[0]\n }\n\n connectedCallback(): void {\n this._syncFromAttrs()\n this._disposers.push(\n effect(() => {\n this.setAttribute('data-state', this._open[0]() ? 'open' : 'closed')\n }),\n )\n }\n\n disconnectedCallback(): void {\n this._clearTimers()\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 'open':\n this._setOpen(value !== null)\n break\n case 'open-delay':\n if (value !== null) this._openDelay[1](Number(value))\n break\n case 'close-delay':\n if (value !== null) this._closeDelay[1](Number(value))\n break\n case 'placement':\n if (isPlacement(value)) this._placement[1](value)\n break\n }\n }\n\n private _setOpen(next: boolean): void {\n this._clearTimers()\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 private _scheduleOpen(): void {\n this._clearTimers()\n this._openTimer = setTimeout(() => this._setOpen(true), this._openDelay[0]())\n }\n\n private _scheduleClose(): void {\n this._clearTimers()\n this._closeTimer = setTimeout(() => this._setOpen(false), this._closeDelay[0]())\n }\n\n private _clearTimers(): void {\n if (this._openTimer) clearTimeout(this._openTimer)\n if (this._closeTimer) clearTimeout(this._closeTimer)\n this._openTimer = null\n this._closeTimer = null\n }\n\n private _syncFromAttrs(): void {\n if (this.hasAttribute('open-delay')) this._openDelay[1](Number(this.getAttribute('open-delay')))\n if (this.hasAttribute('close-delay'))\n this._closeDelay[1](Number(this.getAttribute('close-delay')))\n const p = this.getAttribute('placement')\n if (isPlacement(p)) this._placement[1](p)\n this._open[1](this.hasAttribute('open'))\n }\n}\n\nfunction isPlacement(v: string | null): v is Placement {\n return v === 'top' || v === 'bottom' || v === 'left' || v === 'right'\n}\n\nexport class AihuTooltipTrigger extends HTMLElement {\n private ctx!: TooltipContextValue\n private disposers: Array<() => void> = []\n\n connectedCallback(): void {\n this.ctx = injectValue(this, tooltipContext)\n this.ctx.registerTrigger(this)\n this.addEventListener('mouseenter', this._onEnter)\n this.addEventListener('mouseleave', this._onLeave)\n this.addEventListener('focus', this._onEnter)\n this.addEventListener('blur', this._onLeave)\n this.addEventListener('keydown', this._onKeydown)\n // APG: the trigger is described-by the tooltip.\n this.disposers.push(\n effect(() => {\n this.setAttribute('aria-describedby', this.ctx.contentId())\n this.setAttribute('data-state', this.ctx.open() ? 'open' : 'closed')\n }),\n )\n }\n\n disconnectedCallback(): void {\n for (const d of this.disposers) d()\n this.disposers = []\n }\n\n private readonly _onEnter = (): void => {\n this.ctx.scheduleOpen()\n }\n private readonly _onLeave = (): void => {\n this.ctx.scheduleClose()\n }\n private readonly _onKeydown = (ev: KeyboardEvent): void => {\n if (ev.key === 'Escape') this.ctx.dismiss()\n }\n}\n\nexport class AihuTooltipContent extends HTMLElement {\n private ctx!: TooltipContextValue\n private disposers: Array<() => void> = []\n\n connectedCallback(): void {\n this.ctx = injectValue(this, tooltipContext)\n // APG: tooltip content is not focusable.\n this.setAttribute('role', 'tooltip')\n this.id = this.ctx.contentId()\n this.addEventListener('keydown', this._onKeydown)\n this.disposers.push(\n effect(() => {\n const open = this.ctx.open()\n this.setAttribute('data-state', open ? 'open' : 'closed')\n if (open) this._position()\n }),\n )\n }\n\n disconnectedCallback(): void {\n this.removeEventListener('keydown', this._onKeydown)\n for (const d of this.disposers) d()\n this.disposers = []\n }\n\n /** Position against the trigger using the REUSED css-engine shim. */\n private _position(): void {\n const anchor = this.ctx.triggerEl()\n if (!anchor) return\n position(anchor, this, { placement: this.ctx.placement() })\n }\n\n private readonly _onKeydown = (ev: KeyboardEvent): void => {\n if (ev.key === 'Escape') this.ctx.dismiss()\n }\n}\n\nconst REGISTRY: Array<[string, CustomElementConstructor]> = [\n ['aihu-tooltip-root', AihuTooltipRoot],\n ['aihu-tooltip-trigger', AihuTooltipTrigger],\n ['aihu-tooltip-content', AihuTooltipContent],\n]\n\nlet _defined = false\n/** Register all tooltip custom elements (idempotent). */\nexport function defineTooltip(): void {\n if (_defined) return\n for (const [tag, ctor] of REGISTRY) {\n if (!customElements.get(tag)) customElements.define(tag, ctor)\n }\n _defined = true\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tooltip.js","names":[],"sources":["../src/tooltip/index.ts"],"sourcesContent":["/**\n * Headless tooltip — `<aihu-tooltip-root>` + `<aihu-tooltip-trigger>` +\n * `<aihu-tooltip-content>`. Implements the WAI-ARIA APG\n * [Tooltip](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/) pattern: the\n * trigger is `aria-describedby` the tooltip (NOT labelled-by), the content has\n * `role=\"tooltip\"` and is not focusable, and Escape dismisses.\n *\n * Placement REUSES the Plan 3 `position()` shim from\n * `@aihu/css-engine/runtime/progressive` — tooltip contains NO positioning math\n * of its own and adds NO floating-ui dependency.\n *\n * Root attributes (reflected): `open` (boolean), `open-delay`/`close-delay`\n * (ms, default 700/300), `placement` (`\"top\"|\"bottom\"|\"left\"|\"right\"`).\n * Root signals: `open`, `coords` (computed position written by the shim).\n */\n\nimport { type Placement, position } from '@aihu/css-engine/runtime/progressive'\nimport { effect, type Read, signal } from '@aihu/signals'\nimport { createDomContext, injectValue, provideContext } from '../dom-context.ts'\n\nexport interface TooltipCoords {\n x: number\n y: number\n placement: Placement\n}\n\nexport interface TooltipContextValue {\n readonly open: Read<boolean>\n readonly contentId: Read<string>\n readonly placement: Read<Placement>\n readonly openDelay: Read<number>\n readonly closeDelay: Read<number>\n setOpen(next: boolean): void\n /** Schedule open/close honoring the configured delays. */\n scheduleOpen(): void\n scheduleClose(): void\n /** Immediate dismiss (Escape). */\n dismiss(): void\n registerTrigger(el: Element): void\n triggerEl(): Element | null\n}\n\nexport const tooltipContext = createDomContext<TooltipContextValue>('tooltip')\n\nlet _idSeq = 0\nconst uid = (): string => `aihu-tooltip-${(_idSeq += 1)}`\n\nexport class AihuTooltipRoot extends HTMLElement {\n static readonly observedAttributes = ['open', 'open-delay', 'close-delay', 'placement']\n\n private readonly _open = signal(false)\n private readonly _contentId = signal(uid())\n private readonly _placement = signal<Placement>('bottom')\n private readonly _openDelay = signal(700)\n private readonly _closeDelay = signal(300)\n private _trigger: Element | null = null\n private _openTimer: ReturnType<typeof setTimeout> | null = null\n private _closeTimer: ReturnType<typeof setTimeout> | null = null\n private _disposers: Array<() => void> = []\n private _ctx: TooltipContextValue\n\n constructor() {\n super()\n this._ctx = {\n open: this._open[0],\n contentId: this._contentId[0],\n placement: this._placement[0],\n openDelay: this._openDelay[0],\n closeDelay: this._closeDelay[0],\n setOpen: (n) => this._setOpen(n),\n scheduleOpen: () => this._scheduleOpen(),\n scheduleClose: () => this._scheduleClose(),\n dismiss: () => this._setOpen(false),\n registerTrigger: (el) => {\n this._trigger = el\n },\n triggerEl: () => this._trigger,\n }\n provideContext(this, tooltipContext, this._ctx)\n }\n\n get open(): Read<boolean> {\n return this._open[0]\n }\n\n connectedCallback(): void {\n this._syncFromAttrs()\n this._disposers.push(\n effect(() => {\n this.setAttribute('data-state', this._open[0]() ? 'open' : 'closed')\n }),\n )\n }\n\n disconnectedCallback(): void {\n this._clearTimers()\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 'open':\n this._setOpen(value !== null)\n break\n case 'open-delay':\n if (value !== null) this._openDelay[1](Number(value))\n break\n case 'close-delay':\n if (value !== null) this._closeDelay[1](Number(value))\n break\n case 'placement':\n if (isPlacement(value)) this._placement[1](value)\n break\n }\n }\n\n private _setOpen(next: boolean): void {\n this._clearTimers()\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 private _scheduleOpen(): void {\n this._clearTimers()\n this._openTimer = setTimeout(() => this._setOpen(true), this._openDelay[0]())\n }\n\n private _scheduleClose(): void {\n this._clearTimers()\n this._closeTimer = setTimeout(() => this._setOpen(false), this._closeDelay[0]())\n }\n\n private _clearTimers(): void {\n if (this._openTimer) clearTimeout(this._openTimer)\n if (this._closeTimer) clearTimeout(this._closeTimer)\n this._openTimer = null\n this._closeTimer = null\n }\n\n private _syncFromAttrs(): void {\n if (this.hasAttribute('open-delay')) this._openDelay[1](Number(this.getAttribute('open-delay')))\n if (this.hasAttribute('close-delay'))\n this._closeDelay[1](Number(this.getAttribute('close-delay')))\n const p = this.getAttribute('placement')\n if (isPlacement(p)) this._placement[1](p)\n this._open[1](this.hasAttribute('open'))\n }\n}\n\nfunction isPlacement(v: string | null): v is Placement {\n return v === 'top' || v === 'bottom' || v === 'left' || v === 'right'\n}\n\nexport class AihuTooltipTrigger extends HTMLElement {\n private ctx!: TooltipContextValue\n private disposers: Array<() => void> = []\n\n connectedCallback(): void {\n this.ctx = injectValue(this, tooltipContext)\n this.ctx.registerTrigger(this)\n this.addEventListener('mouseenter', this._onEnter)\n this.addEventListener('mouseleave', this._onLeave)\n this.addEventListener('focus', this._onEnter)\n this.addEventListener('blur', this._onLeave)\n this.addEventListener('keydown', this._onKeydown)\n // APG: the trigger is described-by the tooltip.\n this.disposers.push(\n effect(() => {\n this.setAttribute('aria-describedby', this.ctx.contentId())\n this.setAttribute('data-state', this.ctx.open() ? 'open' : 'closed')\n }),\n )\n }\n\n disconnectedCallback(): void {\n for (const d of this.disposers) d()\n this.disposers = []\n }\n\n private readonly _onEnter = (): void => {\n this.ctx.scheduleOpen()\n }\n private readonly _onLeave = (): void => {\n this.ctx.scheduleClose()\n }\n private readonly _onKeydown = (ev: KeyboardEvent): void => {\n if (ev.key === 'Escape') this.ctx.dismiss()\n }\n}\n\nexport class AihuTooltipContent extends HTMLElement {\n private ctx!: TooltipContextValue\n private disposers: Array<() => void> = []\n\n connectedCallback(): void {\n this.ctx = injectValue(this, tooltipContext)\n // APG: tooltip content is not focusable.\n this.setAttribute('role', 'tooltip')\n this.id = this.ctx.contentId()\n this.addEventListener('keydown', this._onKeydown)\n this.disposers.push(\n effect(() => {\n const open = this.ctx.open()\n this.setAttribute('data-state', open ? 'open' : 'closed')\n if (open) this._position()\n }),\n )\n }\n\n disconnectedCallback(): void {\n this.removeEventListener('keydown', this._onKeydown)\n for (const d of this.disposers) d()\n this.disposers = []\n }\n\n /** Position against the trigger using the REUSED css-engine shim. */\n private _position(): void {\n const anchor = this.ctx.triggerEl()\n if (!anchor) return\n position(anchor, this, { placement: this.ctx.placement() })\n }\n\n private readonly _onKeydown = (ev: KeyboardEvent): void => {\n if (ev.key === 'Escape') this.ctx.dismiss()\n }\n}\n\nconst REGISTRY: Array<[string, CustomElementConstructor]> = [\n ['aihu-tooltip-root', AihuTooltipRoot],\n ['aihu-tooltip-trigger', AihuTooltipTrigger],\n ['aihu-tooltip-content', AihuTooltipContent],\n]\n\nconst _definedPrefixes = new Set<string>()\n/**\n * Register all tooltip custom elements under `<prefix>-tooltip-*` (idempotent\n * per prefix). Non-default prefixes register a fresh trivial subclass per\n * piece — a constructor can only be `customElements.define`d once. Demos/\n * stories use a non-`aihu` prefix so styled recipes own the `aihu-tooltip-*`\n * namespace (spec §9.4).\n */\nexport function defineTooltip(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"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aihu/primitives",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -46,6 +46,34 @@
|
|
|
46
46
|
"./button": {
|
|
47
47
|
"types": "./dist/button.d.ts",
|
|
48
48
|
"import": "./dist/button.js"
|
|
49
|
+
},
|
|
50
|
+
"./separator": {
|
|
51
|
+
"types": "./dist/separator.d.ts",
|
|
52
|
+
"import": "./dist/separator.js"
|
|
53
|
+
},
|
|
54
|
+
"./label": {
|
|
55
|
+
"types": "./dist/label.d.ts",
|
|
56
|
+
"import": "./dist/label.js"
|
|
57
|
+
},
|
|
58
|
+
"./input": {
|
|
59
|
+
"types": "./dist/input.d.ts",
|
|
60
|
+
"import": "./dist/input.js"
|
|
61
|
+
},
|
|
62
|
+
"./textarea": {
|
|
63
|
+
"types": "./dist/textarea.d.ts",
|
|
64
|
+
"import": "./dist/textarea.js"
|
|
65
|
+
},
|
|
66
|
+
"./checkbox": {
|
|
67
|
+
"types": "./dist/checkbox.d.ts",
|
|
68
|
+
"import": "./dist/checkbox.js"
|
|
69
|
+
},
|
|
70
|
+
"./switch": {
|
|
71
|
+
"types": "./dist/switch.d.ts",
|
|
72
|
+
"import": "./dist/switch.js"
|
|
73
|
+
},
|
|
74
|
+
"./radio-group": {
|
|
75
|
+
"types": "./dist/radio-group.d.ts",
|
|
76
|
+
"import": "./dist/radio-group.js"
|
|
49
77
|
}
|
|
50
78
|
},
|
|
51
79
|
"files": [
|
|
@@ -57,7 +85,7 @@
|
|
|
57
85
|
"dependencies": {
|
|
58
86
|
"@aihu/signals": "0.2.0",
|
|
59
87
|
"@aihu/arbor": "1.0.0",
|
|
60
|
-
"@aihu/css-engine": "0.4.
|
|
88
|
+
"@aihu/css-engine": "0.4.3"
|
|
61
89
|
},
|
|
62
90
|
"scripts": {
|
|
63
91
|
"build": "rolldown -c",
|
|
@@ -75,5 +103,8 @@
|
|
|
75
103
|
"bugs": "https://github.com/fellwork/aihu/issues",
|
|
76
104
|
"publishConfig": {
|
|
77
105
|
"access": "public"
|
|
106
|
+
},
|
|
107
|
+
"devDependencies": {
|
|
108
|
+
"storybook": "^10.4.3"
|
|
78
109
|
}
|
|
79
110
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"form-control.d.ts","names":[],"sources":["../src/form-control/index.ts"],"mappings":";;;;UAqBiB,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;AAAA;AAAA,cAGb,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,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;;EAejB,iBAAA,CAAA;EAuBA,oBAAA,CAAA;EAKA,wBAAA,CAAyB,IAAA,UAAc,IAAA,iBAAqB,KAAA;EA9D5C;;EAiFhB,oBAAA,CAAA;EAAA,QAIQ,aAAA;EAhFS;EAAA,QAuFT,QAAA;EAAA,QAMA,iBAAA;AAAA;;iBA+BM,iBAAA,CAAkB,GAAA"}
|
package/dist/form-control.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"form-control.js","names":[],"sources":["../src/form-control/index.ts"],"sourcesContent":["/**\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 }` signals so\n * descendant pieces consume the shared state without prop-drilling.\n */\n\nimport { effect, type Read, signal } from '@aihu/signals'\nimport { createDomContext, provideContext } from '../dom-context.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}\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\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\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 })\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 private _control(): HTMLElement | null {\n return this.querySelector<HTMLElement>(\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.\n const label = this.querySelector<HTMLElement>('label, [data-fc-label]')\n if (label && control) {\n if (label instanceof HTMLLabelElement) label.htmlFor = control.id\n else label.setAttribute('for', control.id)\n }\n\n // Collect description + error ids into aria-describedby.\n const described: string[] = []\n for (const el of this.querySelectorAll<HTMLElement>('[data-fc-description], [data-fc-error]')) {\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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"}
|