@aihu/primitives 0.0.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/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/button-C-8c-A17.js +126 -0
- package/dist/button-C-8c-A17.js.map +1 -0
- package/dist/button.d.ts +2 -0
- package/dist/button.js +2 -0
- package/dist/collection.d.ts +26 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +72 -0
- package/dist/collection.js.map +1 -0
- package/dist/config-provider.d.ts +39 -0
- package/dist/config-provider.d.ts.map +1 -0
- package/dist/config-provider.js +112 -0
- package/dist/config-provider.js.map +1 -0
- package/dist/dialog-y7MHc6vf.js +253 -0
- package/dist/dialog-y7MHc6vf.js.map +1 -0
- package/dist/dialog.d.ts +2 -0
- package/dist/dialog.js +2 -0
- package/dist/dom-context.d.ts +40 -0
- package/dist/dom-context.d.ts.map +1 -0
- package/dist/dom-context.js +64 -0
- package/dist/dom-context.js.map +1 -0
- package/dist/form-control.d.ts +41 -0
- package/dist/form-control.d.ts.map +1 -0
- package/dist/form-control.js +145 -0
- package/dist/form-control.js.map +1 -0
- package/dist/index-D9kf9rVU.d.ts +86 -0
- package/dist/index-D9kf9rVU.d.ts.map +1 -0
- package/dist/index-DPD4L6Nj.d.ts +31 -0
- package/dist/index-DPD4L6Nj.d.ts.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/presence-gate.d.ts +33 -0
- package/dist/presence-gate.d.ts.map +1 -0
- package/dist/presence-gate.js +109 -0
- package/dist/presence-gate.js.map +1 -0
- package/dist/roving-focus.d.ts +33 -0
- package/dist/roving-focus.d.ts.map +1 -0
- package/dist/roving-focus.js +159 -0
- package/dist/roving-focus.js.map +1 -0
- package/dist/tooltip.d.ts +72 -0
- package/dist/tooltip.d.ts.map +1 -0
- package/dist/tooltip.js +195 -0
- package/dist/tooltip.js.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { createDomContext, injectValue, provideContext } from "./dom-context.js";
|
|
2
|
+
import { effect, signal } from "@aihu/signals";
|
|
3
|
+
//#region src/dialog/focus-trap.ts
|
|
4
|
+
/**
|
|
5
|
+
* `createFocusTrap` — a tiny native-DOM focus trap (no library). Queries the
|
|
6
|
+
* focusable descendants of a container, wraps Tab at the edges, moves focus to
|
|
7
|
+
* the first focusable (or the container) on activate, and restores focus to the
|
|
8
|
+
* previously-active element on deactivate. Used by `dialog-content`.
|
|
9
|
+
*/
|
|
10
|
+
const FOCUSABLE = "a[href], area[href], button:not([disabled]), input:not([disabled]):not([type=\"hidden\"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"]), [contenteditable=\"true\"], audio[controls], video[controls], details>summary:first-of-type";
|
|
11
|
+
function focusables(container) {
|
|
12
|
+
return Array.from(container.querySelectorAll(FOCUSABLE)).filter((el) => el.offsetParent !== null || el === document.activeElement || isInJsdom(el));
|
|
13
|
+
}
|
|
14
|
+
/** jsdom does not lay out, so `offsetParent` is always null — treat all as visible there. */
|
|
15
|
+
function isInJsdom(_el) {
|
|
16
|
+
return typeof navigator !== "undefined" && navigator.userAgent.includes("jsdom");
|
|
17
|
+
}
|
|
18
|
+
function createFocusTrap(container) {
|
|
19
|
+
let previouslyFocused = null;
|
|
20
|
+
let active = false;
|
|
21
|
+
const onKeydown = (ev) => {
|
|
22
|
+
if (!active || ev.key !== "Tab") return;
|
|
23
|
+
const items = focusables(container);
|
|
24
|
+
const first = items[0];
|
|
25
|
+
const last = items[items.length - 1];
|
|
26
|
+
if (!first || !last) {
|
|
27
|
+
ev.preventDefault();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const current = container.getRootNode().activeElement;
|
|
31
|
+
if (ev.shiftKey && (current === first || !container.contains(current))) {
|
|
32
|
+
ev.preventDefault();
|
|
33
|
+
last.focus();
|
|
34
|
+
} else if (!ev.shiftKey && (current === last || !container.contains(current))) {
|
|
35
|
+
ev.preventDefault();
|
|
36
|
+
first.focus();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
activate() {
|
|
41
|
+
if (active) return;
|
|
42
|
+
active = true;
|
|
43
|
+
previouslyFocused = document.activeElement;
|
|
44
|
+
const items = focusables(container);
|
|
45
|
+
const target = items[0] ?? container;
|
|
46
|
+
if (!items.length && !container.hasAttribute("tabindex")) container.setAttribute("tabindex", "-1");
|
|
47
|
+
target.focus();
|
|
48
|
+
document.addEventListener("keydown", onKeydown, true);
|
|
49
|
+
},
|
|
50
|
+
deactivate() {
|
|
51
|
+
if (!active) return;
|
|
52
|
+
active = false;
|
|
53
|
+
document.removeEventListener("keydown", onKeydown, true);
|
|
54
|
+
previouslyFocused?.focus?.();
|
|
55
|
+
previouslyFocused = null;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/dialog/index.ts
|
|
61
|
+
/**
|
|
62
|
+
* Headless dialog — `<aihu-dialog-root>` (state owner) + pieces
|
|
63
|
+
* `<aihu-dialog-trigger>`, `<aihu-dialog-content>`, `<aihu-dialog-backdrop>`,
|
|
64
|
+
* `<aihu-dialog-close>`, `<aihu-dialog-title>`, `<aihu-dialog-description>`.
|
|
65
|
+
* Implements the WAI-ARIA APG **Dialog (Modal)** pattern: focus-trap +
|
|
66
|
+
* return-focus, Escape-to-close, `role=dialog` / `aria-modal` /
|
|
67
|
+
* `aria-labelledby` / `aria-describedby`, trigger `aria-haspopup` /
|
|
68
|
+
* `aria-expanded` / `aria-controls`. Emits NO CSS — every piece reflects
|
|
69
|
+
* `data-state="open"|"closed"` for the consumer to style.
|
|
70
|
+
*/
|
|
71
|
+
const dialogContext = createDomContext("dialog");
|
|
72
|
+
let _idSeq = 0;
|
|
73
|
+
const uid = (p) => `aihu-${p}-${_idSeq += 1}`;
|
|
74
|
+
var AihuDialogRoot = class extends HTMLElement {
|
|
75
|
+
static observedAttributes = ["open", "modal"];
|
|
76
|
+
_open = signal(false);
|
|
77
|
+
_modal = signal(true);
|
|
78
|
+
_contentId = signal(uid("dialog"));
|
|
79
|
+
_titleId = signal(null);
|
|
80
|
+
_descriptionId = signal(null);
|
|
81
|
+
_disposers = [];
|
|
82
|
+
_ctx;
|
|
83
|
+
constructor() {
|
|
84
|
+
super();
|
|
85
|
+
this._ctx = {
|
|
86
|
+
open: this._open[0],
|
|
87
|
+
modal: this._modal[0],
|
|
88
|
+
contentId: this._contentId[0],
|
|
89
|
+
titleId: this._titleId[0],
|
|
90
|
+
descriptionId: this._descriptionId[0],
|
|
91
|
+
setTitleId: (id) => this._titleId[1](id),
|
|
92
|
+
setDescriptionId: (id) => this._descriptionId[1](id),
|
|
93
|
+
setOpen: (next) => this.setOpen(next),
|
|
94
|
+
close: () => this.setOpen(false),
|
|
95
|
+
toggle: () => this.setOpen(!this._open[0]())
|
|
96
|
+
};
|
|
97
|
+
provideContext(this, dialogContext, this._ctx);
|
|
98
|
+
}
|
|
99
|
+
get open() {
|
|
100
|
+
return this._open[0];
|
|
101
|
+
}
|
|
102
|
+
setOpen(next) {
|
|
103
|
+
if (next === this._open[0]()) return;
|
|
104
|
+
this._open[1](next);
|
|
105
|
+
if (next) this.setAttribute("open", "");
|
|
106
|
+
else this.removeAttribute("open");
|
|
107
|
+
}
|
|
108
|
+
connectedCallback() {
|
|
109
|
+
if (this.hasAttribute("modal")) this._modal[1](this.getAttribute("modal") !== "false");
|
|
110
|
+
this._open[1](this.hasAttribute("open"));
|
|
111
|
+
this._disposers.push(effect(() => {
|
|
112
|
+
this.setAttribute("data-state", this._open[0]() ? "open" : "closed");
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
disconnectedCallback() {
|
|
116
|
+
for (const d of this._disposers) d();
|
|
117
|
+
this._disposers = [];
|
|
118
|
+
}
|
|
119
|
+
attributeChangedCallback(name, _old, value) {
|
|
120
|
+
if (name === "open") this._open[1](value !== null);
|
|
121
|
+
if (name === "modal") this._modal[1](value !== "false");
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
/** Base for pieces that inject the dialog context lazily on connect. */
|
|
125
|
+
var DialogPiece = class extends HTMLElement {
|
|
126
|
+
ctx;
|
|
127
|
+
disposers = [];
|
|
128
|
+
connectedCallback() {
|
|
129
|
+
this.ctx = injectValue(this, dialogContext);
|
|
130
|
+
this.onConnect();
|
|
131
|
+
}
|
|
132
|
+
disconnectedCallback() {
|
|
133
|
+
for (const d of this.disposers) d();
|
|
134
|
+
this.disposers = [];
|
|
135
|
+
}
|
|
136
|
+
reflectState() {
|
|
137
|
+
this.disposers.push(effect(() => {
|
|
138
|
+
this.setAttribute("data-state", this.ctx.open() ? "open" : "closed");
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
var AihuDialogTrigger = class extends DialogPiece {
|
|
143
|
+
onConnect() {
|
|
144
|
+
if (this.tagName !== "BUTTON") {
|
|
145
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "button");
|
|
146
|
+
if (!this.hasAttribute("tabindex")) this.setAttribute("tabindex", "0");
|
|
147
|
+
}
|
|
148
|
+
this.setAttribute("aria-haspopup", "dialog");
|
|
149
|
+
this.addEventListener("click", this._onClick);
|
|
150
|
+
this.disposers.push(effect(() => {
|
|
151
|
+
const open = this.ctx.open();
|
|
152
|
+
this.setAttribute("aria-expanded", String(open));
|
|
153
|
+
this.setAttribute("aria-controls", this.ctx.contentId());
|
|
154
|
+
}));
|
|
155
|
+
this.reflectState();
|
|
156
|
+
}
|
|
157
|
+
_onClick = () => {
|
|
158
|
+
this.ctx.toggle();
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
var AihuDialogContent = class extends DialogPiece {
|
|
162
|
+
_trap = null;
|
|
163
|
+
onConnect() {
|
|
164
|
+
this.setAttribute("role", this.getAttribute("role") ?? "dialog");
|
|
165
|
+
this.addEventListener("keydown", this._onKeydown);
|
|
166
|
+
this.disposers.push(effect(() => {
|
|
167
|
+
const open = this.ctx.open();
|
|
168
|
+
this.id = this.ctx.contentId();
|
|
169
|
+
if (this.ctx.modal()) this.setAttribute("aria-modal", "true");
|
|
170
|
+
const titleId = this.ctx.titleId();
|
|
171
|
+
if (titleId) this.setAttribute("aria-labelledby", titleId);
|
|
172
|
+
const descId = this.ctx.descriptionId();
|
|
173
|
+
if (descId) this.setAttribute("aria-describedby", descId);
|
|
174
|
+
this.setAttribute("data-state", open ? "open" : "closed");
|
|
175
|
+
if (open) this._activateTrap();
|
|
176
|
+
else this._deactivateTrap();
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
disconnectedCallback() {
|
|
180
|
+
this._deactivateTrap();
|
|
181
|
+
this.removeEventListener("keydown", this._onKeydown);
|
|
182
|
+
super.disconnectedCallback();
|
|
183
|
+
}
|
|
184
|
+
_activateTrap() {
|
|
185
|
+
if (this._trap) return;
|
|
186
|
+
this._trap = createFocusTrap(this);
|
|
187
|
+
this._trap.activate();
|
|
188
|
+
}
|
|
189
|
+
_deactivateTrap() {
|
|
190
|
+
this._trap?.deactivate();
|
|
191
|
+
this._trap = null;
|
|
192
|
+
}
|
|
193
|
+
_onKeydown = (ev) => {
|
|
194
|
+
if (ev.key === "Escape" && this.getAttribute("data-dismissable-escape") !== "false") {
|
|
195
|
+
ev.stopPropagation();
|
|
196
|
+
this.ctx.close();
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
var AihuDialogBackdrop = class extends DialogPiece {
|
|
201
|
+
onConnect() {
|
|
202
|
+
this.addEventListener("click", this._onClick);
|
|
203
|
+
this.reflectState();
|
|
204
|
+
}
|
|
205
|
+
_onClick = () => {
|
|
206
|
+
if (this.ctx.modal() && this.getAttribute("data-dismissable-outside") !== "false") this.ctx.close();
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
var AihuDialogClose = class extends DialogPiece {
|
|
210
|
+
onConnect() {
|
|
211
|
+
if (this.tagName !== "BUTTON") {
|
|
212
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "button");
|
|
213
|
+
if (!this.hasAttribute("tabindex")) this.setAttribute("tabindex", "0");
|
|
214
|
+
}
|
|
215
|
+
this.addEventListener("click", this._onClick);
|
|
216
|
+
this.reflectState();
|
|
217
|
+
}
|
|
218
|
+
_onClick = () => {
|
|
219
|
+
this.ctx.close();
|
|
220
|
+
};
|
|
221
|
+
};
|
|
222
|
+
var AihuDialogTitle = class extends DialogPiece {
|
|
223
|
+
onConnect() {
|
|
224
|
+
if (!this.id) this.id = uid("dialog-title");
|
|
225
|
+
this.ctx.setTitleId(this.id);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
var AihuDialogDescription = class extends DialogPiece {
|
|
229
|
+
onConnect() {
|
|
230
|
+
if (!this.id) this.id = uid("dialog-desc");
|
|
231
|
+
this.ctx.setDescriptionId(this.id);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const REGISTRY = [
|
|
235
|
+
["aihu-dialog-root", AihuDialogRoot],
|
|
236
|
+
["aihu-dialog-trigger", AihuDialogTrigger],
|
|
237
|
+
["aihu-dialog-content", AihuDialogContent],
|
|
238
|
+
["aihu-dialog-backdrop", AihuDialogBackdrop],
|
|
239
|
+
["aihu-dialog-close", AihuDialogClose],
|
|
240
|
+
["aihu-dialog-title", AihuDialogTitle],
|
|
241
|
+
["aihu-dialog-description", AihuDialogDescription]
|
|
242
|
+
];
|
|
243
|
+
let _defined = false;
|
|
244
|
+
/** Register all dialog custom elements (idempotent). */
|
|
245
|
+
function defineDialog() {
|
|
246
|
+
if (_defined) return;
|
|
247
|
+
for (const [tag, ctor] of REGISTRY) if (!customElements.get(tag)) customElements.define(tag, ctor);
|
|
248
|
+
_defined = true;
|
|
249
|
+
}
|
|
250
|
+
//#endregion
|
|
251
|
+
export { AihuDialogRoot as a, defineDialog as c, AihuDialogDescription as i, dialogContext as l, AihuDialogClose as n, AihuDialogTitle as o, AihuDialogContent as r, AihuDialogTrigger as s, AihuDialogBackdrop as t, createFocusTrap as u };
|
|
252
|
+
|
|
253
|
+
//# sourceMappingURL=dialog-y7MHc6vf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialog-y7MHc6vf.js","names":[],"sources":["../src/dialog/focus-trap.ts","../src/dialog/index.ts"],"sourcesContent":["/**\n * `createFocusTrap` — a tiny native-DOM focus trap (no library). Queries the\n * focusable descendants of a container, wraps Tab at the edges, moves focus to\n * the first focusable (or the container) on activate, and restores focus to the\n * previously-active element on deactivate. Used by `dialog-content`.\n */\n\nconst FOCUSABLE =\n 'a[href], area[href], button:not([disabled]), input:not([disabled]):not([type=\"hidden\"]), ' +\n 'select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"]), ' +\n '[contenteditable=\"true\"], audio[controls], video[controls], details>summary:first-of-type'\n\nexport interface FocusTrap {\n activate(): void\n deactivate(): void\n}\n\nfunction focusables(container: Element): HTMLElement[] {\n return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\n (el) => el.offsetParent !== null || el === document.activeElement || isInJsdom(el),\n )\n}\n\n/** jsdom does not lay out, so `offsetParent` is always null — treat all as visible there. */\nfunction isInJsdom(_el: HTMLElement): boolean {\n return typeof navigator !== 'undefined' && navigator.userAgent.includes('jsdom')\n}\n\nexport function createFocusTrap(container: Element): FocusTrap {\n let previouslyFocused: HTMLElement | null = null\n let active = false\n\n const onKeydown = (ev: KeyboardEvent): void => {\n if (!active || ev.key !== 'Tab') return\n const items = focusables(container)\n const first = items[0]\n const last = items[items.length - 1]\n if (!first || !last) {\n ev.preventDefault()\n return\n }\n const current = (container.getRootNode() as Document | ShadowRoot).activeElement as HTMLElement\n\n if (ev.shiftKey && (current === first || !container.contains(current))) {\n ev.preventDefault()\n last.focus()\n } else if (!ev.shiftKey && (current === last || !container.contains(current))) {\n ev.preventDefault()\n first.focus()\n }\n }\n\n return {\n activate(): void {\n if (active) return\n active = true\n previouslyFocused = document.activeElement as HTMLElement | null\n const items = focusables(container)\n const target = items[0] ?? (container as HTMLElement)\n // Ensure the container itself is focusable as a fallback.\n if (!items.length && !(container as HTMLElement).hasAttribute('tabindex')) {\n ;(container as HTMLElement).setAttribute('tabindex', '-1')\n }\n target.focus()\n document.addEventListener('keydown', onKeydown, true)\n },\n deactivate(): void {\n if (!active) return\n active = false\n document.removeEventListener('keydown', onKeydown, true)\n // Return focus to whatever opened the trap.\n previouslyFocused?.focus?.()\n previouslyFocused = null\n },\n }\n}\n","/**\n * Headless dialog — `<aihu-dialog-root>` (state owner) + pieces\n * `<aihu-dialog-trigger>`, `<aihu-dialog-content>`, `<aihu-dialog-backdrop>`,\n * `<aihu-dialog-close>`, `<aihu-dialog-title>`, `<aihu-dialog-description>`.\n * Implements the WAI-ARIA APG **Dialog (Modal)** pattern: focus-trap +\n * return-focus, Escape-to-close, `role=dialog` / `aria-modal` /\n * `aria-labelledby` / `aria-describedby`, trigger `aria-haspopup` /\n * `aria-expanded` / `aria-controls`. Emits NO CSS — every piece reflects\n * `data-state=\"open\"|\"closed\"` for the consumer to style.\n */\n\nimport { effect, type Read, signal } from '@aihu/signals'\nimport { createDomContext, injectValue, provideContext } from '../dom-context.ts'\nimport { createFocusTrap, type FocusTrap } from './focus-trap.ts'\n\nexport interface DialogContextValue {\n readonly open: Read<boolean>\n readonly modal: Read<boolean>\n readonly contentId: Read<string>\n readonly titleId: Read<string | null>\n readonly descriptionId: Read<string | null>\n setTitleId(id: string): void\n setDescriptionId(id: string): void\n setOpen(next: boolean): void\n close(): void\n toggle(): void\n}\n\nexport const dialogContext = createDomContext<DialogContextValue>('dialog')\n\nlet _idSeq = 0\nconst uid = (p: string): string => `aihu-${p}-${(_idSeq += 1)}`\n\nexport class AihuDialogRoot extends HTMLElement {\n static readonly observedAttributes = ['open', 'modal']\n\n private readonly _open = signal(false)\n private readonly _modal = signal(true)\n private readonly _contentId = signal(uid('dialog'))\n private readonly _titleId = signal<string | null>(null)\n private readonly _descriptionId = signal<string | null>(null)\n private _disposers: Array<() => void> = []\n private _ctx: DialogContextValue\n\n constructor() {\n super()\n this._ctx = {\n open: this._open[0],\n modal: this._modal[0],\n contentId: this._contentId[0],\n titleId: this._titleId[0],\n descriptionId: this._descriptionId[0],\n setTitleId: (id) => this._titleId[1](id),\n setDescriptionId: (id) => this._descriptionId[1](id),\n setOpen: (next) => this.setOpen(next),\n close: () => this.setOpen(false),\n toggle: () => this.setOpen(!this._open[0]()),\n }\n provideContext(this, dialogContext, this._ctx)\n }\n\n get open(): Read<boolean> {\n return this._open[0]\n }\n\n setOpen(next: boolean): void {\n if (next === this._open[0]()) return\n this._open[1](next)\n if (next) this.setAttribute('open', '')\n else this.removeAttribute('open')\n }\n\n connectedCallback(): void {\n if (this.hasAttribute('modal')) this._modal[1](this.getAttribute('modal') !== 'false')\n this._open[1](this.hasAttribute('open'))\n this._disposers.push(\n effect(() => {\n this.setAttribute('data-state', this._open[0]() ? 'open' : 'closed')\n }),\n )\n }\n\n disconnectedCallback(): void {\n for (const d of this._disposers) d()\n this._disposers = []\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (name === 'open') this._open[1](value !== null)\n if (name === 'modal') this._modal[1](value !== 'false')\n }\n}\n\n/** Base for pieces that inject the dialog context lazily on connect. */\nabstract class DialogPiece extends HTMLElement {\n protected ctx!: DialogContextValue\n protected disposers: Array<() => void> = []\n\n connectedCallback(): void {\n this.ctx = injectValue(this, dialogContext)\n this.onConnect()\n }\n\n disconnectedCallback(): void {\n for (const d of this.disposers) d()\n this.disposers = []\n }\n\n protected reflectState(): void {\n this.disposers.push(\n effect(() => {\n this.setAttribute('data-state', this.ctx.open() ? 'open' : 'closed')\n }),\n )\n }\n\n protected abstract onConnect(): void\n}\n\nexport class AihuDialogTrigger extends DialogPiece {\n protected onConnect(): void {\n if (this.tagName !== 'BUTTON') {\n if (!this.hasAttribute('role')) this.setAttribute('role', 'button')\n if (!this.hasAttribute('tabindex')) this.setAttribute('tabindex', '0')\n }\n this.setAttribute('aria-haspopup', 'dialog')\n this.addEventListener('click', this._onClick)\n this.disposers.push(\n effect(() => {\n const open = this.ctx.open()\n this.setAttribute('aria-expanded', String(open))\n this.setAttribute('aria-controls', this.ctx.contentId())\n }),\n )\n this.reflectState()\n }\n\n private readonly _onClick = (): void => {\n this.ctx.toggle()\n }\n}\n\nexport class AihuDialogContent extends DialogPiece {\n private _trap: FocusTrap | null = null\n\n protected onConnect(): void {\n this.setAttribute('role', this.getAttribute('role') ?? 'dialog')\n this.addEventListener('keydown', this._onKeydown)\n this.disposers.push(\n effect(() => {\n const open = this.ctx.open()\n this.id = this.ctx.contentId()\n if (this.ctx.modal()) this.setAttribute('aria-modal', 'true')\n const titleId = this.ctx.titleId()\n if (titleId) this.setAttribute('aria-labelledby', titleId)\n const descId = this.ctx.descriptionId()\n if (descId) this.setAttribute('aria-describedby', descId)\n this.setAttribute('data-state', open ? 'open' : 'closed')\n if (open) this._activateTrap()\n else this._deactivateTrap()\n }),\n )\n }\n\n override disconnectedCallback(): void {\n this._deactivateTrap()\n this.removeEventListener('keydown', this._onKeydown)\n super.disconnectedCallback()\n }\n\n private _activateTrap(): void {\n if (this._trap) return\n this._trap = createFocusTrap(this)\n this._trap.activate()\n }\n\n private _deactivateTrap(): void {\n this._trap?.deactivate()\n this._trap = null\n }\n\n private readonly _onKeydown = (ev: KeyboardEvent): void => {\n if (ev.key === 'Escape' && this.getAttribute('data-dismissable-escape') !== 'false') {\n ev.stopPropagation()\n this.ctx.close()\n }\n }\n}\n\nexport class AihuDialogBackdrop extends DialogPiece {\n protected onConnect(): void {\n this.addEventListener('click', this._onClick)\n this.reflectState()\n }\n\n private readonly _onClick = (): void => {\n if (this.ctx.modal() && this.getAttribute('data-dismissable-outside') !== 'false') {\n this.ctx.close()\n }\n }\n}\n\nexport class AihuDialogClose extends DialogPiece {\n protected onConnect(): void {\n if (this.tagName !== 'BUTTON') {\n if (!this.hasAttribute('role')) this.setAttribute('role', 'button')\n if (!this.hasAttribute('tabindex')) this.setAttribute('tabindex', '0')\n }\n this.addEventListener('click', this._onClick)\n this.reflectState()\n }\n\n private readonly _onClick = (): void => {\n this.ctx.close()\n }\n}\n\nexport class AihuDialogTitle extends DialogPiece {\n protected onConnect(): void {\n if (!this.id) this.id = uid('dialog-title')\n this.ctx.setTitleId(this.id)\n }\n}\n\nexport class AihuDialogDescription extends DialogPiece {\n protected onConnect(): void {\n if (!this.id) this.id = uid('dialog-desc')\n this.ctx.setDescriptionId(this.id)\n }\n}\n\nconst REGISTRY: Array<[string, CustomElementConstructor]> = [\n ['aihu-dialog-root', AihuDialogRoot],\n ['aihu-dialog-trigger', AihuDialogTrigger],\n ['aihu-dialog-content', AihuDialogContent],\n ['aihu-dialog-backdrop', AihuDialogBackdrop],\n ['aihu-dialog-close', AihuDialogClose],\n ['aihu-dialog-title', AihuDialogTitle],\n ['aihu-dialog-description', AihuDialogDescription],\n]\n\nlet _defined = false\n/** Register all dialog custom elements (idempotent). */\nexport function defineDialog(): 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\nexport { createFocusTrap, type FocusTrap } from './focus-trap.ts'\n"],"mappings":"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"}
|
package/dist/dialog.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as AihuDialogRoot, c as DialogContextValue, d as FocusTrap, f as createFocusTrap, i as AihuDialogDescription, l as defineDialog, n as AihuDialogClose, o as AihuDialogTitle, r as AihuDialogContent, s as AihuDialogTrigger, t as AihuDialogBackdrop, u as dialogContext } from "./index-D9kf9rVU.js";
|
|
2
|
+
export { AihuDialogBackdrop, AihuDialogClose, AihuDialogContent, AihuDialogDescription, AihuDialogRoot, AihuDialogTitle, AihuDialogTrigger, DialogContextValue, FocusTrap, createFocusTrap, defineDialog, dialogContext };
|
package/dist/dialog.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as AihuDialogRoot, c as defineDialog, i as AihuDialogDescription, l as dialogContext, n as AihuDialogClose, o as AihuDialogTitle, r as AihuDialogContent, s as AihuDialogTrigger, t as AihuDialogBackdrop, u as createFocusTrap } from "./dialog-y7MHc6vf.js";
|
|
2
|
+
export { AihuDialogBackdrop, AihuDialogClose, AihuDialogContent, AihuDialogDescription, AihuDialogRoot, AihuDialogTitle, AihuDialogTrigger, createFocusTrap, defineDialog, dialogContext };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Read } from "@aihu/signals";
|
|
2
|
+
|
|
3
|
+
//#region src/dom-context.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Opaque token identifying a context slot. `_key` is the Symbol used in the
|
|
6
|
+
* per-host registry; `_name` aids debugging + the throw-on-missing message.
|
|
7
|
+
*/
|
|
8
|
+
interface DomContext<T> {
|
|
9
|
+
readonly _key: symbol;
|
|
10
|
+
readonly _name: string;
|
|
11
|
+
readonly _default: T | undefined;
|
|
12
|
+
}
|
|
13
|
+
/** Thrown by `injectContext` when no provider is found and the token has no default. */
|
|
14
|
+
declare class MissingContextError extends Error {
|
|
15
|
+
constructor(name: string);
|
|
16
|
+
}
|
|
17
|
+
/** Create a context token. Optionally carry a default for `injectContext`. */
|
|
18
|
+
declare function createDomContext<T>(name: string, defaultValue?: T): DomContext<T>;
|
|
19
|
+
/**
|
|
20
|
+
* Provide `value` for `ctx` on `host`. Idempotent per `(host, ctx._key)` —
|
|
21
|
+
* re-providing overwrites. `value` may be a raw `T` or a signal `Read<T>`.
|
|
22
|
+
*/
|
|
23
|
+
declare function provideContext<T>(host: Element, ctx: DomContext<T>, value: T | Read<T>): void;
|
|
24
|
+
/**
|
|
25
|
+
* Resolve `ctx` for `from` by walking UP the DOM from `from.parentNode`,
|
|
26
|
+
* crossing `ShadowRoot` boundaries (`ShadowRoot → .host`), nearest-wins.
|
|
27
|
+
* Returns the first match; falls back to `ctx._default`; throws
|
|
28
|
+
* `MissingContextError` if neither is present.
|
|
29
|
+
*/
|
|
30
|
+
declare function injectContext<T>(from: Element, ctx: DomContext<T>): T | Read<T>;
|
|
31
|
+
/**
|
|
32
|
+
* Like `injectContext`, but for the common case where a context carries a raw
|
|
33
|
+
* (non-signal) value — typically an object whose fields are themselves signals.
|
|
34
|
+
* Narrows the `T | Read<T>` union to `T` so call sites read fields directly.
|
|
35
|
+
* Use `injectContext` when a provider may store a bare `Read<T>` signal.
|
|
36
|
+
*/
|
|
37
|
+
declare function injectValue<T>(from: Element, ctx: DomContext<T>): T;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { DomContext, MissingContextError, createDomContext, injectContext, injectValue, provideContext };
|
|
40
|
+
//# sourceMappingURL=dom-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dom-context.d.ts","names":[],"sources":["../src/dom-context.ts"],"mappings":";;;;AAoDA;;;UAxBiB,UAAA;EAAA,SACN,IAAA;EAAA,SACA,KAAA;EAAA,SACA,QAAA,EAAU,CAAA;AAAA;;cAIR,mBAAA,SAA4B,KAAA;cAC3B,IAAA;AAAA;;iBAgBE,gBAAA,GAAA,CAAoB,IAAA,UAAc,YAAA,GAAe,CAAA,GAAI,UAAA,CAAW,CAAA;;;AAQhF;;iBAAgB,cAAA,GAAA,CAAkB,IAAA,EAAM,OAAA,EAAS,GAAA,EAAK,UAAA,CAAW,CAAA,GAAI,KAAA,EAAO,CAAA,GAAI,IAAA,CAAK,CAAA;;;;;;;iBAerE,aAAA,GAAA,CAAiB,IAAA,EAAM,OAAA,EAAS,GAAA,EAAK,UAAA,CAAW,CAAA,IAAK,CAAA,GAAI,IAAA,CAAK,CAAA;;;;;;;iBA2B9D,WAAA,GAAA,CAAe,IAAA,EAAM,OAAA,EAAS,GAAA,EAAK,UAAA,CAAW,CAAA,IAAK,CAAA"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
//#region src/dom-context.ts
|
|
2
|
+
/** Thrown by `injectContext` when no provider is found and the token has no default. */
|
|
3
|
+
var MissingContextError = class extends Error {
|
|
4
|
+
constructor(name) {
|
|
5
|
+
super(`[@aihu/primitives] no provider found for context "${name}" (and it has no default value)`);
|
|
6
|
+
this.name = "MissingContextError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Module-level registry mapping a host element to its provided context values.
|
|
11
|
+
* A `WeakMap` so entries auto-release when the host element is GC'd — no manual
|
|
12
|
+
* teardown, and values can be non-serializable (e.g. signals).
|
|
13
|
+
*/
|
|
14
|
+
const REGISTRY = /* @__PURE__ */ new WeakMap();
|
|
15
|
+
/** Create a context token. Optionally carry a default for `injectContext`. */
|
|
16
|
+
function createDomContext(name, defaultValue) {
|
|
17
|
+
return {
|
|
18
|
+
_key: Symbol(name),
|
|
19
|
+
_name: name,
|
|
20
|
+
_default: defaultValue
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Provide `value` for `ctx` on `host`. Idempotent per `(host, ctx._key)` —
|
|
25
|
+
* re-providing overwrites. `value` may be a raw `T` or a signal `Read<T>`.
|
|
26
|
+
*/
|
|
27
|
+
function provideContext(host, ctx, value) {
|
|
28
|
+
let slots = REGISTRY.get(host);
|
|
29
|
+
if (slots === void 0) {
|
|
30
|
+
slots = /* @__PURE__ */ new Map();
|
|
31
|
+
REGISTRY.set(host, slots);
|
|
32
|
+
}
|
|
33
|
+
slots.set(ctx._key, value);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve `ctx` for `from` by walking UP the DOM from `from.parentNode`,
|
|
37
|
+
* crossing `ShadowRoot` boundaries (`ShadowRoot → .host`), nearest-wins.
|
|
38
|
+
* Returns the first match; falls back to `ctx._default`; throws
|
|
39
|
+
* `MissingContextError` if neither is present.
|
|
40
|
+
*/
|
|
41
|
+
function injectContext(from, ctx) {
|
|
42
|
+
let node = from.parentNode;
|
|
43
|
+
while (node !== null) if (node instanceof Element) {
|
|
44
|
+
const slots = REGISTRY.get(node);
|
|
45
|
+
if (slots?.has(ctx._key)) return slots.get(ctx._key);
|
|
46
|
+
node = node.parentNode;
|
|
47
|
+
} else if (node instanceof ShadowRoot) node = node.host;
|
|
48
|
+
else node = node.parentNode;
|
|
49
|
+
if (ctx._default !== void 0) return ctx._default;
|
|
50
|
+
throw new MissingContextError(ctx._name);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Like `injectContext`, but for the common case where a context carries a raw
|
|
54
|
+
* (non-signal) value — typically an object whose fields are themselves signals.
|
|
55
|
+
* Narrows the `T | Read<T>` union to `T` so call sites read fields directly.
|
|
56
|
+
* Use `injectContext` when a provider may store a bare `Read<T>` signal.
|
|
57
|
+
*/
|
|
58
|
+
function injectValue(from, ctx) {
|
|
59
|
+
return injectContext(from, ctx);
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { MissingContextError, createDomContext, injectContext, injectValue, provideContext };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=dom-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dom-context.js","names":[],"sources":["../src/dom-context.ts"],"sourcesContent":["/**\n * `@aihu/primitives/context` — self-contained DOM-walk context (Option C).\n *\n * A live ancestor-traversal context: a provider stamps a value onto a host\n * element; an injecting descendant resolves it by walking UP the real DOM tree\n * (crossing `ShadowRoot` boundaries), nearest-provider-wins. This is the\n * Radix/Ark root↔piece coordination mechanism — fundamentally different from\n * `@aihu/context`'s DOM-free, SSR-oriented module-level Map.\n *\n * Deliberately distinct vocabulary (`createDomContext` / `provideContext` /\n * `injectContext`, NOT `createContext`) so there is no colliding symbol with\n * `@aihu/context`. **This module does NOT import `@aihu/context`.**\n *\n * Signal composition: the stored value may be a raw `T` or a signal `Read<T>`.\n * The convention is \"provide a `Read<T>` when you want descendants to react\" —\n * a consuming primitive reads the injected `Read<T>` inside an `effect(...)`\n * so it re-runs when the provider updates (e.g. `config-provider` propagating\n * `colorScheme` to every descendant). The util itself is signal-agnostic: it\n * stores whatever is passed and only imports the `Read<T>` *type* — keeping\n * `dom-context.js` under its 1 KB row.\n */\n\nimport type { Read } from '@aihu/signals'\n\n/**\n * Opaque token identifying a context slot. `_key` is the Symbol used in the\n * per-host registry; `_name` aids debugging + the throw-on-missing message.\n */\nexport interface DomContext<T> {\n readonly _key: symbol\n readonly _name: string\n readonly _default: T | undefined\n}\n\n/** Thrown by `injectContext` when no provider is found and the token has no default. */\nexport class MissingContextError extends Error {\n constructor(name: string) {\n super(\n `[@aihu/primitives] no provider found for context \"${name}\" (and it has no default value)`,\n )\n this.name = 'MissingContextError'\n }\n}\n\n/**\n * Module-level registry mapping a host element to its provided context values.\n * A `WeakMap` so entries auto-release when the host element is GC'd — no manual\n * teardown, and values can be non-serializable (e.g. signals).\n */\nconst REGISTRY: WeakMap<Element, Map<symbol, unknown>> = new WeakMap()\n\n/** Create a context token. Optionally carry a default for `injectContext`. */\nexport function createDomContext<T>(name: string, defaultValue?: T): DomContext<T> {\n return { _key: Symbol(name), _name: name, _default: defaultValue }\n}\n\n/**\n * Provide `value` for `ctx` on `host`. Idempotent per `(host, ctx._key)` —\n * re-providing overwrites. `value` may be a raw `T` or a signal `Read<T>`.\n */\nexport function provideContext<T>(host: Element, ctx: DomContext<T>, value: T | Read<T>): void {\n let slots = REGISTRY.get(host)\n if (slots === undefined) {\n slots = new Map<symbol, unknown>()\n REGISTRY.set(host, slots)\n }\n slots.set(ctx._key, value)\n}\n\n/**\n * Resolve `ctx` for `from` by walking UP the DOM from `from.parentNode`,\n * crossing `ShadowRoot` boundaries (`ShadowRoot → .host`), nearest-wins.\n * Returns the first match; falls back to `ctx._default`; throws\n * `MissingContextError` if neither is present.\n */\nexport function injectContext<T>(from: Element, ctx: DomContext<T>): T | Read<T> {\n let node: Node | null = from.parentNode\n while (node !== null) {\n if (node instanceof Element) {\n const slots = REGISTRY.get(node)\n if (slots?.has(ctx._key)) {\n return slots.get(ctx._key) as T | Read<T>\n }\n node = node.parentNode\n } else if (node instanceof ShadowRoot) {\n // Step out of the shadow tree to the host element, then continue up.\n node = node.host\n } else {\n // Document / DocumentFragment (non-shadow) — keep walking up.\n node = node.parentNode\n }\n }\n if (ctx._default !== undefined) return ctx._default\n throw new MissingContextError(ctx._name)\n}\n\n/**\n * Like `injectContext`, but for the common case where a context carries a raw\n * (non-signal) value — typically an object whose fields are themselves signals.\n * Narrows the `T | Read<T>` union to `T` so call sites read fields directly.\n * Use `injectContext` when a provider may store a bare `Read<T>` signal.\n */\nexport function injectValue<T>(from: Element, ctx: DomContext<T>): T {\n return injectContext(from, ctx) as T\n}\n"],"mappings":";;AAmCA,IAAa,sBAAb,cAAyC,MAAM;CAC7C,YAAY,MAAc;EACxB,MACE,qDAAqD,KAAK,iCAC3D;EACD,KAAK,OAAO;;;;;;;;AAShB,MAAM,2BAAmD,IAAI,SAAS;;AAGtE,SAAgB,iBAAoB,MAAc,cAAiC;CACjF,OAAO;EAAE,MAAM,OAAO,KAAK;EAAE,OAAO;EAAM,UAAU;EAAc;;;;;;AAOpE,SAAgB,eAAkB,MAAe,KAAoB,OAA0B;CAC7F,IAAI,QAAQ,SAAS,IAAI,KAAK;CAC9B,IAAI,UAAU,KAAA,GAAW;EACvB,wBAAQ,IAAI,KAAsB;EAClC,SAAS,IAAI,MAAM,MAAM;;CAE3B,MAAM,IAAI,IAAI,MAAM,MAAM;;;;;;;;AAS5B,SAAgB,cAAiB,MAAe,KAAiC;CAC/E,IAAI,OAAoB,KAAK;CAC7B,OAAO,SAAS,MACd,IAAI,gBAAgB,SAAS;EAC3B,MAAM,QAAQ,SAAS,IAAI,KAAK;EAChC,IAAI,OAAO,IAAI,IAAI,KAAK,EACtB,OAAO,MAAM,IAAI,IAAI,KAAK;EAE5B,OAAO,KAAK;QACP,IAAI,gBAAgB,YAEzB,OAAO,KAAK;MAGZ,OAAO,KAAK;CAGhB,IAAI,IAAI,aAAa,KAAA,GAAW,OAAO,IAAI;CAC3C,MAAM,IAAI,oBAAoB,IAAI,MAAM;;;;;;;;AAS1C,SAAgB,YAAe,MAAe,KAAuB;CACnE,OAAO,cAAc,MAAM,IAAI"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { DomContext } from "./dom-context.js";
|
|
2
|
+
import { Read } from "@aihu/signals";
|
|
3
|
+
|
|
4
|
+
//#region src/form-control/index.d.ts
|
|
5
|
+
interface FormControlContextValue {
|
|
6
|
+
readonly disabled: Read<boolean>;
|
|
7
|
+
readonly required: Read<boolean>;
|
|
8
|
+
readonly invalid: Read<boolean>;
|
|
9
|
+
readonly controlId: Read<string>;
|
|
10
|
+
readonly describedById: Read<string | null>;
|
|
11
|
+
}
|
|
12
|
+
declare const formControlContext: DomContext<FormControlContextValue>;
|
|
13
|
+
declare class AihuFormControl extends HTMLElement {
|
|
14
|
+
static readonly observedAttributes: string[];
|
|
15
|
+
private readonly _disabled;
|
|
16
|
+
private readonly _required;
|
|
17
|
+
private readonly _invalid;
|
|
18
|
+
private readonly _controlId;
|
|
19
|
+
private readonly _describedById;
|
|
20
|
+
private _disposers;
|
|
21
|
+
get disabled(): Read<boolean>;
|
|
22
|
+
get required(): Read<boolean>;
|
|
23
|
+
get invalid(): Read<boolean>;
|
|
24
|
+
get controlId(): Read<string>;
|
|
25
|
+
constructor();
|
|
26
|
+
connectedCallback(): void;
|
|
27
|
+
disconnectedCallback(): void;
|
|
28
|
+
attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
|
|
29
|
+
/** Re-derive `aria-describedby` from the slotted description/error pieces.
|
|
30
|
+
* Call after a piece mounts/unmounts. */
|
|
31
|
+
recomputeDescribedBy(): void;
|
|
32
|
+
private _syncBooleans;
|
|
33
|
+
/** The slotted control element (input/select/textarea/[data-fc-control]). */
|
|
34
|
+
private _control;
|
|
35
|
+
private _wireAssociations;
|
|
36
|
+
}
|
|
37
|
+
/** Register `<aihu-form-control>` (idempotent). */
|
|
38
|
+
declare function defineFormControl(tag?: string): void;
|
|
39
|
+
//#endregion
|
|
40
|
+
export { AihuFormControl, FormControlContextValue, defineFormControl, formControlContext };
|
|
41
|
+
//# sourceMappingURL=form-control.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { createDomContext, provideContext } from "./dom-context.js";
|
|
2
|
+
import { effect, signal } from "@aihu/signals";
|
|
3
|
+
//#region src/form-control/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* `<aihu-form-control>` — headless form-field coordinator. Owns the
|
|
6
|
+
* disabled/required/invalid state for a field and wires the ARIA association
|
|
7
|
+
* between a slotted control, its label, and its error/description text. Emits
|
|
8
|
+
* NO CSS.
|
|
9
|
+
*
|
|
10
|
+
* Reflected attributes: `disabled`, `required`, `invalid` (boolean); `name`,
|
|
11
|
+
* `control-id` (string).
|
|
12
|
+
* Owned signals: `disabled`, `required`, `invalid`.
|
|
13
|
+
* ARIA emitted onto the slotted control: `aria-required`, `aria-invalid`,
|
|
14
|
+
* `aria-disabled`, `aria-describedby` (wired to a slotted `[data-fc-description]`
|
|
15
|
+
* / `[data-fc-error]` element's id); a slotted `<label>` is associated via
|
|
16
|
+
* `for`/`id`.
|
|
17
|
+
* Context: provides `formControlContext` carrying
|
|
18
|
+
* `{ disabled, required, invalid, controlId, describedById }` signals so
|
|
19
|
+
* descendant pieces consume the shared state without prop-drilling.
|
|
20
|
+
*/
|
|
21
|
+
const formControlContext = createDomContext("form-control");
|
|
22
|
+
let _idCounter = 0;
|
|
23
|
+
function nextId() {
|
|
24
|
+
_idCounter += 1;
|
|
25
|
+
return `aihu-fc-${_idCounter}`;
|
|
26
|
+
}
|
|
27
|
+
var AihuFormControl = class extends HTMLElement {
|
|
28
|
+
static observedAttributes = [
|
|
29
|
+
"disabled",
|
|
30
|
+
"required",
|
|
31
|
+
"invalid",
|
|
32
|
+
"name",
|
|
33
|
+
"control-id"
|
|
34
|
+
];
|
|
35
|
+
_disabled = signal(false);
|
|
36
|
+
_required = signal(false);
|
|
37
|
+
_invalid = signal(false);
|
|
38
|
+
_controlId = signal("");
|
|
39
|
+
_describedById = signal(null);
|
|
40
|
+
_disposers = [];
|
|
41
|
+
get disabled() {
|
|
42
|
+
return this._disabled[0];
|
|
43
|
+
}
|
|
44
|
+
get required() {
|
|
45
|
+
return this._required[0];
|
|
46
|
+
}
|
|
47
|
+
get invalid() {
|
|
48
|
+
return this._invalid[0];
|
|
49
|
+
}
|
|
50
|
+
get controlId() {
|
|
51
|
+
return this._controlId[0];
|
|
52
|
+
}
|
|
53
|
+
constructor() {
|
|
54
|
+
super();
|
|
55
|
+
provideContext(this, formControlContext, {
|
|
56
|
+
disabled: this._disabled[0],
|
|
57
|
+
required: this._required[0],
|
|
58
|
+
invalid: this._invalid[0],
|
|
59
|
+
controlId: this._controlId[0],
|
|
60
|
+
describedById: this._describedById[0]
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
connectedCallback() {
|
|
64
|
+
const supplied = this.getAttribute("control-id");
|
|
65
|
+
this._controlId[1](supplied ?? nextId());
|
|
66
|
+
this._syncBooleans();
|
|
67
|
+
this._wireAssociations();
|
|
68
|
+
this._disposers.push(effect(() => {
|
|
69
|
+
const control = this._control();
|
|
70
|
+
if (!control) return;
|
|
71
|
+
reflectAria(control, "aria-disabled", this._disabled[0]());
|
|
72
|
+
reflectAria(control, "aria-required", this._required[0]());
|
|
73
|
+
reflectAria(control, "aria-invalid", this._invalid[0]());
|
|
74
|
+
const describedBy = this._describedById[0]();
|
|
75
|
+
if (describedBy) control.setAttribute("aria-describedby", describedBy);
|
|
76
|
+
else control.removeAttribute("aria-describedby");
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
disconnectedCallback() {
|
|
80
|
+
for (const d of this._disposers) d();
|
|
81
|
+
this._disposers = [];
|
|
82
|
+
}
|
|
83
|
+
attributeChangedCallback(name, _old, value) {
|
|
84
|
+
switch (name) {
|
|
85
|
+
case "disabled":
|
|
86
|
+
this._disabled[1](value !== null);
|
|
87
|
+
break;
|
|
88
|
+
case "required":
|
|
89
|
+
this._required[1](value !== null);
|
|
90
|
+
break;
|
|
91
|
+
case "invalid":
|
|
92
|
+
this._invalid[1](value !== null);
|
|
93
|
+
break;
|
|
94
|
+
case "control-id":
|
|
95
|
+
if (value) this._controlId[1](value);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Re-derive `aria-describedby` from the slotted description/error pieces.
|
|
100
|
+
* Call after a piece mounts/unmounts. */
|
|
101
|
+
recomputeDescribedBy() {
|
|
102
|
+
this._wireAssociations();
|
|
103
|
+
}
|
|
104
|
+
_syncBooleans() {
|
|
105
|
+
this._disabled[1](this.hasAttribute("disabled"));
|
|
106
|
+
this._required[1](this.hasAttribute("required"));
|
|
107
|
+
this._invalid[1](this.hasAttribute("invalid"));
|
|
108
|
+
}
|
|
109
|
+
/** The slotted control element (input/select/textarea/[data-fc-control]). */
|
|
110
|
+
_control() {
|
|
111
|
+
return this.querySelector("[data-fc-control], input, select, textarea, [role=\"textbox\"]");
|
|
112
|
+
}
|
|
113
|
+
_wireAssociations() {
|
|
114
|
+
const control = this._control();
|
|
115
|
+
if (control) if (!control.id) control.id = this._controlId[0]();
|
|
116
|
+
else this._controlId[1](control.id);
|
|
117
|
+
const label = this.querySelector("label, [data-fc-label]");
|
|
118
|
+
if (label && control) if (label instanceof HTMLLabelElement) label.htmlFor = control.id;
|
|
119
|
+
else label.setAttribute("for", control.id);
|
|
120
|
+
const described = [];
|
|
121
|
+
for (const el of this.querySelectorAll("[data-fc-description], [data-fc-error]")) {
|
|
122
|
+
if (!el.id) el.id = nextId();
|
|
123
|
+
described.push(el.id);
|
|
124
|
+
}
|
|
125
|
+
this._describedById[1](described.length ? described.join(" ") : null);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
function reflectAria(el, attr, on) {
|
|
129
|
+
if (on) el.setAttribute(attr, "true");
|
|
130
|
+
else el.removeAttribute(attr);
|
|
131
|
+
}
|
|
132
|
+
let _defined = false;
|
|
133
|
+
/** Register `<aihu-form-control>` (idempotent). */
|
|
134
|
+
function defineFormControl(tag = "aihu-form-control") {
|
|
135
|
+
if (_defined || customElements.get(tag)) {
|
|
136
|
+
_defined = true;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
customElements.define(tag, AihuFormControl);
|
|
140
|
+
_defined = true;
|
|
141
|
+
}
|
|
142
|
+
//#endregion
|
|
143
|
+
export { AihuFormControl, defineFormControl, formControlContext };
|
|
144
|
+
|
|
145
|
+
//# sourceMappingURL=form-control.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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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"}
|