@aihu/primitives 0.0.12 → 0.1.0
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
package/dist/input.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { n as TEXT_CONTROL_OBSERVED, t as AihuTextControlBase } from "./text-control-BBX7s8Oe.js";
|
|
2
|
+
//#region src/input/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* `<aihu-input>` — headless single-line text control. Wraps (or creates) a
|
|
5
|
+
* real light-DOM `<input>` and delegates editing/focus/form participation to
|
|
6
|
+
* it (the native-handoff principle — see `text-control.ts`). Ships zero CSS:
|
|
7
|
+
* the host reflects `data-state="disabled" | "readonly" | "idle"` and emits
|
|
8
|
+
* `value-change` CustomEvents for the consumer.
|
|
9
|
+
*/
|
|
10
|
+
const INPUT_FORWARDED = [
|
|
11
|
+
"type",
|
|
12
|
+
"name",
|
|
13
|
+
"placeholder",
|
|
14
|
+
"autocomplete",
|
|
15
|
+
"inputmode",
|
|
16
|
+
"pattern",
|
|
17
|
+
"min",
|
|
18
|
+
"max",
|
|
19
|
+
"step",
|
|
20
|
+
"minlength",
|
|
21
|
+
"maxlength",
|
|
22
|
+
"readonly"
|
|
23
|
+
];
|
|
24
|
+
var AihuInput = class extends AihuTextControlBase {
|
|
25
|
+
static observedAttributes = [...TEXT_CONTROL_OBSERVED, ...INPUT_FORWARDED];
|
|
26
|
+
static FORWARDED = INPUT_FORWARDED;
|
|
27
|
+
nativeTag = "input";
|
|
28
|
+
};
|
|
29
|
+
let _defined = false;
|
|
30
|
+
/** Register `<aihu-input>` (idempotent). */
|
|
31
|
+
function defineInput(tag = "aihu-input") {
|
|
32
|
+
if (_defined || customElements.get(tag)) {
|
|
33
|
+
_defined = true;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
customElements.define(tag, AihuInput);
|
|
37
|
+
_defined = true;
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { AihuInput, AihuTextControlBase, defineInput };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.js","names":[],"sources":["../src/input/index.ts"],"sourcesContent":["/**\n * `<aihu-input>` — headless single-line text control. Wraps (or creates) a\n * real light-DOM `<input>` and delegates editing/focus/form participation to\n * it (the native-handoff principle — see `text-control.ts`). Ships zero CSS:\n * the host reflects `data-state=\"disabled\" | \"readonly\" | \"idle\"` and emits\n * `value-change` CustomEvents for the consumer.\n */\n\nimport { AihuTextControlBase, TEXT_CONTROL_OBSERVED } from './text-control.ts'\n\nconst INPUT_FORWARDED: readonly string[] = [\n 'type',\n 'name',\n 'placeholder',\n 'autocomplete',\n 'inputmode',\n 'pattern',\n 'min',\n 'max',\n 'step',\n 'minlength',\n 'maxlength',\n 'readonly',\n]\n\nexport class AihuInput extends AihuTextControlBase {\n static readonly observedAttributes = [...TEXT_CONTROL_OBSERVED, ...INPUT_FORWARDED]\n protected static override readonly FORWARDED = INPUT_FORWARDED\n\n protected override readonly nativeTag = 'input' as const\n}\n\nlet _defined = false\n/** Register `<aihu-input>` (idempotent). */\nexport function defineInput(tag = 'aihu-input'): void {\n if (_defined || customElements.get(tag)) {\n _defined = true\n return\n }\n customElements.define(tag, AihuInput)\n _defined = true\n}\n\nexport { AihuTextControlBase } from './text-control.ts'\n"],"mappings":";;;;;;;;;AAUA,MAAM,kBAAqC;CACzC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,IAAa,YAAb,cAA+B,oBAAoB;CACjD,OAAgB,qBAAqB,CAAC,GAAG,uBAAuB,GAAG,gBAAgB;CACnF,OAAmC,YAAY;CAE/C,YAAwC;;AAG1C,IAAI,WAAW;;AAEf,SAAgB,YAAY,MAAM,cAAoB;CACpD,IAAI,YAAY,eAAe,IAAI,IAAI,EAAE;EACvC,WAAW;EACX;;CAEF,eAAe,OAAO,KAAK,UAAU;CACrC,WAAW"}
|
package/dist/label.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Read } from "@aihu/signals";
|
|
2
|
+
|
|
3
|
+
//#region src/label/index.d.ts
|
|
4
|
+
declare class AihuLabel extends HTMLElement {
|
|
5
|
+
static readonly observedAttributes: string[];
|
|
6
|
+
private readonly _for;
|
|
7
|
+
private _fc;
|
|
8
|
+
private _disposers;
|
|
9
|
+
/** The explicit target id (`for` attribute) as a signal; null when unset. */
|
|
10
|
+
get forId(): Read<string | null>;
|
|
11
|
+
private get _isNativeLabel();
|
|
12
|
+
connectedCallback(): void;
|
|
13
|
+
disconnectedCallback(): void;
|
|
14
|
+
attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
|
|
15
|
+
/** Resolve the labelled target NOW (per interaction — never cached). */
|
|
16
|
+
private _resolveTarget;
|
|
17
|
+
private readonly _onMousedown;
|
|
18
|
+
private readonly _onClick;
|
|
19
|
+
}
|
|
20
|
+
/** Register `<aihu-label>` (idempotent). */
|
|
21
|
+
declare function defineLabel(tag?: string): void;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { AihuLabel, defineLabel };
|
|
24
|
+
//# sourceMappingURL=label.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/label.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { injectValue } from "./dom-context.js";
|
|
2
|
+
import { r as formControlContext, t as AihuFormControl } from "./form-control-B_BO9j7_.js";
|
|
3
|
+
import { effect, signal } from "@aihu/signals";
|
|
4
|
+
//#region src/label/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* `<aihu-label>` — headless label (the Radix Label parity primitive). Wires
|
|
7
|
+
* the label↔control association and forwards interactions the way a native
|
|
8
|
+
* `<label>` does, for targets that native labels cannot reference (custom
|
|
9
|
+
* hosts, role=checkbox/switch/radio elements). Ships NO CSS.
|
|
10
|
+
*
|
|
11
|
+
* Behavior:
|
|
12
|
+
* - On connect: ensures a stable `id`, stamps `data-fc-label` on itself so an
|
|
13
|
+
* ancestor `<aihu-form-control>` discovers it, and (when inside one) asks
|
|
14
|
+
* that ancestor to re-wire so the control gains
|
|
15
|
+
* `aria-labelledby="<labelId>"`.
|
|
16
|
+
* - Target resolution (re-resolved per interaction, never cached): the `for`
|
|
17
|
+
* attribute is looked up via `getElementById` in the label's root; with no
|
|
18
|
+
* `for`, a `formControlContext` ancestor's `controlId` is used.
|
|
19
|
+
* - Standalone (no form-control ancestor): sets `aria-labelledby` = own id on
|
|
20
|
+
* the resolved target reactively. Skipped when the target lives in a
|
|
21
|
+
* different root — ARIA IDREFs cannot cross shadow boundaries (see
|
|
22
|
+
* accessibility.md; use `aria-label` on the target for cross-root cases).
|
|
23
|
+
* - Click forwarding (non-native hosts only): double-click `mousedown` is
|
|
24
|
+
* prevented (no accidental text selection); `click` focuses native text
|
|
25
|
+
* controls, forwards `click()` to native checkbox/radio or custom
|
|
26
|
+
* checkbox/switch/radio hosts, and does nothing for disabled targets or
|
|
27
|
+
* clicks originating on nested interactive children.
|
|
28
|
+
* - If the host IS a native `<label>` (tagName `LABEL`), click forwarding is
|
|
29
|
+
* native — only the context wiring above applies.
|
|
30
|
+
*
|
|
31
|
+
* Reflected attributes: `for` (optional explicit target id).
|
|
32
|
+
*/
|
|
33
|
+
let _idCounter = 0;
|
|
34
|
+
function nextId() {
|
|
35
|
+
_idCounter += 1;
|
|
36
|
+
return `aihu-label-${_idCounter}`;
|
|
37
|
+
}
|
|
38
|
+
var AihuLabel = class extends HTMLElement {
|
|
39
|
+
static observedAttributes = ["for"];
|
|
40
|
+
_for = signal(null);
|
|
41
|
+
_fc = null;
|
|
42
|
+
_disposers = [];
|
|
43
|
+
/** The explicit target id (`for` attribute) as a signal; null when unset. */
|
|
44
|
+
get forId() {
|
|
45
|
+
return this._for[0];
|
|
46
|
+
}
|
|
47
|
+
get _isNativeLabel() {
|
|
48
|
+
return this.tagName === "LABEL";
|
|
49
|
+
}
|
|
50
|
+
connectedCallback() {
|
|
51
|
+
this._for[1](this.getAttribute("for"));
|
|
52
|
+
if (!this.id) this.id = nextId();
|
|
53
|
+
this.setAttribute("data-fc-label", "");
|
|
54
|
+
try {
|
|
55
|
+
this._fc = injectValue(this, formControlContext);
|
|
56
|
+
} catch {
|
|
57
|
+
this._fc = null;
|
|
58
|
+
}
|
|
59
|
+
if (this._fc) {
|
|
60
|
+
let node = this.parentElement;
|
|
61
|
+
while (node) {
|
|
62
|
+
if (node instanceof AihuFormControl) {
|
|
63
|
+
node.recomputeDescribedBy();
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
node = node.parentElement;
|
|
67
|
+
}
|
|
68
|
+
} else if (!this._isNativeLabel) this._disposers.push(effect(() => {
|
|
69
|
+
this._for[0]();
|
|
70
|
+
const target = this._resolveTarget();
|
|
71
|
+
if (target && target.getRootNode() === this.getRootNode()) target.setAttribute("aria-labelledby", this.id);
|
|
72
|
+
}));
|
|
73
|
+
if (!this._isNativeLabel) {
|
|
74
|
+
this.addEventListener("mousedown", this._onMousedown);
|
|
75
|
+
this.addEventListener("click", this._onClick);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
disconnectedCallback() {
|
|
79
|
+
this.removeEventListener("mousedown", this._onMousedown);
|
|
80
|
+
this.removeEventListener("click", this._onClick);
|
|
81
|
+
for (const d of this._disposers) d();
|
|
82
|
+
this._disposers = [];
|
|
83
|
+
}
|
|
84
|
+
attributeChangedCallback(name, _old, value) {
|
|
85
|
+
if (name === "for") this._for[1](value);
|
|
86
|
+
}
|
|
87
|
+
/** Resolve the labelled target NOW (per interaction — never cached). */
|
|
88
|
+
_resolveTarget() {
|
|
89
|
+
const root = this.getRootNode();
|
|
90
|
+
const forId = this._for[0]();
|
|
91
|
+
if (forId) return root.getElementById(forId) ?? null;
|
|
92
|
+
if (this._fc) {
|
|
93
|
+
const id = this._fc.controlId();
|
|
94
|
+
if (id) return root.getElementById(id) ?? null;
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
_onMousedown = (ev) => {
|
|
99
|
+
if (ev.detail > 1) ev.preventDefault();
|
|
100
|
+
};
|
|
101
|
+
_onClick = (ev) => {
|
|
102
|
+
const target = this._resolveTarget();
|
|
103
|
+
if (!target) return;
|
|
104
|
+
const origin = ev.target instanceof Element ? ev.target : null;
|
|
105
|
+
const interactive = origin?.closest("button,input,select,textarea,a") ?? null;
|
|
106
|
+
if (interactive !== null && interactive !== target) return;
|
|
107
|
+
if (origin !== null && (origin === target || target.contains(origin))) return;
|
|
108
|
+
if (target.getAttribute("aria-disabled") === "true" || target.disabled === true) return;
|
|
109
|
+
const tag = target.tagName;
|
|
110
|
+
const inputType = tag === "INPUT" ? target.type : null;
|
|
111
|
+
if (inputType === "checkbox" || inputType === "radio") {
|
|
112
|
+
target.click();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") {
|
|
116
|
+
target.focus();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const role = target.getAttribute("role");
|
|
120
|
+
if (role === "checkbox" || role === "switch" || role === "radio") target.click();
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
let _defined = false;
|
|
124
|
+
/** Register `<aihu-label>` (idempotent). */
|
|
125
|
+
function defineLabel(tag = "aihu-label") {
|
|
126
|
+
if (_defined || customElements.get(tag)) {
|
|
127
|
+
_defined = true;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
customElements.define(tag, AihuLabel);
|
|
131
|
+
_defined = true;
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
export { AihuLabel, defineLabel };
|
|
135
|
+
|
|
136
|
+
//# sourceMappingURL=label.js.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { DomContext } from "./dom-context.js";
|
|
2
|
+
import { AihuRovingFocus } from "./roving-focus.js";
|
|
3
|
+
import { Read } from "@aihu/signals";
|
|
4
|
+
|
|
5
|
+
//#region src/radio-group/index.d.ts
|
|
6
|
+
interface RadioGroupContextValue {
|
|
7
|
+
/** Currently selected item value; null when nothing is selected. */
|
|
8
|
+
readonly value: Read<string | null>;
|
|
9
|
+
/** Group-effective disabled (own attribute ∥ inherited form-control). */
|
|
10
|
+
readonly disabled: Read<boolean>;
|
|
11
|
+
readonly required: Read<boolean>;
|
|
12
|
+
/** Programmatic write: signal + reflected `value` attribute. NO event. */
|
|
13
|
+
setValue(next: string | null): void;
|
|
14
|
+
/** USER-driven selection of `item` (click path): moves the tab stop to the
|
|
15
|
+
* item WITHOUT stealing focus, selects its value, emits `value-change`. */
|
|
16
|
+
selectItem(item: Element): void;
|
|
17
|
+
}
|
|
18
|
+
declare const radioGroupContext: DomContext<RadioGroupContextValue>;
|
|
19
|
+
/** Per-item context so `<aihu-radio-group-indicator>` mirrors its OWN item
|
|
20
|
+
* (nearest-provider-wins walk lands on the enclosing item, not the root). */
|
|
21
|
+
interface RadioGroupItemContextValue {
|
|
22
|
+
readonly checked: Read<boolean>;
|
|
23
|
+
readonly disabled: Read<boolean>;
|
|
24
|
+
}
|
|
25
|
+
declare const radioGroupItemContext: DomContext<RadioGroupItemContextValue>;
|
|
26
|
+
declare class AihuRadioGroupRoot extends AihuRovingFocus {
|
|
27
|
+
static readonly observedAttributes: string[];
|
|
28
|
+
private readonly _value;
|
|
29
|
+
private readonly _name;
|
|
30
|
+
private readonly _groupDisabled;
|
|
31
|
+
private readonly _required;
|
|
32
|
+
private _fc;
|
|
33
|
+
private _rgDisposers;
|
|
34
|
+
private _defaultSeeded;
|
|
35
|
+
constructor();
|
|
36
|
+
get value(): Read<string | null>;
|
|
37
|
+
/** Programmatic write: signal + reflected `value` attribute (two-way,
|
|
38
|
+
* dialog open-attr pattern). Does NOT emit `value-change`. */
|
|
39
|
+
setValue(next: string | null): void;
|
|
40
|
+
/** Move the roving current index. With `focus = true` (the base keyboard
|
|
41
|
+
* path — arrows/Home/End) this ALSO selects the landed item's value (APG:
|
|
42
|
+
* moving focus in a radio group selects). `focus = false` moves the tab
|
|
43
|
+
* stop silently and never selects. */
|
|
44
|
+
setCurrent(index: number, focus?: boolean): void;
|
|
45
|
+
connectedCallback(): void;
|
|
46
|
+
disconnectedCallback(): void;
|
|
47
|
+
attributeChangedCallback(name: string, old: string | null, value: string | null): void;
|
|
48
|
+
private _effectiveDisabled;
|
|
49
|
+
private _effectiveRequired;
|
|
50
|
+
private _syncOwnAttrs;
|
|
51
|
+
private _selectItem;
|
|
52
|
+
private _userSelect;
|
|
53
|
+
}
|
|
54
|
+
declare class AihuRadioGroupItem extends HTMLElement {
|
|
55
|
+
static readonly observedAttributes: string[];
|
|
56
|
+
private readonly _itemValue;
|
|
57
|
+
private readonly _disabled;
|
|
58
|
+
private _ctx;
|
|
59
|
+
private _collection;
|
|
60
|
+
private _unregister;
|
|
61
|
+
private _disposers;
|
|
62
|
+
constructor();
|
|
63
|
+
connectedCallback(): void;
|
|
64
|
+
disconnectedCallback(): void;
|
|
65
|
+
attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
|
|
66
|
+
private _checked;
|
|
67
|
+
private _effectiveDisabled;
|
|
68
|
+
private readonly _onKeydown;
|
|
69
|
+
private readonly _onClickCapture;
|
|
70
|
+
}
|
|
71
|
+
/** Presentational styling hook: mirrors its enclosing ITEM's state (nearest
|
|
72
|
+
* `radioGroupItemContext` provider), hidden from AT. */
|
|
73
|
+
declare class AihuRadioGroupIndicator extends HTMLElement {
|
|
74
|
+
private _disposers;
|
|
75
|
+
connectedCallback(): void;
|
|
76
|
+
disconnectedCallback(): void;
|
|
77
|
+
}
|
|
78
|
+
/** Register all radio-group custom elements (idempotent). */
|
|
79
|
+
declare function defineRadioGroup(): void;
|
|
80
|
+
//#endregion
|
|
81
|
+
export { AihuRadioGroupIndicator, AihuRadioGroupItem, AihuRadioGroupRoot, RadioGroupContextValue, RadioGroupItemContextValue, defineRadioGroup, radioGroupContext, radioGroupItemContext };
|
|
82
|
+
//# sourceMappingURL=radio-group.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"radio-group.d.ts","names":[],"sources":["../src/radio-group/index.ts"],"mappings":";;;;;UAgCiB,sBAAA;EAUJ;EAAA,SARF,KAAA,EAAO,IAAA;EAQQ;EAAA,SANf,QAAA,EAAU,IAAA;EAAA,SACV,QAAA,EAAU,IAAA;;EAEnB,QAAA,CAAS,IAAA;EAMmB;AAI9B;EAPE,UAAA,CAAW,IAAA,EAAM,OAAA;AAAA;AAAA,cAGN,iBAAA,EAAiB,UAAA,CAAA,sBAAA;;;UAIb,0BAAA;EAAA,SACN,OAAA,EAAS,IAAA;EAAA,SACT,QAAA,EAAU,IAAA;AAAA;AAAA,cAGR,qBAAA,EAAqB,UAAA,CAAA,0BAAA;AAAA,cAGrB,kBAAA,SAA2B,eAAA;EAAA,gBACb,kBAAA;EAAA,iBASR,MAAA;EAAA,iBACA,KAAA;EAAA,iBACA,cAAA;EAAA,iBACA,SAAA;EAAA,QACT,GAAA;EAAA,QACA,YAAA;EAAA,QACA,cAAA;;MAaJ,KAAA,CAAA,GAAS,IAAA;EAlBI;;EAwBjB,QAAA,CAAS,IAAA;EArBD;;;;EAgCC,UAAA,CAAW,KAAA,UAAe,KAAA;EAS1B,iBAAA,CAAA;EA0EA,oBAAA,CAAA;EAMA,wBAAA,CAAyB,IAAA,UAAc,GAAA,iBAAoB,KAAA;EAAA,QAuB5D,kBAAA;EAAA,QAKA,kBAAA;EAAA,QAKA,aAAA;EAAA,QAMA,WAAA;EAAA,QASA,WAAA;AAAA;AAAA,cAcG,kBAAA,SAA2B,WAAA;EAAA,gBACtB,kBAAA;EAAA,iBAEC,UAAA;EAAA,iBACA,SAAA;EAAA,QACT,IAAA;EAAA,QACA,WAAA;EAAA,QACA,WAAA;EAAA,QACA,UAAA;;EAUR,iBAAA,CAAA;EAkDA,oBAAA,CAAA;EASA,wBAAA,CAAyB,IAAA,UAAc,IAAA,iBAAqB,KAAA;EAAA,QAKpD,QAAA;EAAA,QAKA,kBAAA;EAAA,iBAKS,UAAA;EAAA,iBAcA,eAAA;AAAA;;;cAYN,uBAAA,SAAgC,WAAA;EAAA,QACnC,UAAA;EAER,iBAAA,CAAA;EAYA,oBAAA,CAAA;AAAA;;iBAcc,gBAAA,CAAA"}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { createDomContext, injectValue, provideContext } from "./dom-context.js";
|
|
2
|
+
import { i as attachHiddenInput, r as formControlContext } from "./form-control-B_BO9j7_.js";
|
|
3
|
+
import { collectionContext } from "./collection.js";
|
|
4
|
+
import { AihuRovingFocus } from "./roving-focus.js";
|
|
5
|
+
import { effect, signal, untrack } from "@aihu/signals";
|
|
6
|
+
//#region src/radio-group/index.ts
|
|
7
|
+
/**
|
|
8
|
+
* Headless radio group — `<aihu-radio-group-root>` (state owner, EXTENDS
|
|
9
|
+
* `AihuRovingFocus`) + `<aihu-radio-group-item>` (role="radio") +
|
|
10
|
+
* presentational `<aihu-radio-group-indicator>`. Implements the WAI-ARIA APG
|
|
11
|
+
* **Radio Group** pattern: `role="radiogroup"` on the root, roving tabindex
|
|
12
|
+
* across the items (exactly one `tabindex="0"`), arrow keys move focus AND
|
|
13
|
+
* select (orientation defaults to `"both"`, loop defaults ON for the APG
|
|
14
|
+
* wrap), Space selects an unchecked item, Enter does NOT activate. Emits NO
|
|
15
|
+
* CSS — each piece reflects `data-state="checked" | "unchecked"`
|
|
16
|
+
* (+ `data-disabled` presence) for the consumer to style.
|
|
17
|
+
*
|
|
18
|
+
* Form participation rides `attachHiddenInput` (ONE root-owned
|
|
19
|
+
* visually-hidden native radio in the root's light DOM): submits
|
|
20
|
+
* `name → value` only when a selection exists — no selection submits nothing
|
|
21
|
+
* (native parity).
|
|
22
|
+
*
|
|
23
|
+
* `value-change` (detail `{ value: string }`, bubbles, composed) is dispatched
|
|
24
|
+
* on USER-driven selection only (clicks/keys via the roving `setCurrent`
|
|
25
|
+
* path) — programmatic `setValue()` / attribute writes do not emit (checkbox
|
|
26
|
+
* precedent).
|
|
27
|
+
*/
|
|
28
|
+
const radioGroupContext = createDomContext("radio-group");
|
|
29
|
+
const radioGroupItemContext = createDomContext("radio-group-item");
|
|
30
|
+
var AihuRadioGroupRoot = class extends AihuRovingFocus {
|
|
31
|
+
static observedAttributes = [
|
|
32
|
+
...AihuRovingFocus.observedAttributes,
|
|
33
|
+
"value",
|
|
34
|
+
"default-value",
|
|
35
|
+
"name",
|
|
36
|
+
"disabled",
|
|
37
|
+
"required"
|
|
38
|
+
];
|
|
39
|
+
_value = signal(null);
|
|
40
|
+
_name = signal(null);
|
|
41
|
+
_groupDisabled = signal(false);
|
|
42
|
+
_required = signal(false);
|
|
43
|
+
_fc = null;
|
|
44
|
+
_rgDisposers = [];
|
|
45
|
+
_defaultSeeded = false;
|
|
46
|
+
constructor() {
|
|
47
|
+
super();
|
|
48
|
+
provideContext(this, radioGroupContext, {
|
|
49
|
+
value: this._value[0],
|
|
50
|
+
disabled: () => this._effectiveDisabled(),
|
|
51
|
+
required: () => this._effectiveRequired(),
|
|
52
|
+
setValue: (next) => this.setValue(next),
|
|
53
|
+
selectItem: (item) => this._selectItem(item)
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
get value() {
|
|
57
|
+
return this._value[0];
|
|
58
|
+
}
|
|
59
|
+
/** Programmatic write: signal + reflected `value` attribute (two-way,
|
|
60
|
+
* dialog open-attr pattern). Does NOT emit `value-change`. */
|
|
61
|
+
setValue(next) {
|
|
62
|
+
if (next === this._value[0]()) return;
|
|
63
|
+
this._value[1](next);
|
|
64
|
+
if (next === null) this.removeAttribute("value");
|
|
65
|
+
else this.setAttribute("value", next);
|
|
66
|
+
}
|
|
67
|
+
/** Move the roving current index. With `focus = true` (the base keyboard
|
|
68
|
+
* path — arrows/Home/End) this ALSO selects the landed item's value (APG:
|
|
69
|
+
* moving focus in a radio group selects). `focus = false` moves the tab
|
|
70
|
+
* stop silently and never selects. */
|
|
71
|
+
setCurrent(index, focus = true) {
|
|
72
|
+
super.setCurrent(index, focus);
|
|
73
|
+
if (!focus) return;
|
|
74
|
+
const v = this.items()[this.currentIndex()]?.getAttribute("value") ?? null;
|
|
75
|
+
if (v === null) return;
|
|
76
|
+
this._userSelect(v);
|
|
77
|
+
}
|
|
78
|
+
connectedCallback() {
|
|
79
|
+
if (!this.hasAttribute("orientation")) this.setAttribute("orientation", "both");
|
|
80
|
+
if (!this.hasAttribute("loop")) this.setAttribute("loop", "");
|
|
81
|
+
if (this.hasAttribute("value")) this._value[1](this.getAttribute("value"));
|
|
82
|
+
else if (!this._defaultSeeded && this.hasAttribute("default-value")) this._value[1](this.getAttribute("default-value"));
|
|
83
|
+
this._defaultSeeded = true;
|
|
84
|
+
this._syncOwnAttrs();
|
|
85
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "radiogroup");
|
|
86
|
+
this.setAttribute("data-fc-control", "");
|
|
87
|
+
try {
|
|
88
|
+
this._fc = injectValue(this, formControlContext);
|
|
89
|
+
} catch {
|
|
90
|
+
this._fc = null;
|
|
91
|
+
}
|
|
92
|
+
super.connectedCallback();
|
|
93
|
+
this._rgDisposers.push(effect(() => {
|
|
94
|
+
if (this._effectiveRequired()) this.setAttribute("aria-required", "true");
|
|
95
|
+
else this.removeAttribute("aria-required");
|
|
96
|
+
if (this._effectiveDisabled()) {
|
|
97
|
+
this.setAttribute("aria-disabled", "true");
|
|
98
|
+
this.setAttribute("data-disabled", "");
|
|
99
|
+
} else {
|
|
100
|
+
this.removeAttribute("aria-disabled");
|
|
101
|
+
this.removeAttribute("data-disabled");
|
|
102
|
+
}
|
|
103
|
+
}), effect(() => {
|
|
104
|
+
const items = this.items();
|
|
105
|
+
const v = this._value[0]();
|
|
106
|
+
if (v === null) return;
|
|
107
|
+
const idx = items.findIndex((el) => el.getAttribute("value") === v);
|
|
108
|
+
if (idx < 0) return;
|
|
109
|
+
untrack(() => {
|
|
110
|
+
if (idx !== this.currentIndex()) this.setCurrent(idx, false);
|
|
111
|
+
});
|
|
112
|
+
}), attachHiddenInput(this, {
|
|
113
|
+
type: "radio",
|
|
114
|
+
name: this._name[0],
|
|
115
|
+
value: () => this._value[0]() ?? "",
|
|
116
|
+
checked: () => this._value[0]() !== null,
|
|
117
|
+
required: () => this._effectiveRequired(),
|
|
118
|
+
disabled: () => this._effectiveDisabled()
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
disconnectedCallback() {
|
|
122
|
+
super.disconnectedCallback();
|
|
123
|
+
for (const d of this._rgDisposers) d();
|
|
124
|
+
this._rgDisposers = [];
|
|
125
|
+
}
|
|
126
|
+
attributeChangedCallback(name, old, value) {
|
|
127
|
+
switch (name) {
|
|
128
|
+
case "value":
|
|
129
|
+
this._value[1](value);
|
|
130
|
+
break;
|
|
131
|
+
case "name":
|
|
132
|
+
this._name[1](value);
|
|
133
|
+
break;
|
|
134
|
+
case "disabled":
|
|
135
|
+
this._groupDisabled[1](value !== null);
|
|
136
|
+
break;
|
|
137
|
+
case "required":
|
|
138
|
+
this._required[1](value !== null);
|
|
139
|
+
break;
|
|
140
|
+
case "default-value": break;
|
|
141
|
+
default: super.attributeChangedCallback(name, old, value);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
_effectiveDisabled() {
|
|
145
|
+
if (this._groupDisabled[0]()) return true;
|
|
146
|
+
return this._fc ? this._fc.disabled() : false;
|
|
147
|
+
}
|
|
148
|
+
_effectiveRequired() {
|
|
149
|
+
if (this._required[0]()) return true;
|
|
150
|
+
return this._fc ? this._fc.required() : false;
|
|
151
|
+
}
|
|
152
|
+
_syncOwnAttrs() {
|
|
153
|
+
this._groupDisabled[1](this.hasAttribute("disabled"));
|
|
154
|
+
this._required[1](this.hasAttribute("required"));
|
|
155
|
+
this._name[1](this.getAttribute("name"));
|
|
156
|
+
}
|
|
157
|
+
_selectItem(item) {
|
|
158
|
+
if (this._effectiveDisabled()) return;
|
|
159
|
+
const idx = this.items().indexOf(item);
|
|
160
|
+
if (idx >= 0) this.setCurrent(idx, false);
|
|
161
|
+
const v = item.getAttribute("value");
|
|
162
|
+
if (v !== null) this._userSelect(v);
|
|
163
|
+
}
|
|
164
|
+
_userSelect(v) {
|
|
165
|
+
if (this._effectiveDisabled()) return;
|
|
166
|
+
if (v === this._value[0]()) return;
|
|
167
|
+
this.setValue(v);
|
|
168
|
+
this.dispatchEvent(new CustomEvent("value-change", {
|
|
169
|
+
detail: { value: v },
|
|
170
|
+
bubbles: true,
|
|
171
|
+
composed: true
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
var AihuRadioGroupItem = class extends HTMLElement {
|
|
176
|
+
static observedAttributes = ["value", "disabled"];
|
|
177
|
+
_itemValue = signal(null);
|
|
178
|
+
_disabled = signal(false);
|
|
179
|
+
_ctx = null;
|
|
180
|
+
_collection = null;
|
|
181
|
+
_unregister = null;
|
|
182
|
+
_disposers = [];
|
|
183
|
+
constructor() {
|
|
184
|
+
super();
|
|
185
|
+
provideContext(this, radioGroupItemContext, {
|
|
186
|
+
checked: () => this._checked(),
|
|
187
|
+
disabled: () => this._effectiveDisabled()
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
connectedCallback() {
|
|
191
|
+
this._itemValue[1](this.getAttribute("value"));
|
|
192
|
+
this._disabled[1](this.hasAttribute("disabled"));
|
|
193
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "radio");
|
|
194
|
+
try {
|
|
195
|
+
this._ctx = injectValue(this, radioGroupContext);
|
|
196
|
+
} catch {
|
|
197
|
+
this._ctx = null;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
this._collection = injectValue(this, collectionContext);
|
|
201
|
+
} catch {
|
|
202
|
+
this._collection = null;
|
|
203
|
+
}
|
|
204
|
+
this.addEventListener("keydown", this._onKeydown);
|
|
205
|
+
this.addEventListener("click", this._onClickCapture, true);
|
|
206
|
+
this._disposers.push(effect(() => {
|
|
207
|
+
const checked = this._checked();
|
|
208
|
+
this.setAttribute("aria-checked", checked ? "true" : "false");
|
|
209
|
+
this.setAttribute("data-state", checked ? "checked" : "unchecked");
|
|
210
|
+
if (this._effectiveDisabled()) {
|
|
211
|
+
this.setAttribute("aria-disabled", "true");
|
|
212
|
+
this.setAttribute("data-disabled", "");
|
|
213
|
+
} else {
|
|
214
|
+
this.removeAttribute("aria-disabled");
|
|
215
|
+
this.removeAttribute("data-disabled");
|
|
216
|
+
}
|
|
217
|
+
}), effect(() => {
|
|
218
|
+
if (this._effectiveDisabled()) {
|
|
219
|
+
this._unregister?.();
|
|
220
|
+
this._unregister = null;
|
|
221
|
+
this.setAttribute("tabindex", "-1");
|
|
222
|
+
} else if (this._unregister === null && this._collection !== null) this._unregister = this._collection.register(this);
|
|
223
|
+
}));
|
|
224
|
+
}
|
|
225
|
+
disconnectedCallback() {
|
|
226
|
+
this.removeEventListener("keydown", this._onKeydown);
|
|
227
|
+
this.removeEventListener("click", this._onClickCapture, true);
|
|
228
|
+
this._unregister?.();
|
|
229
|
+
this._unregister = null;
|
|
230
|
+
for (const d of this._disposers) d();
|
|
231
|
+
this._disposers = [];
|
|
232
|
+
}
|
|
233
|
+
attributeChangedCallback(name, _old, value) {
|
|
234
|
+
if (name === "value") this._itemValue[1](value);
|
|
235
|
+
if (name === "disabled") this._disabled[1](value !== null);
|
|
236
|
+
}
|
|
237
|
+
_checked() {
|
|
238
|
+
const v = this._itemValue[0]();
|
|
239
|
+
return v !== null && this._ctx !== null && this._ctx.value() === v;
|
|
240
|
+
}
|
|
241
|
+
_effectiveDisabled() {
|
|
242
|
+
if (this._disabled[0]()) return true;
|
|
243
|
+
return this._ctx ? this._ctx.disabled() : false;
|
|
244
|
+
}
|
|
245
|
+
_onKeydown = (ev) => {
|
|
246
|
+
if (ev.key === "Enter") {
|
|
247
|
+
ev.preventDefault();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (ev.key === " " || ev.key === "Spacebar") {
|
|
251
|
+
ev.preventDefault();
|
|
252
|
+
if (this._effectiveDisabled()) return;
|
|
253
|
+
if (!this._checked()) this.click();
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
_onClickCapture = (ev) => {
|
|
257
|
+
if (this._effectiveDisabled()) {
|
|
258
|
+
ev.preventDefault();
|
|
259
|
+
ev.stopImmediatePropagation();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
this._ctx?.selectItem(this);
|
|
263
|
+
};
|
|
264
|
+
};
|
|
265
|
+
/** Presentational styling hook: mirrors its enclosing ITEM's state (nearest
|
|
266
|
+
* `radioGroupItemContext` provider), hidden from AT. */
|
|
267
|
+
var AihuRadioGroupIndicator = class extends HTMLElement {
|
|
268
|
+
_disposers = [];
|
|
269
|
+
connectedCallback() {
|
|
270
|
+
const ctx = injectValue(this, radioGroupItemContext);
|
|
271
|
+
this.setAttribute("aria-hidden", "true");
|
|
272
|
+
this._disposers.push(effect(() => {
|
|
273
|
+
this.setAttribute("data-state", ctx.checked() ? "checked" : "unchecked");
|
|
274
|
+
if (ctx.disabled()) this.setAttribute("data-disabled", "");
|
|
275
|
+
else this.removeAttribute("data-disabled");
|
|
276
|
+
}));
|
|
277
|
+
}
|
|
278
|
+
disconnectedCallback() {
|
|
279
|
+
for (const d of this._disposers) d();
|
|
280
|
+
this._disposers = [];
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
const REGISTRY = [
|
|
284
|
+
["aihu-radio-group-root", AihuRadioGroupRoot],
|
|
285
|
+
["aihu-radio-group-item", AihuRadioGroupItem],
|
|
286
|
+
["aihu-radio-group-indicator", AihuRadioGroupIndicator]
|
|
287
|
+
];
|
|
288
|
+
let _defined = false;
|
|
289
|
+
/** Register all radio-group custom elements (idempotent). */
|
|
290
|
+
function defineRadioGroup() {
|
|
291
|
+
if (_defined) return;
|
|
292
|
+
for (const [tag, ctor] of REGISTRY) if (!customElements.get(tag)) customElements.define(tag, ctor);
|
|
293
|
+
_defined = true;
|
|
294
|
+
}
|
|
295
|
+
//#endregion
|
|
296
|
+
export { AihuRadioGroupIndicator, AihuRadioGroupItem, AihuRadioGroupRoot, defineRadioGroup, radioGroupContext, radioGroupItemContext };
|
|
297
|
+
|
|
298
|
+
//# sourceMappingURL=radio-group.js.map
|