praxis-kit 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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -0
  3. package/dist/_shared/diagnostics.d.ts +312 -0
  4. package/dist/_shared/diagnostics.js +360 -0
  5. package/dist/build-runtime-CJ_nQEaZ.js +5065 -0
  6. package/dist/codemod/index.d.ts +2 -0
  7. package/dist/codemod/index.js +176520 -0
  8. package/dist/contract/index.d.ts +677 -0
  9. package/dist/contract/index.js +341 -0
  10. package/dist/eslint/index.d.ts +90 -0
  11. package/dist/eslint/index.js +1047 -0
  12. package/dist/guards/index.d.ts +78 -0
  13. package/dist/guards/index.js +118 -0
  14. package/dist/html/index.d.ts +151 -0
  15. package/dist/html/index.js +1244 -0
  16. package/dist/index-BIBd_iPD.d.ts +951 -0
  17. package/dist/lit/index.d.ts +862 -0
  18. package/dist/lit/index.js +4893 -0
  19. package/dist/preact/index.d.ts +796 -0
  20. package/dist/preact/index.js +5043 -0
  21. package/dist/react/index.d.ts +28 -0
  22. package/dist/react/index.js +205 -0
  23. package/dist/react/legacy.d.ts +29 -0
  24. package/dist/react/legacy.js +80 -0
  25. package/dist/solid/index.d.ts +728 -0
  26. package/dist/solid/index.js +4821 -0
  27. package/dist/svelte/Polymorphic.svelte +190 -0
  28. package/dist/svelte/_polymorphic-runtime.d.ts +102 -0
  29. package/dist/svelte/_polymorphic-runtime.js +371 -0
  30. package/dist/svelte/index.d.ts +994 -0
  31. package/dist/svelte/index.js +4482 -0
  32. package/dist/tailwind/index.d.ts +197 -0
  33. package/dist/tailwind/index.js +767 -0
  34. package/dist/tailwind/safelist.css +20 -0
  35. package/dist/ts-plugin/index.cjs +166 -0
  36. package/dist/ts-plugin/index.d.cts +9 -0
  37. package/dist/utils/index.d.ts +19 -0
  38. package/dist/utils/index.js +21 -0
  39. package/dist/vite-plugin/index.d.ts +200 -0
  40. package/dist/vite-plugin/index.js +2106 -0
  41. package/dist/vue/index.d.ts +729 -0
  42. package/dist/vue/index.js +4945 -0
  43. package/dist/web/index.d.ts +832 -0
  44. package/dist/web/index.js +4868 -0
  45. package/package.json +258 -0
@@ -0,0 +1,4821 @@
1
+ import { clsx } from "clsx";
2
+ import { DiagnosticCategory, DiagnosticCode, resolveDiagnostics, silentDiagnostics, throwDiagnostics, throwDiagnostics as throwDiagnostics$1, warnDiagnostics } from "../_shared/diagnostics.js";
3
+ import { cva } from "class-variance-authority";
4
+ import { createEffect, createMemo, mergeProps, onCleanup, splitProps } from "solid-js";
5
+ import { Dynamic, createComponent, mergeProps as mergeProps$1 } from "solid-js/web";
6
+ //#region ../../lib/adapter-utils/src/invariant.ts
7
+ function panic(message) {
8
+ throw new Error(message);
9
+ }
10
+ function invariant(condition, message) {
11
+ if (!condition) panic(message);
12
+ }
13
+ //#endregion
14
+ //#region ../../lib/adapter-utils/src/runtime/apply-display-name.ts
15
+ function applyDisplayName(component, name) {
16
+ Object.assign(component, { displayName: name ?? "PolymorphicComponent" });
17
+ }
18
+ //#endregion
19
+ //#region ../../lib/adapter-utils/src/runtime/define-component.ts
20
+ function defineContractComponent(options) {
21
+ return (factory) => factory(options);
22
+ }
23
+ //#endregion
24
+ //#region ../../lib/adapter-utils/src/runtime/assemble-compound-component.ts
25
+ /**
26
+ * Assembles a compound component by attaching sub-components to a
27
+ * generated root component, producing APIs such as `Card.Header`,
28
+ * `Card.Content`, and `Card.Footer`.
29
+ *
30
+ * Mutates and returns `root` in place, following the convention used by
31
+ * other component post-processing helpers.
32
+ */
33
+ function assembleCompoundComponent(root, subComponents) {
34
+ if (!subComponents) return root;
35
+ return Object.assign(root, subComponents);
36
+ }
37
+ //#endregion
38
+ //#region ../../lib/primitive/src/tag/resolve-tag.ts
39
+ function makeResolveTag(defaultTag) {
40
+ return function tag(as) {
41
+ return as ?? defaultTag;
42
+ };
43
+ }
44
+ //#endregion
45
+ //#region ../../lib/primitive/src/rule/rule-brand.ts
46
+ const RULE_BRAND = Symbol("praxis-kit/dynamic-rule");
47
+ //#endregion
48
+ //#region ../../lib/primitive/src/rule/dynamic.ts
49
+ function dynamic(resolve) {
50
+ return {
51
+ [RULE_BRAND]: true,
52
+ resolve
53
+ };
54
+ }
55
+ //#endregion
56
+ //#region ../../lib/foundation/src/iterate.ts
57
+ function find(iterable, callback) {
58
+ for (const value of iterable) {
59
+ const result = callback(value);
60
+ if (result != null) return result;
61
+ }
62
+ return null;
63
+ }
64
+ function some(iterable, predicate) {
65
+ for (const value of iterable) if (predicate(value)) return true;
66
+ return false;
67
+ }
68
+ function every(iterable, predicate) {
69
+ let index = 0;
70
+ for (const value of iterable) if (!predicate(value, index++)) return false;
71
+ return true;
72
+ }
73
+ function* filter(iterable, predicate) {
74
+ let index = 0;
75
+ for (const value of iterable) if (predicate(value, index++)) yield value;
76
+ }
77
+ function* map(iterable, callback) {
78
+ let index = 0;
79
+ for (const value of iterable) yield callback(value, index++);
80
+ }
81
+ function forEach(iterable, callback) {
82
+ let index = 0;
83
+ for (const value of iterable) callback(value, index++);
84
+ }
85
+ function reduce(iterable, initial, callback) {
86
+ let accumulator = initial;
87
+ let index = 0;
88
+ for (const value of iterable) accumulator = callback(accumulator, value, index++);
89
+ return accumulator;
90
+ }
91
+ /**
92
+ * Transforms an iterable into a Record.
93
+ *
94
+ * The callback returns a `[key, value]` tuple for each element. Returning
95
+ * `null` aborts the collection and causes `collect()` to return `null`.
96
+ */
97
+ function collect(iterable, callback) {
98
+ const result = {};
99
+ let index = 0;
100
+ for (const value of iterable) {
101
+ const entry = callback(value, index++);
102
+ if (entry === null) return null;
103
+ result[entry[0]] = entry[1];
104
+ }
105
+ return result;
106
+ }
107
+ function findLast(value, callback) {
108
+ for (let index = value.length - 1; index >= 0; index--) {
109
+ const result = callback(value[index], index);
110
+ if (result != null) return result;
111
+ }
112
+ return null;
113
+ }
114
+ function* items(collection) {
115
+ for (let i = 0; i < collection.length; i++) {
116
+ const item = collection.item(i);
117
+ if (item !== null) yield item;
118
+ }
119
+ }
120
+ function nodeList(list) {
121
+ return { *[Symbol.iterator]() {
122
+ for (let i = 0; i < list.length; i++) {
123
+ const node = list.item(i);
124
+ if (node !== null) yield node;
125
+ }
126
+ } };
127
+ }
128
+ function mapEntries(m) {
129
+ return m.entries();
130
+ }
131
+ function set(s) {
132
+ return s.values();
133
+ }
134
+ function hasOwn(object, key) {
135
+ return Object.hasOwn(object, key);
136
+ }
137
+ function* entries(object) {
138
+ for (const key in object) {
139
+ if (!hasOwn(object, key)) continue;
140
+ yield [key, object[key]];
141
+ }
142
+ }
143
+ function* keys(object) {
144
+ for (const [key] of entries(object)) yield key;
145
+ }
146
+ function* values(object) {
147
+ for (const [, value] of entries(object)) yield value;
148
+ }
149
+ function mapValues(object, callback) {
150
+ const result = {};
151
+ for (const [key, value] of entries(object)) result[key] = callback(value, key);
152
+ return result;
153
+ }
154
+ function forEachEntry(object, callback) {
155
+ for (const [key, value] of entries(object)) callback(key, value);
156
+ }
157
+ function forEachKey(object, callback) {
158
+ for (const key of keys(object)) callback(key);
159
+ }
160
+ function forEachValue(object, callback) {
161
+ for (const value of values(object)) callback(value);
162
+ }
163
+ function forEachSet(s, callback) {
164
+ for (const value of s) callback(value);
165
+ }
166
+ const iterate = Object.freeze({
167
+ entries,
168
+ filter,
169
+ find,
170
+ findLast,
171
+ forEach,
172
+ forEachEntry,
173
+ forEachKey,
174
+ forEachSet,
175
+ forEachValue,
176
+ items,
177
+ keys,
178
+ map,
179
+ mapEntries,
180
+ mapValues,
181
+ nodeList,
182
+ reduce,
183
+ collect,
184
+ set,
185
+ some,
186
+ every,
187
+ values
188
+ });
189
+ //#endregion
190
+ //#region ../../lib/foundation/src/assert-never.ts
191
+ function assertNever(value) {
192
+ throw new Error(`Unexpected value: ${String(value)}`);
193
+ }
194
+ //#endregion
195
+ //#region ../../lib/foundation/src/cn.ts
196
+ function cn(...inputs) {
197
+ return clsx(...inputs);
198
+ }
199
+ //#endregion
200
+ //#region ../../lib/foundation/src/lru-cache.ts
201
+ /**
202
+ * A bounded cache that evicts the least recently used entry once `maxSize` is
203
+ * exceeded. Backed by a single `Map`, relying on its insertion-order iteration:
204
+ * `get()` promotes a hit to most-recently-used by deleting and re-inserting the
205
+ * key (moving it to the tail), and `set()` evicts the head (oldest) key when
206
+ * over capacity.
207
+ *
208
+ * Consolidates a pattern that was hand-rolled independently in three places
209
+ * (`StaticClassResolver`, `VariantClassResolver`, `AriaPolicyEngine#planCache`)
210
+ * before this existed.
211
+ */
212
+ var LRUCache = class {
213
+ #maxSize;
214
+ #store = /* @__PURE__ */ new Map();
215
+ constructor(maxSize) {
216
+ if (!Number.isInteger(maxSize) || maxSize < 1) throw new RangeError("LRUCache maxSize must be a positive integer.");
217
+ this.#maxSize = maxSize;
218
+ }
219
+ get(key) {
220
+ if (!this.#store.has(key)) return void 0;
221
+ const value = this.#store.get(key);
222
+ this.#store.delete(key);
223
+ this.#store.set(key, value);
224
+ return value;
225
+ }
226
+ set(key, value) {
227
+ this.#store.delete(key);
228
+ this.#store.set(key, value);
229
+ if (this.#store.size > this.#maxSize) {
230
+ const lru = this.#store.keys().next().value;
231
+ if (lru !== void 0) this.#store.delete(lru);
232
+ }
233
+ }
234
+ has(key) {
235
+ return this.#store.has(key);
236
+ }
237
+ delete(key) {
238
+ return this.#store.delete(key);
239
+ }
240
+ get size() {
241
+ return this.#store.size;
242
+ }
243
+ clear() {
244
+ this.#store.clear();
245
+ }
246
+ };
247
+ //#endregion
248
+ //#region ../../lib/foundation/src/type-guards.ts
249
+ function isString(value) {
250
+ return typeof value === "string";
251
+ }
252
+ function isNumber(value) {
253
+ return typeof value === "number";
254
+ }
255
+ function isFunction(value) {
256
+ return typeof value === "function";
257
+ }
258
+ function isObject(value, excludeArrays = false) {
259
+ if (value === null || typeof value !== "object") return false;
260
+ return excludeArrays ? !Array.isArray(value) : true;
261
+ }
262
+ function isDefined(value) {
263
+ return value !== void 0;
264
+ }
265
+ function isUndefined(value) {
266
+ return value === void 0;
267
+ }
268
+ function isNull(value) {
269
+ return value === null;
270
+ }
271
+ function isNonNull(value) {
272
+ return value != null;
273
+ }
274
+ function isNullish(value) {
275
+ return isNull(value) || isUndefined(value);
276
+ }
277
+ //#endregion
278
+ //#region ../../lib/primitive/src/rule/is-dynamic-rule.ts
279
+ function isDynamicRule(rule) {
280
+ return isObject(rule, true) && Reflect.get(rule, RULE_BRAND) === true;
281
+ }
282
+ //#endregion
283
+ //#region ../../lib/primitive/src/rule/resolve-rule.ts
284
+ function resolveRule(rule, context) {
285
+ return isDynamicRule(rule) ? rule.resolve(context) : rule;
286
+ }
287
+ //#endregion
288
+ //#region ../../lib/primitive/src/utils/merge-props.ts
289
+ function mergeProps$2(defaultProps, props) {
290
+ return {
291
+ ...defaultProps ?? {},
292
+ ...props
293
+ };
294
+ }
295
+ //#endregion
296
+ //#region ../../lib/primitive/src/constants/aria/global-aria-attributes.ts
297
+ const GLOBAL_ARIA_ATTRIBUTES = /* @__PURE__ */ new Set([
298
+ "aria-atomic",
299
+ "aria-busy",
300
+ "aria-controls",
301
+ "aria-current",
302
+ "aria-describedby",
303
+ "aria-description",
304
+ "aria-details",
305
+ "aria-disabled",
306
+ "aria-errormessage",
307
+ "aria-flowto",
308
+ "aria-hidden",
309
+ "aria-keyshortcuts",
310
+ "aria-label",
311
+ "aria-labelledby",
312
+ "aria-live",
313
+ "aria-owns",
314
+ "aria-relevant",
315
+ "aria-roledescription"
316
+ ]);
317
+ //#endregion
318
+ //#region ../../lib/primitive/src/constants/aria/implicit-role-record.ts
319
+ /**
320
+ * A **deliberately partial** static tag→role model — not "the ARIA validator".
321
+ *
322
+ * HTML implicit roles fall into four kinds; this file only covers the first:
323
+ *
324
+ * 1. **static** — role depends on the tag alone (`nav → navigation`,
325
+ * `article → article`). Only these belong in `IMPLICIT_ROLE_RECORD`.
326
+ * 2. **attribute-dependent** — role depends on an attribute value
327
+ * (`a`/`area` is `link` *with* `href`, `generic` without — see
328
+ * `getAnchorImplicitRole`; `input` per `type`, see `INPUT_TYPE_ROLE_MAP` +
329
+ * `getInputImplicitRole`; `img` per `alt`; `select` is `combobox` or
330
+ * `listbox` per `multiple`/`size`, see `getSelectImplicitRole`).
331
+ * 3. **context-dependent** — role depends on ancestry
332
+ * (`section`/`form` become landmarks only when they have an accessible name;
333
+ * `header`/`footer` are `banner`/`contentinfo` only at the top level — see
334
+ * `getConditionalImplicitRole`).
335
+ * 4. **state-/naming-dependent** — role depends on runtime state or naming.
336
+ *
337
+ * Entries here that are *actually* attribute-dependent (`td`, `th`) are the "no
338
+ * attributes / defaults" case; callers that know the attributes must prefer the
339
+ * conditional helpers. Do not add an entry whose real role needs more than the
340
+ * tag — `a`/`area` and `select` are deliberately absent for that reason (a bare
341
+ * `<a>` is `generic`, not `link`; a bare `<select>` is `combobox`, not
342
+ * `listbox`, per ARIA-in-HTML).
343
+ */
344
+ const IMPLICIT_ROLE_RECORD = Object.freeze({
345
+ article: "article",
346
+ aside: "complementary",
347
+ footer: "contentinfo",
348
+ header: "banner",
349
+ main: "main",
350
+ nav: "navigation",
351
+ button: "button",
352
+ textarea: "textbox",
353
+ h1: "heading",
354
+ h2: "heading",
355
+ h3: "heading",
356
+ h4: "heading",
357
+ h5: "heading",
358
+ h6: "heading",
359
+ ul: "list",
360
+ ol: "list",
361
+ li: "listitem",
362
+ table: "table",
363
+ tr: "row",
364
+ td: "cell",
365
+ th: "columnheader",
366
+ dialog: "dialog",
367
+ fieldset: "group",
368
+ figure: "figure",
369
+ meter: "meter",
370
+ output: "status",
371
+ progress: "progressbar"
372
+ });
373
+ const INPUT_TYPE_ROLE_MAP = Object.freeze({
374
+ checkbox: "checkbox",
375
+ radio: "radio",
376
+ range: "slider",
377
+ number: "spinbutton",
378
+ search: "searchbox",
379
+ text: "textbox",
380
+ email: "textbox",
381
+ tel: "textbox",
382
+ url: "textbox",
383
+ button: "button",
384
+ submit: "button",
385
+ reset: "button",
386
+ image: "button"
387
+ });
388
+ /**
389
+ * Roles whose implicit assignment this library treats as **not overridable** by
390
+ * an explicit `role` attribute (a warning, not a hard block).
391
+ *
392
+ * DELIBERATE POLICY, wider than ARIA-in-HTML (audited 2026-09, see
393
+ * docs/accessibility/html-aria-audit.md D1). ARIA-in-HTML *permits* specific role overrides
394
+ * on landmark elements (e.g. `<nav role="tablist">`); Praxis flags them anyway
395
+ * via `landmarkRoleRule`, because overriding a landmark's role silently drops it
396
+ * from the screen-reader landmark menu, and the APG's own menu/tab/tree patterns
397
+ * never put those roles on a landmark element. The allowed-role tables in
398
+ * `role-restrictions.ts` remain spec-accurate; this set only drives the softer
399
+ * landmark-override advisory. `<header>`/`<footer>` are `banner`/`contentinfo`
400
+ * only at the top level (see `getConditionalImplicitRole`).
401
+ */
402
+ const STRONG_ROLES = Object.freeze([
403
+ "main",
404
+ "navigation",
405
+ "complementary",
406
+ "contentinfo",
407
+ "banner"
408
+ ]);
409
+ const STANDALONE_ROLES = Object.freeze(["article"]);
410
+ const STRONG_ROLES_SET = new Set(STRONG_ROLES);
411
+ const STANDALONE_ROLES_SET = new Set(STANDALONE_ROLES);
412
+ //#endregion
413
+ //#region ../../lib/primitive/src/constants/aria/known-aria-roles.ts
414
+ const KNOWN_ARIA_ROLES = Object.freeze([
415
+ "alert",
416
+ "alertdialog",
417
+ "application",
418
+ "article",
419
+ "banner",
420
+ "blockquote",
421
+ "button",
422
+ "caption",
423
+ "cell",
424
+ "checkbox",
425
+ "code",
426
+ "columnheader",
427
+ "combobox",
428
+ "complementary",
429
+ "contentinfo",
430
+ "definition",
431
+ "deletion",
432
+ "dialog",
433
+ "document",
434
+ "emphasis",
435
+ "feed",
436
+ "figure",
437
+ "form",
438
+ "generic",
439
+ "grid",
440
+ "gridcell",
441
+ "group",
442
+ "heading",
443
+ "img",
444
+ "insertion",
445
+ "link",
446
+ "list",
447
+ "listbox",
448
+ "listitem",
449
+ "log",
450
+ "main",
451
+ "marquee",
452
+ "math",
453
+ "menu",
454
+ "menubar",
455
+ "menuitem",
456
+ "menuitemcheckbox",
457
+ "menuitemradio",
458
+ "meter",
459
+ "navigation",
460
+ "none",
461
+ "note",
462
+ "option",
463
+ "paragraph",
464
+ "presentation",
465
+ "progressbar",
466
+ "radio",
467
+ "radiogroup",
468
+ "region",
469
+ "row",
470
+ "rowgroup",
471
+ "rowheader",
472
+ "scrollbar",
473
+ "search",
474
+ "searchbox",
475
+ "separator",
476
+ "slider",
477
+ "spinbutton",
478
+ "status",
479
+ "strong",
480
+ "subscript",
481
+ "superscript",
482
+ "switch",
483
+ "tab",
484
+ "table",
485
+ "tablist",
486
+ "tabpanel",
487
+ "term",
488
+ "textbox",
489
+ "time",
490
+ "timer",
491
+ "toolbar",
492
+ "tooltip",
493
+ "tree",
494
+ "treegrid",
495
+ "treeitem"
496
+ ]);
497
+ const KNOWN_ARIA_ROLES_SET = new Set(KNOWN_ARIA_ROLES);
498
+ //#endregion
499
+ //#region ../../lib/primitive/src/constants/aria/role-restricted-attributes.ts
500
+ const ROLE_RESTRICTED_ATTRIBUTES = /* @__PURE__ */ new Map([
501
+ ["aria-activedescendant", /* @__PURE__ */ new Set([
502
+ "application",
503
+ "combobox",
504
+ "grid",
505
+ "group",
506
+ "listbox",
507
+ "menu",
508
+ "menubar",
509
+ "radiogroup",
510
+ "row",
511
+ "searchbox",
512
+ "spinbutton",
513
+ "tablist",
514
+ "textbox",
515
+ "toolbar",
516
+ "tree",
517
+ "treegrid"
518
+ ])],
519
+ ["aria-autocomplete", /* @__PURE__ */ new Set([
520
+ "combobox",
521
+ "searchbox",
522
+ "textbox"
523
+ ])],
524
+ ["aria-checked", /* @__PURE__ */ new Set([
525
+ "checkbox",
526
+ "menuitemcheckbox",
527
+ "menuitemradio",
528
+ "option",
529
+ "radio",
530
+ "switch",
531
+ "treeitem"
532
+ ])],
533
+ ["aria-colcount", /* @__PURE__ */ new Set([
534
+ "grid",
535
+ "table",
536
+ "treegrid"
537
+ ])],
538
+ ["aria-colindex", /* @__PURE__ */ new Set([
539
+ "cell",
540
+ "columnheader",
541
+ "gridcell",
542
+ "row",
543
+ "rowheader"
544
+ ])],
545
+ ["aria-colspan", /* @__PURE__ */ new Set([
546
+ "cell",
547
+ "columnheader",
548
+ "gridcell",
549
+ "rowheader"
550
+ ])],
551
+ ["aria-expanded", /* @__PURE__ */ new Set([
552
+ "application",
553
+ "button",
554
+ "checkbox",
555
+ "columnheader",
556
+ "combobox",
557
+ "gridcell",
558
+ "link",
559
+ "listbox",
560
+ "menuitem",
561
+ "menuitemcheckbox",
562
+ "menuitemradio",
563
+ "row",
564
+ "rowheader",
565
+ "switch",
566
+ "tab",
567
+ "treeitem"
568
+ ])],
569
+ ["aria-haspopup", /* @__PURE__ */ new Set([
570
+ "application",
571
+ "button",
572
+ "columnheader",
573
+ "combobox",
574
+ "gridcell",
575
+ "link",
576
+ "menuitem",
577
+ "menuitemcheckbox",
578
+ "menuitemradio",
579
+ "rowheader",
580
+ "searchbox",
581
+ "slider",
582
+ "tab",
583
+ "textbox",
584
+ "treeitem"
585
+ ])],
586
+ ["aria-invalid", /* @__PURE__ */ new Set([
587
+ "application",
588
+ "checkbox",
589
+ "columnheader",
590
+ "combobox",
591
+ "gridcell",
592
+ "listbox",
593
+ "menuitemcheckbox",
594
+ "menuitemradio",
595
+ "radiogroup",
596
+ "rowheader",
597
+ "searchbox",
598
+ "slider",
599
+ "spinbutton",
600
+ "switch",
601
+ "textbox",
602
+ "tree",
603
+ "treegrid"
604
+ ])],
605
+ ["aria-level", /* @__PURE__ */ new Set([
606
+ "heading",
607
+ "listitem",
608
+ "row",
609
+ "tablist",
610
+ "treeitem"
611
+ ])],
612
+ ["aria-modal", /* @__PURE__ */ new Set(["alertdialog", "dialog"])],
613
+ ["aria-multiline", /* @__PURE__ */ new Set(["searchbox", "textbox"])],
614
+ ["aria-multiselectable", /* @__PURE__ */ new Set([
615
+ "grid",
616
+ "listbox",
617
+ "tablist",
618
+ "tree",
619
+ "treegrid"
620
+ ])],
621
+ ["aria-orientation", /* @__PURE__ */ new Set([
622
+ "listbox",
623
+ "menu",
624
+ "menubar",
625
+ "radiogroup",
626
+ "scrollbar",
627
+ "separator",
628
+ "slider",
629
+ "tablist",
630
+ "toolbar",
631
+ "tree",
632
+ "treegrid"
633
+ ])],
634
+ ["aria-placeholder", /* @__PURE__ */ new Set(["searchbox", "textbox"])],
635
+ ["aria-posinset", /* @__PURE__ */ new Set([
636
+ "article",
637
+ "listitem",
638
+ "menuitem",
639
+ "menuitemcheckbox",
640
+ "menuitemradio",
641
+ "option",
642
+ "radio",
643
+ "row",
644
+ "tab",
645
+ "treeitem"
646
+ ])],
647
+ ["aria-pressed", /* @__PURE__ */ new Set(["button"])],
648
+ ["aria-readonly", /* @__PURE__ */ new Set([
649
+ "checkbox",
650
+ "columnheader",
651
+ "combobox",
652
+ "grid",
653
+ "gridcell",
654
+ "listbox",
655
+ "menuitemcheckbox",
656
+ "menuitemradio",
657
+ "radiogroup",
658
+ "rowheader",
659
+ "searchbox",
660
+ "slider",
661
+ "spinbutton",
662
+ "switch",
663
+ "textbox",
664
+ "treegrid"
665
+ ])],
666
+ ["aria-required", /* @__PURE__ */ new Set([
667
+ "checkbox",
668
+ "columnheader",
669
+ "combobox",
670
+ "gridcell",
671
+ "listbox",
672
+ "menuitemcheckbox",
673
+ "menuitemradio",
674
+ "radiogroup",
675
+ "rowheader",
676
+ "searchbox",
677
+ "spinbutton",
678
+ "switch",
679
+ "textbox",
680
+ "tree",
681
+ "treegrid"
682
+ ])],
683
+ ["aria-rowcount", /* @__PURE__ */ new Set([
684
+ "grid",
685
+ "table",
686
+ "treegrid"
687
+ ])],
688
+ ["aria-rowindex", /* @__PURE__ */ new Set([
689
+ "cell",
690
+ "columnheader",
691
+ "gridcell",
692
+ "row",
693
+ "rowheader"
694
+ ])],
695
+ ["aria-rowspan", /* @__PURE__ */ new Set([
696
+ "cell",
697
+ "columnheader",
698
+ "gridcell",
699
+ "rowheader"
700
+ ])],
701
+ ["aria-selected", /* @__PURE__ */ new Set([
702
+ "columnheader",
703
+ "gridcell",
704
+ "option",
705
+ "row",
706
+ "rowheader",
707
+ "tab",
708
+ "treeitem"
709
+ ])],
710
+ ["aria-setsize", /* @__PURE__ */ new Set([
711
+ "article",
712
+ "listitem",
713
+ "menuitem",
714
+ "menuitemcheckbox",
715
+ "menuitemradio",
716
+ "option",
717
+ "radio",
718
+ "row",
719
+ "tab",
720
+ "treeitem"
721
+ ])],
722
+ ["aria-sort", /* @__PURE__ */ new Set(["columnheader", "rowheader"])],
723
+ ["aria-valuemax", /* @__PURE__ */ new Set([
724
+ "meter",
725
+ "progressbar",
726
+ "scrollbar",
727
+ "separator",
728
+ "slider",
729
+ "spinbutton"
730
+ ])],
731
+ ["aria-valuemin", /* @__PURE__ */ new Set([
732
+ "meter",
733
+ "progressbar",
734
+ "scrollbar",
735
+ "separator",
736
+ "slider",
737
+ "spinbutton"
738
+ ])],
739
+ ["aria-valuenow", /* @__PURE__ */ new Set([
740
+ "meter",
741
+ "progressbar",
742
+ "scrollbar",
743
+ "separator",
744
+ "slider",
745
+ "spinbutton"
746
+ ])],
747
+ ["aria-valuetext", /* @__PURE__ */ new Set([
748
+ "meter",
749
+ "progressbar",
750
+ "scrollbar",
751
+ "separator",
752
+ "slider",
753
+ "spinbutton"
754
+ ])]
755
+ ]);
756
+ //#endregion
757
+ //#region ../../lib/primitive/src/constants/html/void-tags.ts
758
+ /**
759
+ * HTML void elements.
760
+ *
761
+ * Void elements cannot have child nodes and therefore all share the same
762
+ * empty content model — a WHATWG-stable fact, not a praxis-kit opinion.
763
+ * Single source of truth shared by the contract engine's built-in void-tag
764
+ * children contract (`@praxis-kit/core`'s `VOID_TAGS` re-export) and the
765
+ * Tailwind pipeline's flex/grid-on-void-tag warning, so the two can't drift.
766
+ */
767
+ const VOID_TAGS = [
768
+ "area",
769
+ "base",
770
+ "br",
771
+ "col",
772
+ "embed",
773
+ "hr",
774
+ "img",
775
+ "input",
776
+ "link",
777
+ "meta",
778
+ "param",
779
+ "source",
780
+ "track",
781
+ "wbr"
782
+ ];
783
+ //#endregion
784
+ //#region ../../lib/primitive/src/guards/aria/is-aria-attribute.ts
785
+ function isGlobalAriaAttribute(attr) {
786
+ return GLOBAL_ARIA_ATTRIBUTES.has(attr);
787
+ }
788
+ function isAriaAttributeValidForRole(attr, role) {
789
+ const allowedRoles = ROLE_RESTRICTED_ATTRIBUTES.get(attr);
790
+ if (isUndefined(allowedRoles)) return true;
791
+ if (isUndefined(role)) return false;
792
+ return allowedRoles.has(role);
793
+ }
794
+ //#endregion
795
+ //#region ../../lib/primitive/src/guards/aria/is-aria-role.ts
796
+ function lookupImplicitRole(tag) {
797
+ return IMPLICIT_ROLE_RECORD[tag];
798
+ }
799
+ /**
800
+ * Returns whether the element has a strong implicit ARIA role.
801
+ *
802
+ * Strong implicit roles cannot be overridden with an explicit `role`
803
+ * attribute unless the HTML specification explicitly permits it.
804
+ */
805
+ function isStrongImplicitRole(tag) {
806
+ const role = lookupImplicitRole(tag);
807
+ return !isNullish(role) && STRONG_ROLES_SET.has(role);
808
+ }
809
+ /**
810
+ * Returns whether the element's implicit ARIA role is considered
811
+ * standalone for accessibility validation purposes.
812
+ */
813
+ function hasStandaloneRole(tag) {
814
+ const role = lookupImplicitRole(tag);
815
+ return !isNullish(role) && STANDALONE_ROLES_SET.has(role);
816
+ }
817
+ /**
818
+ * Input types for which the presence of a `list` attribute changes the implicit
819
+ * ARIA role from `textbox` to `combobox`, per the ARIA-in-HTML specification.
820
+ * `packages/core`'s `inputElementSpec` keeps its own copy of this set (it must
821
+ * not import the guard module) — the two are pinned to the spec by tests on both
822
+ * sides and must stay in sync.
823
+ */
824
+ const LIST_ELIGIBLE_INPUT_TYPES = /* @__PURE__ */ new Set([
825
+ "text",
826
+ "search",
827
+ "tel",
828
+ "url",
829
+ "email"
830
+ ]);
831
+ /**
832
+ * Returns the implicit ARIA role for an `<input>` element.
833
+ *
834
+ * An omitted `type` attribute defaults to `text` (HTML), so a bare `<input>`
835
+ * resolves to `textbox` — `<input role="textbox">` is then caught as redundant.
836
+ *
837
+ * For text-like input types associated with a `<datalist>` via the
838
+ * `list` attribute, the implicit role becomes `combobox` instead of
839
+ * `textbox`, per the ARIA-in-HTML specification.
840
+ *
841
+ * Returns `undefined` for input types that do not expose an implicit
842
+ * ARIA role (for example `color`, `date`, or `hidden`).
843
+ */
844
+ function getInputImplicitRole(type, list) {
845
+ const resolvedType = isNullish(type) ? "text" : type;
846
+ if (!isString(resolvedType)) return void 0;
847
+ const role = INPUT_TYPE_ROLE_MAP[resolvedType];
848
+ if (!role) return void 0;
849
+ if (!isNullish(list) && LIST_ELIGIBLE_INPUT_TYPES.has(resolvedType)) return "combobox";
850
+ return role;
851
+ }
852
+ function isBooleanAttrOn(value) {
853
+ if (value === false || value === void 0 || value === null) return false;
854
+ if (typeof value === "string") return value.toLowerCase() !== "false";
855
+ return true;
856
+ }
857
+ /**
858
+ * Returns the implicit ARIA role for a `<select>` element.
859
+ *
860
+ * Per HTML-AAM / ARIA-in-HTML a `<select>` is a **`combobox`** in its default
861
+ * drop-down form, and a **`listbox`** only when it is a list box — i.e. it has a
862
+ * `multiple` attribute, or a `size` attribute whose value is greater than 1.
863
+ *
864
+ * Kept as a helper (not an `IMPLICIT_ROLE_RECORD` entry) because the role needs
865
+ * more than the tag name — the same reason `<input>` is handled by
866
+ * `getInputImplicitRole`.
867
+ */
868
+ function getSelectImplicitRole(multiple, size) {
869
+ if (isBooleanAttrOn(multiple)) return "listbox";
870
+ const sizeNum = typeof size === "number" ? size : typeof size === "string" ? Number(size) : NaN;
871
+ if (Number.isFinite(sizeNum) && sizeNum > 1) return "listbox";
872
+ return "combobox";
873
+ }
874
+ /**
875
+ * Returns the implicit ARIA role for an `<a>` or `<area>` element.
876
+ *
877
+ * Per ARIA-in-HTML, an `<a>`/`<area>` **with an `href`** is a `link`; **without
878
+ * an `href`** it is `generic` (it has no interactive semantics — it is just a
879
+ * styled span). Kept as a helper (not an `IMPLICIT_ROLE_RECORD` entry) because
880
+ * the role needs the `href` attribute, not just the tag: a bare `<a>` is *not* a
881
+ * `link`, so `<a role="button">` on it is not a redundant override.
882
+ *
883
+ * A `href=""` (empty string) still counts — it is a valid same-page link.
884
+ */
885
+ function getAnchorImplicitRole(href) {
886
+ return isNullish(href) ? "generic" : "link";
887
+ }
888
+ /**
889
+ * Returns the conditional implicit landmark role for `<section>` and
890
+ * `<form>` elements.
891
+ *
892
+ * Per HTML-AAM, these elements expose their landmark roles only when
893
+ * they have an accessible name provided by `aria-label` or
894
+ * `aria-labelledby`.
895
+ */
896
+ function getConditionalImplicitRole(tag, ariaLabel, ariaLabelledBy) {
897
+ if (!(isString(ariaLabel) && ariaLabel.trim().length > 0 || isString(ariaLabelledBy) && ariaLabelledBy.trim().length > 0)) return void 0;
898
+ if (tag === "section") return "region";
899
+ if (tag === "form") return "form";
900
+ }
901
+ //#endregion
902
+ //#region ../../lib/primitive/src/guards/aria/is-known-aria-role.ts
903
+ function isKnownAriaRole(value) {
904
+ return isString(value) && KNOWN_ARIA_ROLES_SET.has(value);
905
+ }
906
+ //#endregion
907
+ //#region ../../lib/primitive/src/guards/children/component-id.ts
908
+ /**
909
+ * Well-known Symbol stamped onto every component created by praxis-kit factories.
910
+ * Stores the factory's defaultTag — the tag that renders when no `as` prop is given.
911
+ * HOC wrappers must propagate it: `Wrapped[COMPONENT_DEFAULT_TAG] = Original[COMPONENT_DEFAULT_TAG]`.
912
+ */
913
+ const COMPONENT_DEFAULT_TAG = Symbol.for("praxis.component-default-tag");
914
+ //#endregion
915
+ //#region ../../lib/primitive/src/guards/children/is-tag.ts
916
+ function getAsProp(child) {
917
+ if (!isObject(child) || !("props" in child)) return void 0;
918
+ const { props } = child;
919
+ if (!isObject(props)) return void 0;
920
+ const as = Reflect.get(props, "as");
921
+ return isString(as) && as !== "" ? as : void 0;
922
+ }
923
+ /**
924
+ * Resolves the effective HTML tag for a vnode:
925
+ * - native element: returns its type string directly
926
+ * - praxis-kit component: resolves `as ?? defaultTag`, mirroring render-time logic
927
+ * - anything else: returns undefined
928
+ */
929
+ function getTag(child) {
930
+ if (!isObject(child) || !("type" in child)) return void 0;
931
+ const { type: t } = child;
932
+ if (isString(t)) return t;
933
+ if (typeof t === "function" || isObject(t)) {
934
+ const defaultTag = Reflect.get(t, COMPONENT_DEFAULT_TAG);
935
+ if (!isString(defaultTag)) return void 0;
936
+ return getAsProp(child) ?? defaultTag;
937
+ }
938
+ }
939
+ function isTag(...args) {
940
+ if (isString(args[0])) {
941
+ const set = new Set(args);
942
+ return (child) => {
943
+ const tag = getTag(child);
944
+ return tag !== void 0 && set.has(tag);
945
+ };
946
+ }
947
+ const [child, ...tags] = args;
948
+ const set = new Set(tags);
949
+ const tag = getTag(child);
950
+ return tag !== void 0 && set.has(tag);
951
+ }
952
+ //#endregion
953
+ //#region ../../lib/adapter-utils/src/runtime/finalize-component.ts
954
+ /**
955
+ * Attaches the resolved default-tag metadata and assembles any
956
+ * sub-components onto a generated component — the tail every adapter
957
+ * factory runs after building its framework component.
958
+ *
959
+ * Display-name handling stays a separate, explicit call in each adapter
960
+ * (`applyDisplayName`) rather than folding in here, since React's version
961
+ * additionally stamps a `COMPONENT_ID` for identity-based child matching
962
+ * that the other adapters don't need.
963
+ */
964
+ function finalizeComponent(component, defaultTag, subComponents) {
965
+ if (typeof defaultTag === "string") Object.assign(component, { [COMPONENT_DEFAULT_TAG]: defaultTag });
966
+ return assembleCompoundComponent(component, subComponents);
967
+ }
968
+ //#endregion
969
+ //#region ../../lib/adapter-utils/src/runtime/is-factory-options-like.ts
970
+ /**
971
+ * Loosely validates one recognized `FactoryOptions` field's runtime shape.
972
+ * "Loosely" because the field's declared type is itself parameterized by a
973
+ * generic (Props, Variants, TPlugin, ...) that's erased at runtime — these
974
+ * checks confirm the field is the right *kind* of value (object, function,
975
+ * string), not that it satisfies its exact generic instantiation, which no
976
+ * runtime check can ever do.
977
+ *
978
+ * Covers every field `FactoryOptions` itself declares — identical across
979
+ * every adapter, since they all extend the same core type. Adapter-specific
980
+ * additions (React's `slotComponent`/`artifact`, every adapter's own
981
+ * `filterProps`, etc.) are passed in by the caller as extra entries.
982
+ */
983
+ const FACTORY_OPTIONS_FIELD_VALIDATORS = {
984
+ tag: (v) => v === void 0 || isString(v),
985
+ name: (v) => v === void 0 || isString(v),
986
+ defaults: (v) => v === void 0 || isObject(v),
987
+ normalize: (v) => v === void 0 || isFunction(v),
988
+ styling: (v) => v === void 0 || isObject(v),
989
+ enforcement: (v) => v === void 0 || isObject(v),
990
+ diagnostics: (v) => v === void 0 || isObject(v),
991
+ subComponents: (v) => v === void 0 || isObject(v),
992
+ onElement: (v) => v === void 0 || isFunction(v)
993
+ };
994
+ /**
995
+ * Type guard narrowing the generic `FactoryOptions` shape down to an
996
+ * adapter-specific factory options type `T` — the type each adapter's
997
+ * `buildRuntime` is declared against.
998
+ *
999
+ * Walks every own enumerable property on `options` and checks it against
1000
+ * `FACTORY_OPTIONS_FIELD_VALIDATORS` plus `extraFieldValidators` (the
1001
+ * adapter's own additions on top of `FactoryOptions`): an unrecognized key,
1002
+ * or a recognized key holding a value of the wrong kind, fails the guard.
1003
+ * This is a genuine structural check, not a relabeled assertion — though it
1004
+ * necessarily stops at each field's runtime *kind*, since the field's actual
1005
+ * generic instantiation is erased at runtime and no guard can validate it.
1006
+ * That part remains the caller's responsibility, same as with any other
1007
+ * generic function in TypeScript.
1008
+ */
1009
+ function isFactoryOptionsLike(options, extraFieldValidators) {
1010
+ if (!isObject(options)) return false;
1011
+ const validators = {
1012
+ ...FACTORY_OPTIONS_FIELD_VALIDATORS,
1013
+ ...extraFieldValidators
1014
+ };
1015
+ for (const [key, value] of Object.entries(options)) {
1016
+ const validate = validators[key];
1017
+ if (!validate || !validate(value)) return false;
1018
+ }
1019
+ return true;
1020
+ }
1021
+ //#endregion
1022
+ //#region ../../lib/contract/src/aria/aria-role-policy.ts
1023
+ function getImplicitRole(tag, props) {
1024
+ if (tag in IMPLICIT_ROLE_RECORD) return IMPLICIT_ROLE_RECORD[tag];
1025
+ if (tag === "a" || tag === "area") return getAnchorImplicitRole(props?.href);
1026
+ if (tag === "input") return getInputImplicitRole(props?.type, props?.list);
1027
+ if (tag === "select") return getSelectImplicitRole(props?.multiple, props?.size);
1028
+ if (tag === "img") return props?.alt === "" ? "none" : "img";
1029
+ if (tag === "section" || tag === "form") return getConditionalImplicitRole(tag, props?.["aria-label"], props?.["aria-labelledby"]);
1030
+ }
1031
+ //#endregion
1032
+ //#region ../../lib/contract/src/diagnostics/anchor-accessibility.ts
1033
+ function createDiagnostic(input) {
1034
+ return {
1035
+ category: DiagnosticCategory.Accessibility,
1036
+ ...input
1037
+ };
1038
+ }
1039
+ const AnchorAccessibilityDiagnostics = {
1040
+ roleButtonWithHref() {
1041
+ return createDiagnostic({
1042
+ code: DiagnosticCode.A11yAnchorRoleButtonWithHref,
1043
+ severity: "warning",
1044
+ message: "role=\"button\" on an <a> with an href overrides its navigation semantics for assistive technology; use a real <button> element, or remove href, if this element should not navigate.",
1045
+ rationale: "Assistive technology announces role=\"button\" as a button, not a link — but the element still follows the link when activated by mouse or keyboard, which is confusing and inconsistent with how a button is expected to behave.",
1046
+ suggestions: [{
1047
+ title: "Use a real <button>",
1048
+ description: "If this element should not navigate, render a <button> instead of an <a>."
1049
+ }, {
1050
+ title: "Remove role=\"button\"",
1051
+ description: "If this element should navigate, keep the default link role."
1052
+ }]
1053
+ });
1054
+ },
1055
+ ariaDisabledInert() {
1056
+ return createDiagnostic({
1057
+ code: DiagnosticCode.A11yAnchorAriaDisabledInert,
1058
+ severity: "warning",
1059
+ message: "aria-disabled does not prevent an <a> from being focused, clicked, or navigated via keyboard; remove href, or prevent navigation yourself, if this link should be inert.",
1060
+ rationale: "Unlike a native form control, an <a> has no disabled state the browser enforces — aria-disabled only changes what assistive technology announces, not what actually happens when the link is activated by mouse or keyboard.",
1061
+ suggestions: [{
1062
+ title: "Remove href",
1063
+ description: "If this element is temporarily unavailable, remove href until it can be activated."
1064
+ }, {
1065
+ title: "Prevent navigation when disabled",
1066
+ description: "Prevent the click and keyboard activation when the link is disabled so it behaves consistently for all users."
1067
+ }]
1068
+ });
1069
+ }
1070
+ };
1071
+ //#endregion
1072
+ //#region ../../lib/contract/src/diagnostics/aria.ts
1073
+ const AriaDiagnostics = {
1074
+ /** Generic bridge for violations produced by external AriaRule functions. */
1075
+ fromViolation(v) {
1076
+ return {
1077
+ code: DiagnosticCode.AriaViolation,
1078
+ category: DiagnosticCategory.ARIA,
1079
+ message: v.message
1080
+ };
1081
+ },
1082
+ attributeInvalid(key, role) {
1083
+ return {
1084
+ code: DiagnosticCode.AriaAttributeInvalid,
1085
+ category: DiagnosticCategory.ARIA,
1086
+ message: `"${key}" is not valid on role="${role}". It will be removed.`,
1087
+ rationale: "Invalid ARIA attributes are ignored by assistive technology and may trigger accessibility-tree warnings in browser devtools.",
1088
+ suggestions: [{
1089
+ title: "Remove the attribute",
1090
+ description: `"${key}" is not in the allowed attribute set for role="${role}".`
1091
+ }]
1092
+ };
1093
+ },
1094
+ missingLiveRegion(role, impliedLive) {
1095
+ return {
1096
+ code: DiagnosticCode.AriaMissingLiveRegion,
1097
+ category: DiagnosticCategory.ARIA,
1098
+ message: `role="${role}" implies aria-live="${impliedLive}" but it is missing. It has been injected.`,
1099
+ rationale: "Live-region roles announce dynamic content changes to screen readers. Without aria-live the politeness level is unspecified and announcements may be silent.",
1100
+ suggestions: [{
1101
+ title: `Add aria-live="${impliedLive}"`,
1102
+ description: `role="${role}" conventionally implies aria-live="${impliedLive}".`,
1103
+ fix: `aria-live="${impliedLive}"`
1104
+ }]
1105
+ };
1106
+ },
1107
+ missingAtomic(role) {
1108
+ return {
1109
+ code: DiagnosticCode.AriaMissingAtomic,
1110
+ category: DiagnosticCategory.ARIA,
1111
+ message: `role="${role}" is a live region. Consider setting aria-atomic="true" if the full region should be announced as a unit, or aria-atomic="false" if only changed nodes should be read.`,
1112
+ rationale: "aria-atomic controls whether assistive technology announces the entire live region or only the changed nodes. Omitting it leaves the behaviour browser-defined."
1113
+ };
1114
+ },
1115
+ relevantInvalidTokens(invalid) {
1116
+ const quoted = invalid.map((t) => `"${t}"`).join(", ");
1117
+ return {
1118
+ code: DiagnosticCode.AriaRelevantInvalidToken,
1119
+ category: DiagnosticCategory.ARIA,
1120
+ message: `aria-relevant contains invalid token(s): ${quoted}. Valid tokens are: additions, removals, text, all.`,
1121
+ rationale: "aria-relevant accepts a space-separated list of change types. Unrecognised tokens are silently ignored by assistive technology, making the attribute ineffective.",
1122
+ suggestions: [{
1123
+ title: "Use only valid tokens",
1124
+ description: "Valid values are: additions, removals, text, all (or a space-separated combination)."
1125
+ }]
1126
+ };
1127
+ },
1128
+ relevantSuperseded() {
1129
+ return {
1130
+ code: DiagnosticCode.AriaRelevantSuperseded,
1131
+ category: DiagnosticCategory.ARIA,
1132
+ message: "aria-relevant includes \"all\" alongside other tokens. \"all\" supersedes additions, removals, and text — use aria-relevant=\"all\" alone.",
1133
+ rationale: "\"all\" is equivalent to \"additions removals text\". Combining it with other tokens is redundant and may confuse readers of the markup.",
1134
+ suggestions: [{
1135
+ title: "Use aria-relevant=\"all\"",
1136
+ fix: "aria-relevant=\"all\""
1137
+ }]
1138
+ };
1139
+ },
1140
+ missingAccessibleName(tag) {
1141
+ return {
1142
+ code: DiagnosticCode.AriaMissingAccessibleName,
1143
+ category: DiagnosticCategory.ARIA,
1144
+ message: `<${tag}> has no accessible name. Add aria-label or aria-labelledby.`,
1145
+ rationale: "Elements with a landmark or interactive role must have an accessible name so that assistive technology can identify them when presenting the page outline.",
1146
+ suggestions: [{
1147
+ title: "Add aria-label",
1148
+ description: `Add aria-label="…" directly to the <${tag}> element.`
1149
+ }, {
1150
+ title: "Add aria-labelledby",
1151
+ description: "Point aria-labelledby at the id of an existing heading or label element."
1152
+ }]
1153
+ };
1154
+ },
1155
+ nameProhibited(attr, role) {
1156
+ return {
1157
+ code: DiagnosticCode.AriaNameProhibited,
1158
+ category: DiagnosticCategory.ARIA,
1159
+ message: `"${attr}" is prohibited on role="${role}" — this role does not support a name from the author. It will be removed.`,
1160
+ rationale: "WAI-ARIA 1.2 lists a set of roles (generic, presentation/none, and the inline text-level roles) as Name Prohibited: aria-label / aria-labelledby on them are a conformance error and are ignored by assistive technology.",
1161
+ suggestions: [{
1162
+ title: "Remove the attribute",
1163
+ description: `role="${role}" cannot be named. If the element needs an accessible name, give it a role that supports one (or use visible text / a wrapping labelled element).`
1164
+ }]
1165
+ };
1166
+ },
1167
+ attributeOnPresentational(attr, tag) {
1168
+ return {
1169
+ code: DiagnosticCode.AriaAttributeOnPresentational,
1170
+ category: DiagnosticCategory.ARIA,
1171
+ message: `"${attr}" is not allowed on a presentational <${tag}>. Presentational elements are invisible to assistive technology.`,
1172
+ rationale: "role=\"none\" and role=\"presentation\" (including <img alt=\"\">) remove an element from the accessibility tree. ARIA attributes on such elements are ignored by assistive technology.",
1173
+ suggestions: [{
1174
+ title: "Remove the attribute",
1175
+ description: `"${attr}" has no effect when the element has role="none" or role="presentation".`
1176
+ }]
1177
+ };
1178
+ },
1179
+ ariaHiddenOnFocusable(tag) {
1180
+ return {
1181
+ code: DiagnosticCode.AriaHiddenOnFocusable,
1182
+ category: DiagnosticCategory.ARIA,
1183
+ message: `aria-hidden="true" must not be used on focusable <${tag}> elements. Screen reader users who navigate by keyboard will encounter the element but receive no information about it.`,
1184
+ rationale: "aria-hidden removes an element from the accessibility tree while leaving it keyboard-reachable. This creates a \"ghost\" — a focusable element assistive technology cannot describe.",
1185
+ suggestions: [{
1186
+ title: "Remove aria-hidden",
1187
+ description: "If the element should be hidden from all users, use the HTML hidden attribute or CSS display:none instead."
1188
+ }, {
1189
+ title: "Make the element non-focusable",
1190
+ description: "If the element is intentionally decorative, add tabindex=\"-1\" and disable it so it is not reachable by keyboard."
1191
+ }]
1192
+ };
1193
+ },
1194
+ invalidAttributeValue(attr, value, expected) {
1195
+ const got = value === null ? "null" : value === void 0 ? "undefined" : typeof value === "string" ? `"${value}"` : String(value);
1196
+ return {
1197
+ code: DiagnosticCode.AriaInvalidAttributeValue,
1198
+ category: DiagnosticCategory.ARIA,
1199
+ message: `"${attr}" has an invalid value (${got}). Expected: ${expected}.`,
1200
+ rationale: "ARIA attributes with invalid values are silently ignored by assistive technology, making the markup semantically inert.",
1201
+ suggestions: [{
1202
+ title: `Use a valid value for ${attr}`,
1203
+ description: `Valid values are: ${expected}.`
1204
+ }]
1205
+ };
1206
+ },
1207
+ redundantAriaLevel(tag, level) {
1208
+ return {
1209
+ code: DiagnosticCode.AriaRedundantLevelAttribute,
1210
+ category: DiagnosticCategory.ARIA,
1211
+ message: `aria-level="${level}" is redundant on <${tag}>: the element already has an implicit heading level of ${level}. Remove the attribute.`,
1212
+ rationale: "Restating the implicit aria-level adds noise without semantic value. Use aria-level only to override the native heading level (e.g. aria-level=\"3\" on <h2>).",
1213
+ suggestions: [{
1214
+ title: "Remove aria-level",
1215
+ description: `<${tag}> already implies aria-level="${level}".`
1216
+ }]
1217
+ };
1218
+ },
1219
+ requiredProperty(attr, role) {
1220
+ return {
1221
+ code: DiagnosticCode.AriaRequiredProperty,
1222
+ category: DiagnosticCategory.ARIA,
1223
+ message: `"${attr}" is required for role="${role}" but is missing.`,
1224
+ rationale: `WAI-ARIA 1.2 specifies required states and properties for certain roles. Without "${attr}", assistive technology cannot correctly communicate the element's state to users.`,
1225
+ suggestions: [{
1226
+ title: `Add ${attr}`,
1227
+ description: `role="${role}" requires "${attr}" to be present.`
1228
+ }]
1229
+ };
1230
+ },
1231
+ invalidRole(role, tag) {
1232
+ return {
1233
+ code: DiagnosticCode.AriaInvalidRole,
1234
+ category: DiagnosticCategory.ARIA,
1235
+ message: `Invalid role "${role ?? ""}" on <${tag}>.`,
1236
+ rationale: "An unrecognised or misapplied ARIA role is ignored by assistive technology and may degrade the accessibility of the element."
1237
+ };
1238
+ }
1239
+ };
1240
+ //#endregion
1241
+ //#region ../../lib/contract/src/diagnostics/contract.ts
1242
+ const ContractDiagnostics = {
1243
+ unexpectedChild(typeName, index, context) {
1244
+ return {
1245
+ code: DiagnosticCode.UnexpectedChild,
1246
+ category: DiagnosticCategory.Contract,
1247
+ component: context,
1248
+ message: `${context}: unexpected child "${typeName}" at index ${index}.`
1249
+ };
1250
+ },
1251
+ ambiguousChild(typeName, index, ruleNames, context) {
1252
+ const quoted = ruleNames.map((n) => `"${n}"`).join(" and ");
1253
+ return {
1254
+ code: DiagnosticCode.AmbiguousChild,
1255
+ category: DiagnosticCategory.Contract,
1256
+ component: context,
1257
+ message: `${context}: child "${typeName}" at index ${index} matches multiple child rules: ${quoted}.`
1258
+ };
1259
+ },
1260
+ cardinalityMin(ruleName, min, context) {
1261
+ return {
1262
+ code: DiagnosticCode.CardinalityMin,
1263
+ category: DiagnosticCategory.Contract,
1264
+ component: context,
1265
+ message: `${context}: "${ruleName}" requires at least ${min}.`
1266
+ };
1267
+ },
1268
+ cardinalityMax(ruleName, max, context) {
1269
+ return {
1270
+ code: DiagnosticCode.CardinalityMax,
1271
+ category: DiagnosticCategory.Contract,
1272
+ component: context,
1273
+ message: `${context}: "${ruleName}" allows at most ${max}.`
1274
+ };
1275
+ },
1276
+ positionViolation(ruleName, position, index, context) {
1277
+ return {
1278
+ code: DiagnosticCode.PositionViolation,
1279
+ category: DiagnosticCategory.Contract,
1280
+ component: context,
1281
+ message: `${context}: "${ruleName}" must be ${position}, got index ${index}`
1282
+ };
1283
+ },
1284
+ unknownVariantDim(component, label, dim) {
1285
+ return {
1286
+ code: DiagnosticCode.ContractUnknownVariantDim,
1287
+ category: DiagnosticCategory.Contract,
1288
+ message: `${component}: ${label} references unknown variant "${dim}".`
1289
+ };
1290
+ },
1291
+ unknownVariantValue(component, label, dim, value, valid) {
1292
+ return {
1293
+ code: DiagnosticCode.ContractUnknownVariantValue,
1294
+ category: DiagnosticCategory.Contract,
1295
+ message: `${component}: ${label} sets "${dim}" to unknown value "${value}" (valid: ${valid.join(", ")}).`
1296
+ };
1297
+ },
1298
+ unknownRecipeKey(component, key) {
1299
+ return {
1300
+ code: DiagnosticCode.ContractUnknownRecipeKey,
1301
+ category: DiagnosticCategory.Contract,
1302
+ message: `${component}: unknown recipeKey "${key}" — no preset with that name exists.`
1303
+ };
1304
+ },
1305
+ invalidVariantValue(component, key, value) {
1306
+ return {
1307
+ code: DiagnosticCode.ContractInvalidVariantValue,
1308
+ category: DiagnosticCategory.Contract,
1309
+ message: `${component}: variant "${key}=${value}" is not a defined value for the "${key}" dimension.`
1310
+ };
1311
+ },
1312
+ allowedAsViolation(tag, allowedAs, component) {
1313
+ const allowed = allowedAs.map((t) => `"${String(t)}"`).join(", ");
1314
+ return {
1315
+ code: DiagnosticCode.AllowedAsViolation,
1316
+ category: DiagnosticCategory.Contract,
1317
+ component,
1318
+ message: `<${component}>: "as" prop received "${tag}" but only [${allowed}] are allowed.`
1319
+ };
1320
+ }
1321
+ };
1322
+ //#endregion
1323
+ //#region ../../lib/contract/src/diagnostics/html.ts
1324
+ const ATTRIBUTE_IGNORED_CODES = {
1325
+ checked: DiagnosticCode.HtmlInputCheckedIgnoredForType,
1326
+ multiple: DiagnosticCode.HtmlInputMultipleIgnoredForType,
1327
+ maxLength: DiagnosticCode.HtmlInputMaxLengthIgnoredForType,
1328
+ minLength: DiagnosticCode.HtmlInputMinLengthIgnoredForType,
1329
+ pattern: DiagnosticCode.HtmlInputPatternIgnoredForType,
1330
+ min: DiagnosticCode.HtmlInputMinIgnoredForType,
1331
+ max: DiagnosticCode.HtmlInputMaxIgnoredForType,
1332
+ step: DiagnosticCode.HtmlInputStepIgnoredForType,
1333
+ accept: DiagnosticCode.HtmlInputAcceptIgnoredForType,
1334
+ capture: DiagnosticCode.HtmlInputCaptureIgnoredForType,
1335
+ size: DiagnosticCode.HtmlInputSizeIgnoredForType,
1336
+ alt: DiagnosticCode.HtmlInputAltIgnoredForType,
1337
+ height: DiagnosticCode.HtmlInputHeightIgnoredForType,
1338
+ width: DiagnosticCode.HtmlInputWidthIgnoredForType
1339
+ };
1340
+ const HtmlDiagnostics = {
1341
+ emptyRole(tag) {
1342
+ return {
1343
+ code: DiagnosticCode.HtmlEmptyRole,
1344
+ category: DiagnosticCategory.HTML,
1345
+ severity: "warning",
1346
+ message: `<${tag}> has an explicit empty role="". Omit the attribute instead.`
1347
+ };
1348
+ },
1349
+ implicitRoleRedundant(tag, implicitRole) {
1350
+ return {
1351
+ code: DiagnosticCode.HtmlImplicitRoleRedundant,
1352
+ category: DiagnosticCategory.HTML,
1353
+ severity: "warning",
1354
+ message: `<${tag}> already has implicit role="${implicitRole}". Avoid redundant role assignment.`
1355
+ };
1356
+ },
1357
+ implicitRoleOverride(tag, implicitRole, role) {
1358
+ return {
1359
+ code: DiagnosticCode.HtmlImplicitRoleOverride,
1360
+ category: DiagnosticCategory.HTML,
1361
+ severity: "error",
1362
+ message: `<${tag}> should not override its implicit role="${implicitRole}" with role="${role}".`
1363
+ };
1364
+ },
1365
+ standaloneRegionOverride(tag, implicitRole) {
1366
+ return {
1367
+ code: DiagnosticCode.HtmlStandaloneRegionOverride,
1368
+ category: DiagnosticCategory.HTML,
1369
+ severity: "error",
1370
+ message: `<${tag}> is a self-contained element with implicit role="${implicitRole}". Assigning role="region" has been removed.`
1371
+ };
1372
+ },
1373
+ landmarkRoleOverride(tag, implicitRole, role) {
1374
+ return {
1375
+ code: DiagnosticCode.HtmlLandmarkRoleOverride,
1376
+ category: DiagnosticCategory.HTML,
1377
+ severity: "error",
1378
+ message: `<${tag}> has a fixed landmark role="${implicitRole}". role="${role}" overrides it and confuses assistive technology. The override has been removed.`
1379
+ };
1380
+ },
1381
+ invalidChild(child, parent, allowed) {
1382
+ return {
1383
+ code: DiagnosticCode.HtmlInvalidChild,
1384
+ category: DiagnosticCategory.HTML,
1385
+ severity: "error",
1386
+ message: `<${child}> is not a valid direct child of <${parent}>. Allowed: ${allowed}.`
1387
+ };
1388
+ },
1389
+ roleNotPermitted(tag, role, allowedRoles) {
1390
+ const allowed = allowedRoles.length > 0 ? allowedRoles.map((r) => `"${r}"`).join(", ") : "none — no explicit role is permitted on this element";
1391
+ return {
1392
+ code: DiagnosticCode.HtmlRoleNotPermitted,
1393
+ category: DiagnosticCategory.HTML,
1394
+ severity: "error",
1395
+ message: `role="${role}" is not permitted on <${tag}>. Allowed alternate role(s): ${allowed}.`,
1396
+ rationale: "The WAI-ARIA \"ARIA in HTML\" specification restricts which explicit roles a native element may take. A role outside that set is ignored or produces undefined behavior in assistive technology."
1397
+ };
1398
+ },
1399
+ input: {
1400
+ unsupportedType(type) {
1401
+ return {
1402
+ code: DiagnosticCode.HtmlInputUnsupportedType,
1403
+ category: DiagnosticCategory.HTML,
1404
+ severity: "warning",
1405
+ message: `type="${type}" is not a value defined by the HTML specification. Browsers silently fall back to type="text".`,
1406
+ rationale: "An unrecognized input type is not invalid markup — the spec requires the \"text\" fallback — but it usually means a typo, since the input keeps working while silently losing the intended type-specific behavior (validation, virtual keyboard, picker UI).",
1407
+ suggestions: [{
1408
+ title: "Check for a typo in the type value",
1409
+ description: `"${type}" does not match any HTML5 input type.`
1410
+ }]
1411
+ };
1412
+ },
1413
+ attributeIgnoredForType(attribute, type, allowedTypes) {
1414
+ const allowed = allowedTypes.map((t) => `"${t}"`).join(", ");
1415
+ return {
1416
+ code: ATTRIBUTE_IGNORED_CODES[attribute],
1417
+ category: DiagnosticCategory.HTML,
1418
+ severity: "warning",
1419
+ message: `"${attribute}" is ignored on <input type="${type}">.`,
1420
+ rationale: `"${attribute}" only has an effect when type is one of: ${allowed}. Browsers silently ignore it on other input types.`,
1421
+ suggestions: [{
1422
+ title: `Remove "${attribute}"`,
1423
+ description: `"${attribute}" only affects <input> when type is one of: ${allowed}.`
1424
+ }]
1425
+ };
1426
+ }
1427
+ },
1428
+ anchor: { dangerousHref(href) {
1429
+ return {
1430
+ code: DiagnosticCode.HtmlAnchorDangerousHref,
1431
+ category: DiagnosticCategory.HTML,
1432
+ severity: "warning",
1433
+ message: `href="${href}" uses a scheme that executes attacker-controlled content when navigated to. It has been removed.`,
1434
+ rationale: "\"javascript:\", \"data:\", and \"vbscript:\" URLs are a common XSS vector when href comes from untrusted input — none of the three are needed for legitimate navigation."
1435
+ };
1436
+ } }
1437
+ };
1438
+ //#endregion
1439
+ //#region ../../lib/contract/src/diagnostics/input-accessibility.ts
1440
+ function accessibilityFact(input) {
1441
+ return {
1442
+ category: DiagnosticCategory.Accessibility,
1443
+ ...input
1444
+ };
1445
+ }
1446
+ const InputAccessibilityDiagnostics = {
1447
+ missingAccessibleName() {
1448
+ return accessibilityFact({
1449
+ code: DiagnosticCode.A11yInputMissingAccessibleName,
1450
+ severity: "warning",
1451
+ message: "This input has no accessible name. Add an associated <label>, aria-label, or aria-labelledby.",
1452
+ rationale: "Assistive technology announces a form field by its accessible name; without one, users of screen readers cannot tell what the field is for.",
1453
+ suggestions: [{
1454
+ title: "Add aria-label",
1455
+ description: "Set aria-label=\"…\" directly on the input."
1456
+ }, {
1457
+ title: "Add an associated <label>",
1458
+ description: "Wrap the input in a <label>, or point a <label for=\"…\"> at its id."
1459
+ }]
1460
+ });
1461
+ },
1462
+ placeholderIsNotLabel() {
1463
+ return accessibilityFact({
1464
+ code: DiagnosticCode.A11yInputPlaceholderNotLabel,
1465
+ severity: "warning",
1466
+ message: "Placeholder text does not provide an accessible name. Add an associated <label>, aria-label, or aria-labelledby.",
1467
+ rationale: "Placeholder text disappears as users interact with the field and is not treated as the control's accessible name by many assistive technologies.",
1468
+ suggestions: [{
1469
+ title: "Add aria-label",
1470
+ description: "Set aria-label=\"…\" directly on the input."
1471
+ }, {
1472
+ title: "Add an associated <label>",
1473
+ description: "Wrap the input in a <label>, or point a <label for=\"…\"> at its id."
1474
+ }]
1475
+ });
1476
+ },
1477
+ passwordMissingAutocomplete() {
1478
+ return accessibilityFact({
1479
+ code: DiagnosticCode.A11yInputPasswordAutocomplete,
1480
+ severity: "warning",
1481
+ message: "Password inputs should specify an autoComplete value.",
1482
+ rationale: "Without an explicit autocomplete hint, password managers and browsers cannot reliably tell a sign-in field apart from a password-creation field.",
1483
+ suggestions: [{
1484
+ title: "Set autoComplete=\"current-password\"",
1485
+ description: "Use this for sign-in forms."
1486
+ }, {
1487
+ title: "Set autoComplete=\"new-password\"",
1488
+ description: "Use this for sign-up / change-password forms."
1489
+ }]
1490
+ });
1491
+ },
1492
+ requiredReadOnlyConflict() {
1493
+ return accessibilityFact({
1494
+ code: DiagnosticCode.A11yInputRequiredReadOnlyConflict,
1495
+ severity: "warning",
1496
+ message: "\"required\" has no effect while this field is readOnly — the two attributes together usually signal an unintended state.",
1497
+ rationale: "The combination is valid HTML, but a readOnly control cannot be edited, so its \"required\" constraint can never be satisfied or violated through user interaction. Consider using disabled instead of readOnly, or only applying required when the field is editable."
1498
+ });
1499
+ }
1500
+ };
1501
+ //#endregion
1502
+ //#region ../../lib/contract/src/diagnostics/slot.ts
1503
+ const SlotDiagnostics = {
1504
+ exclusive(name) {
1505
+ return {
1506
+ code: DiagnosticCode.SlotExclusive,
1507
+ category: DiagnosticCategory.Contract,
1508
+ component: name,
1509
+ message: `${name}: "as" and "asChild" are mutually exclusive`
1510
+ };
1511
+ },
1512
+ singleChildRequired(name, elementTerm) {
1513
+ return {
1514
+ code: DiagnosticCode.SlotSingleChild,
1515
+ category: DiagnosticCategory.Contract,
1516
+ component: name,
1517
+ message: `${name}: asChild requires a ${elementTerm} child`
1518
+ };
1519
+ },
1520
+ singleChildExceeded(name, elementTerm, count) {
1521
+ return {
1522
+ code: DiagnosticCode.SlotSingleChild,
1523
+ category: DiagnosticCategory.Contract,
1524
+ component: name,
1525
+ message: `${name}: asChild requires exactly one ${elementTerm} child, got ${count}`
1526
+ };
1527
+ },
1528
+ discardedChildren(name, elementTerm, count) {
1529
+ const noun = count === 1 ? "child" : "children";
1530
+ return {
1531
+ code: DiagnosticCode.SlotDiscardedChildren,
1532
+ category: DiagnosticCategory.Contract,
1533
+ component: name,
1534
+ message: `${name}: asChild discarded ${count} non-element ${noun} — only ${elementTerm}s are valid asChild children.`
1535
+ };
1536
+ },
1537
+ renderFnRequired(name, received) {
1538
+ return {
1539
+ code: DiagnosticCode.SlotRenderFn,
1540
+ category: DiagnosticCategory.Contract,
1541
+ component: name,
1542
+ message: `${name}: asChild requires a render function as children, got ${received}`
1543
+ };
1544
+ }
1545
+ };
1546
+ //#endregion
1547
+ //#region ../../lib/contract/src/strict/invariant-base.ts
1548
+ var InvariantBase = class {
1549
+ diagnostics;
1550
+ constructor(diagnostics) {
1551
+ this.diagnostics = diagnostics;
1552
+ }
1553
+ get warnActive() {
1554
+ return this.diagnostics.warnActive;
1555
+ }
1556
+ violate(input) {
1557
+ this.diagnostics.error(input);
1558
+ }
1559
+ warn(input) {
1560
+ this.diagnostics.warn(input);
1561
+ }
1562
+ invariant(condition, input) {
1563
+ if (!condition) this.violate(input);
1564
+ }
1565
+ };
1566
+ //#endregion
1567
+ //#region ../../lib/contract/src/aria/spec/roles/required-properties.ts
1568
+ const REQUIRED_ARIA_PROPERTIES = {
1569
+ combobox: ["aria-expanded"],
1570
+ option: ["aria-selected"],
1571
+ slider: ["aria-valuenow"],
1572
+ scrollbar: ["aria-controls", "aria-valuenow"],
1573
+ spinbutton: ["aria-valuenow"]
1574
+ };
1575
+ //#endregion
1576
+ //#region ../../lib/contract/src/aria/spec/roles/name-required.ts
1577
+ const NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
1578
+ //#endregion
1579
+ //#region ../../lib/contract/src/aria/spec/roles/name-prohibited.ts
1580
+ const NAME_PROHIBITED_ROLES = /* @__PURE__ */ new Set([
1581
+ "caption",
1582
+ "code",
1583
+ "deletion",
1584
+ "emphasis",
1585
+ "generic",
1586
+ "insertion",
1587
+ "none",
1588
+ "paragraph",
1589
+ "presentation",
1590
+ "strong",
1591
+ "subscript",
1592
+ "superscript"
1593
+ ]);
1594
+ const NAME_PROHIBITED_ATTRIBUTES = ["aria-label", "aria-labelledby"];
1595
+ //#endregion
1596
+ //#region ../../lib/contract/src/aria/spec/validators/required-properties-validator.ts
1597
+ const NO_VIOLATIONS$1 = [{ valid: true }];
1598
+ function requiredAttributeByRole(roles, attribute) {
1599
+ return Object.fromEntries([...roles].map((role) => [role, [attribute]]));
1600
+ }
1601
+ /**
1602
+ * Reports missing attributes required by an element's effective ARIA role.
1603
+ *
1604
+ * Shared by validators that differ only in their role-to-attribute mapping and diagnostic
1605
+ * generation.
1606
+ */
1607
+ function checkRequiredAttributes(requirement, { props, effectiveRole }) {
1608
+ if (!effectiveRole) return NO_VIOLATIONS$1;
1609
+ const requiredAttributes = requirement.attributesByRole[effectiveRole];
1610
+ if (!requiredAttributes) return NO_VIOLATIONS$1;
1611
+ const results = [];
1612
+ for (const attribute of requiredAttributes) {
1613
+ if (attribute in props) continue;
1614
+ results.push({
1615
+ valid: false,
1616
+ fixable: false,
1617
+ severity: "warning",
1618
+ attribute,
1619
+ diagnostic: requirement.diagnosticFor(attribute, effectiveRole)
1620
+ });
1621
+ }
1622
+ return results;
1623
+ }
1624
+ //#endregion
1625
+ //#region ../../lib/contract/src/aria/spec/roles/live-region.ts
1626
+ const LIVE_REGION_ROLES = /* @__PURE__ */ new Map([
1627
+ ["alert", "assertive"],
1628
+ ["status", "polite"],
1629
+ ["log", "polite"],
1630
+ ["timer", "off"]
1631
+ ]);
1632
+ const ATOMIC_REQUIREMENTS = requiredAttributeByRole(LIVE_REGION_ROLES.keys(), "aria-atomic");
1633
+ //#endregion
1634
+ //#region ../../lib/contract/src/aria/spec/attributes/aria-value-types.ts
1635
+ const ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
1636
+ ["aria-atomic", { kind: "boolean" }],
1637
+ ["aria-busy", { kind: "boolean" }],
1638
+ ["aria-disabled", { kind: "boolean" }],
1639
+ ["aria-expanded", { kind: "boolean" }],
1640
+ ["aria-hidden", { kind: "boolean" }],
1641
+ ["aria-modal", { kind: "boolean" }],
1642
+ ["aria-multiline", { kind: "boolean" }],
1643
+ ["aria-multiselectable", { kind: "boolean" }],
1644
+ ["aria-readonly", { kind: "boolean" }],
1645
+ ["aria-required", { kind: "boolean" }],
1646
+ ["aria-selected", { kind: "boolean" }],
1647
+ ["aria-checked", { kind: "tristate" }],
1648
+ ["aria-pressed", { kind: "tristate" }],
1649
+ ["aria-valuenow", { kind: "number" }],
1650
+ ["aria-valuemin", { kind: "number" }],
1651
+ ["aria-valuemax", { kind: "number" }],
1652
+ ["aria-level", {
1653
+ kind: "integer",
1654
+ min: 1
1655
+ }],
1656
+ ["aria-posinset", {
1657
+ kind: "integer",
1658
+ min: 1
1659
+ }],
1660
+ ["aria-setsize", {
1661
+ kind: "integer",
1662
+ min: -1
1663
+ }],
1664
+ ["aria-rowcount", {
1665
+ kind: "integer",
1666
+ min: -1
1667
+ }],
1668
+ ["aria-colcount", {
1669
+ kind: "integer",
1670
+ min: -1
1671
+ }],
1672
+ ["aria-rowindex", {
1673
+ kind: "integer",
1674
+ min: 1
1675
+ }],
1676
+ ["aria-colindex", {
1677
+ kind: "integer",
1678
+ min: 1
1679
+ }],
1680
+ ["aria-rowspan", {
1681
+ kind: "integer",
1682
+ min: 0
1683
+ }],
1684
+ ["aria-colspan", {
1685
+ kind: "integer",
1686
+ min: 0
1687
+ }],
1688
+ ["aria-autocomplete", {
1689
+ kind: "enum",
1690
+ values: /* @__PURE__ */ new Set([
1691
+ "inline",
1692
+ "list",
1693
+ "both",
1694
+ "none"
1695
+ ])
1696
+ }],
1697
+ ["aria-current", {
1698
+ kind: "enum",
1699
+ values: /* @__PURE__ */ new Set([
1700
+ "page",
1701
+ "step",
1702
+ "location",
1703
+ "date",
1704
+ "time",
1705
+ "true",
1706
+ "false"
1707
+ ])
1708
+ }],
1709
+ ["aria-haspopup", {
1710
+ kind: "enum",
1711
+ values: /* @__PURE__ */ new Set([
1712
+ "false",
1713
+ "true",
1714
+ "menu",
1715
+ "listbox",
1716
+ "tree",
1717
+ "grid",
1718
+ "dialog"
1719
+ ])
1720
+ }],
1721
+ ["aria-invalid", {
1722
+ kind: "enum",
1723
+ values: /* @__PURE__ */ new Set([
1724
+ "grammar",
1725
+ "false",
1726
+ "spelling",
1727
+ "true"
1728
+ ])
1729
+ }],
1730
+ ["aria-live", {
1731
+ kind: "enum",
1732
+ values: /* @__PURE__ */ new Set([
1733
+ "assertive",
1734
+ "off",
1735
+ "polite"
1736
+ ])
1737
+ }],
1738
+ ["aria-orientation", {
1739
+ kind: "enum",
1740
+ values: /* @__PURE__ */ new Set([
1741
+ "horizontal",
1742
+ "vertical",
1743
+ "undefined"
1744
+ ])
1745
+ }],
1746
+ ["aria-sort", {
1747
+ kind: "enum",
1748
+ values: /* @__PURE__ */ new Set([
1749
+ "ascending",
1750
+ "descending",
1751
+ "none",
1752
+ "other"
1753
+ ])
1754
+ }]
1755
+ ]);
1756
+ //#endregion
1757
+ //#region ../../lib/contract/src/aria/spec/attributes/aria-relevant-tokens.ts
1758
+ const VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set([
1759
+ "additions",
1760
+ "removals",
1761
+ "text",
1762
+ "all"
1763
+ ]);
1764
+ //#endregion
1765
+ //#region ../../lib/contract/src/aria/spec/elements/heading-implicit-levels.ts
1766
+ const HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
1767
+ ["h1", 1],
1768
+ ["h2", 2],
1769
+ ["h3", 3],
1770
+ ["h4", 4],
1771
+ ["h5", 5],
1772
+ ["h6", 6]
1773
+ ]);
1774
+ //#endregion
1775
+ //#region ../../lib/contract/src/aria/spec/elements/focusable.ts
1776
+ function parseTabIndex(raw) {
1777
+ if (typeof raw === "number") return Number.isInteger(raw) ? raw : void 0;
1778
+ if (typeof raw !== "string") return void 0;
1779
+ const trimmed = raw.trim();
1780
+ if (trimmed === "") return void 0;
1781
+ const n = Number(trimmed);
1782
+ return Number.isInteger(n) ? n : void 0;
1783
+ }
1784
+ function isDisabled(props) {
1785
+ const d = props["disabled"];
1786
+ return d !== void 0 && d !== false && d !== null;
1787
+ }
1788
+ function isContentEditable(props) {
1789
+ const c = props["contenteditable"] ?? props["contentEditable"];
1790
+ return c === "" || c === true || c === "true" || c === "plaintext-only";
1791
+ }
1792
+ function hasHref(props) {
1793
+ return props["href"] !== void 0 && props["href"] !== null;
1794
+ }
1795
+ /**
1796
+ * Whether an element is reachable in the sequential keyboard tab order — a native control its
1797
+ * own attributes leave tabbable, `tabindex >= 0`, or `contenteditable`.
1798
+ *
1799
+ * This is **tabbability, not raw focusability**: `tabindex="-1"` is deliberately treated as *not*
1800
+ * qualifying, even though such an element can still receive programmatic focus. The one consumer
1801
+ * (`#checkAriaHiddenOnFocusable`) cares about content a keyboard user can land on, and `../pk`'s
1802
+ * behavior — `aria-hidden` on `<h2 tabindex="-1">` is not flagged — depends on this. If the
1803
+ * contract system ever needs to separate focusability from tabbability, split this into
1804
+ * `isPotentiallyFocusable` / `isPotentiallyTabbable`; until then one predicate with this policy
1805
+ * is enough.
1806
+ *
1807
+ * Absent `tabindex`, `contenteditable` makes anything tabbable; otherwise the answer is per
1808
+ * native tag, gated on that tag's own attributes.
1809
+ */
1810
+ function isPotentiallyFocusable(tag, props) {
1811
+ const tabindex = parseTabIndex(props["tabindex"] ?? props["tabIndex"]);
1812
+ if (tabindex !== void 0) return tabindex >= 0;
1813
+ if (isContentEditable(props)) return true;
1814
+ switch (tag) {
1815
+ case "a":
1816
+ case "area": return hasHref(props);
1817
+ case "input": return props["type"] !== "hidden" && !isDisabled(props);
1818
+ case "button":
1819
+ case "select":
1820
+ case "textarea": return !isDisabled(props);
1821
+ default: return false;
1822
+ }
1823
+ }
1824
+ //#endregion
1825
+ //#region ../../lib/contract/src/aria/aria-policy-engine.ts
1826
+ const NO_VIOLATIONS = [{ valid: true }];
1827
+ const NO_VARIANT_KEYS = /* @__PURE__ */ new Set();
1828
+ function isIntrinsicTag(tag) {
1829
+ return isString(tag);
1830
+ }
1831
+ function omitProp(obj, key) {
1832
+ const { [key]: _, ...rest } = obj;
1833
+ return rest;
1834
+ }
1835
+ function strictNumeric(value) {
1836
+ if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
1837
+ if (typeof value !== "string") return void 0;
1838
+ const trimmed = value.trim();
1839
+ if (trimmed === "") return void 0;
1840
+ const n = Number(trimmed);
1841
+ return Number.isFinite(n) ? n : void 0;
1842
+ }
1843
+ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
1844
+ #extraRules;
1845
+ #variantKeys;
1846
+ #planCache = new LRUCache(100);
1847
+ static #removeAttributeFixCache = /* @__PURE__ */ new Map();
1848
+ constructor(diagnostics, options) {
1849
+ super(diagnostics);
1850
+ this.#extraRules = options?.rules ?? [];
1851
+ this.#variantKeys = options?.variantKeys ?? NO_VARIANT_KEYS;
1852
+ }
1853
+ static #normalizeEmptyRole(tag, props) {
1854
+ if (props.role !== "") return { normalized: false };
1855
+ const d = HtmlDiagnostics.emptyRole(tag);
1856
+ return {
1857
+ normalized: true,
1858
+ result: {
1859
+ props: omitProp(props, "role"),
1860
+ violations: [{
1861
+ message: d.message,
1862
+ diagnostic: d,
1863
+ tag,
1864
+ role: "",
1865
+ attribute: void 0,
1866
+ severity: d.severity,
1867
+ phase: "evaluate"
1868
+ }]
1869
+ }
1870
+ };
1871
+ }
1872
+ static #deriveContext(tag, props, variantKeys = NO_VARIANT_KEYS) {
1873
+ if (!isIntrinsicTag(tag)) return {
1874
+ proceed: false,
1875
+ result: {
1876
+ props,
1877
+ violations: []
1878
+ }
1879
+ };
1880
+ const implicitRole = getImplicitRole(tag, props);
1881
+ const hasRole = isNonNull(implicitRole) || isString(props.role) && props.role.length > 0;
1882
+ const normalized = AriaPolicyEngine.#normalizeEmptyRole(tag, props);
1883
+ const workingProps = normalized.normalized ? normalized.result.props : props;
1884
+ const preExistingViolations = normalized.normalized ? normalized.result.violations : [];
1885
+ const effectiveRole = workingProps.role ?? implicitRole;
1886
+ return {
1887
+ proceed: true,
1888
+ tag,
1889
+ implicitRole,
1890
+ effectiveRole,
1891
+ hasRole,
1892
+ props: workingProps,
1893
+ preExistingViolations,
1894
+ context: {
1895
+ tag,
1896
+ props: workingProps,
1897
+ implicitRole,
1898
+ effectiveRole,
1899
+ variantKeys
1900
+ }
1901
+ };
1902
+ }
1903
+ static #runRules(rules, context) {
1904
+ const violations = [];
1905
+ const fixes = [];
1906
+ iterate.forEach(rules, (rule) => {
1907
+ if (isNonNull(rule.tags) && !rule.tags.includes(context.tag)) return;
1908
+ iterate.forEach(rule(context), (result) => {
1909
+ if (result.valid) return;
1910
+ const { tag, props: { role } } = context;
1911
+ const { message, attribute, severity } = result;
1912
+ const resolvedMessage = message ?? result.diagnostic?.message;
1913
+ const fallbackDiag = isNonNull(resolvedMessage) ? void 0 : AriaDiagnostics.invalidRole(role, tag);
1914
+ violations.push({
1915
+ message: resolvedMessage ?? fallbackDiag.message,
1916
+ tag,
1917
+ role,
1918
+ attribute,
1919
+ severity,
1920
+ phase: "evaluate",
1921
+ ...isNonNull(result.diagnostic) && { diagnostic: result.diagnostic },
1922
+ ...isNonNull(fallbackDiag) && { diagnostic: fallbackDiag }
1923
+ });
1924
+ if (result.fixable) fixes.push(result.fix);
1925
+ });
1926
+ });
1927
+ return {
1928
+ violations,
1929
+ fixes
1930
+ };
1931
+ }
1932
+ static #getRules(context) {
1933
+ if (AriaPolicyEngine.#hasRole(context.props) || isNonNull(context.effectiveRole) && LIVE_REGION_ROLES.has(context.effectiveRole)) return AriaPolicyEngine.#pipeline;
1934
+ return AriaPolicyEngine.#implicitOnlyRules;
1935
+ }
1936
+ static evaluate(tag, props) {
1937
+ const derived = AriaPolicyEngine.#deriveContext(tag, props);
1938
+ if (!derived.proceed) return derived.result;
1939
+ if (!derived.hasRole) return {
1940
+ props: derived.props,
1941
+ violations: [...derived.preExistingViolations]
1942
+ };
1943
+ const { tag: narrowedTag, implicitRole, context, props: workingProps, preExistingViolations } = derived;
1944
+ const { violations, fixes } = AriaPolicyEngine.#runRules(AriaPolicyEngine.#getRules(context), context);
1945
+ return {
1946
+ props: AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes),
1947
+ violations: [...preExistingViolations, ...violations]
1948
+ };
1949
+ }
1950
+ static #evaluateWithRules(tag, props, extraRules, extraProps, variantKeys = NO_VARIANT_KEYS) {
1951
+ const derived = AriaPolicyEngine.#deriveContext(tag, props, variantKeys);
1952
+ if (!derived.proceed) return derived.result;
1953
+ const { tag: narrowedTag, implicitRole, context, props: workingProps, preExistingViolations } = derived;
1954
+ const builtinRules = derived.hasRole ? AriaPolicyEngine.#getRules(context) : [];
1955
+ const builtin = AriaPolicyEngine.#runRules(builtinRules, context);
1956
+ const extraContext = extraProps === void 0 ? context : {
1957
+ ...context,
1958
+ props: extraProps
1959
+ };
1960
+ const extra = AriaPolicyEngine.#runRules(extraRules, extraContext);
1961
+ const violations = [...builtin.violations, ...extra.violations];
1962
+ const fixes = [...builtin.fixes, ...extra.fixes];
1963
+ return {
1964
+ props: AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes, variantKeys),
1965
+ violations: [...preExistingViolations, ...violations]
1966
+ };
1967
+ }
1968
+ report(violations) {
1969
+ iterate.forEach(violations, (v) => {
1970
+ const d = v.diagnostic ?? AriaDiagnostics.fromViolation(v);
1971
+ if (v.severity === "error") this.violate(d);
1972
+ else this.warn(d);
1973
+ });
1974
+ }
1975
+ static #createPlanKey(tag, props) {
1976
+ if (!isIntrinsicTag(tag)) return null;
1977
+ const parts = [tag];
1978
+ if (typeof props.role === "string") parts.push(`role:${props.role}`);
1979
+ if (tag === "input" && typeof props.type === "string") parts.push(`type:${props.type}`);
1980
+ if (tag === "img") parts.push(`alt:${props.alt === "" ? "empty" : "present"}`);
1981
+ const ariaEntries = [];
1982
+ iterate.forEachEntry(props, (k, v) => {
1983
+ if (!k.startsWith("aria-")) return;
1984
+ if (!isString(v) && !isNumber(v) && typeof v !== "boolean") return;
1985
+ ariaEntries.push(`${k}:${String(v)}`);
1986
+ });
1987
+ if (ariaEntries.length > 0) parts.push(...ariaEntries.sort());
1988
+ return parts.join("|");
1989
+ }
1990
+ static #extraRulesKeySuffix(extraRules, props) {
1991
+ const parts = [];
1992
+ for (const rule of extraRules) {
1993
+ const readsProps = rule.readsProps;
1994
+ if (!isNonNull(readsProps)) return null;
1995
+ for (const propKey of readsProps) {
1996
+ const v = props[propKey];
1997
+ if (v !== void 0 && !isString(v) && !isNumber(v) && typeof v !== "boolean") return null;
1998
+ parts.push(`x:${propKey}:${String(v)}`);
1999
+ }
2000
+ }
2001
+ return parts.sort().join("|");
2002
+ }
2003
+ static #computePlan(inputProps, resultProps) {
2004
+ const removals = /* @__PURE__ */ new Set();
2005
+ const updates = {};
2006
+ iterate.forEachKey(inputProps, (key) => {
2007
+ if (!(key in resultProps)) removals.add(key);
2008
+ });
2009
+ iterate.forEachEntry(resultProps, (key, resultVal) => {
2010
+ if (inputProps[key] !== resultVal) updates[key] = resultVal;
2011
+ });
2012
+ return {
2013
+ removals,
2014
+ updates
2015
+ };
2016
+ }
2017
+ static #applyPlan(props, removals, updates) {
2018
+ const hasRemovals = removals.size > 0;
2019
+ const hasUpdates = Object.keys(updates).length > 0;
2020
+ if (!hasRemovals && !hasUpdates) return props;
2021
+ const next = {};
2022
+ iterate.forEachEntry(props, (k, v) => {
2023
+ if (!removals.has(k)) next[k] = v;
2024
+ });
2025
+ Object.assign(next, updates);
2026
+ return next;
2027
+ }
2028
+ validate(tag, props, extraProps) {
2029
+ const ruleProps = extraProps ?? props;
2030
+ const baseKey = AriaPolicyEngine.#createPlanKey(tag, props);
2031
+ let key = baseKey;
2032
+ if (this.#extraRules.length > 0) {
2033
+ const suffix = AriaPolicyEngine.#extraRulesKeySuffix(this.#extraRules, ruleProps);
2034
+ key = isNonNull(baseKey) && isNonNull(suffix) ? `${baseKey}|${suffix}` : null;
2035
+ }
2036
+ if (!isNull(key)) {
2037
+ const cached = this.#planCache.get(key);
2038
+ if (cached !== void 0) {
2039
+ if (cached.violations.length > 0) this.report(cached.violations);
2040
+ return {
2041
+ props: AriaPolicyEngine.#applyPlan(props, cached.removals, cached.updates),
2042
+ violations: cached.violations
2043
+ };
2044
+ }
2045
+ }
2046
+ const result = this.#extraRules.length ? AriaPolicyEngine.#evaluateWithRules(tag, props, this.#extraRules, extraProps, this.#variantKeys) : AriaPolicyEngine.evaluate(tag, props);
2047
+ if (result.violations.length > 0) this.report(result.violations);
2048
+ if (!isNull(key)) {
2049
+ const { removals, updates } = AriaPolicyEngine.#computePlan(props, result.props);
2050
+ const plan = {
2051
+ removals,
2052
+ updates,
2053
+ violations: result.violations
2054
+ };
2055
+ this.#planCache.set(key, plan);
2056
+ }
2057
+ return result;
2058
+ }
2059
+ static #hasRole(props) {
2060
+ return isString(props.role) && props.role.length > 0;
2061
+ }
2062
+ static #applyFixes(tag, implicitRole, props, fixes, variantKeys = NO_VARIANT_KEYS) {
2063
+ if (fixes.length === 0) return props;
2064
+ const sorted = [...fixes].sort((a, b) => (a.priority ?? Infinity) - (b.priority ?? Infinity));
2065
+ let next = props;
2066
+ iterate.forEach(sorted, ({ apply }) => {
2067
+ const fixResult = apply({
2068
+ tag,
2069
+ implicitRole,
2070
+ effectiveRole: next.role ?? implicitRole,
2071
+ props: next,
2072
+ variantKeys
2073
+ });
2074
+ if (fixResult.applied) next = fixResult.next;
2075
+ });
2076
+ return next;
2077
+ }
2078
+ static #removeRole = {
2079
+ kind: "removeRole",
2080
+ apply: ({ props }) => {
2081
+ if (!("role" in props)) return {
2082
+ applied: false,
2083
+ next: props
2084
+ };
2085
+ return {
2086
+ applied: true,
2087
+ next: omitProp(props, "role"),
2088
+ previous: props
2089
+ };
2090
+ }
2091
+ };
2092
+ static #makeRemoveAttributeFix(attr) {
2093
+ const cached = AriaPolicyEngine.#removeAttributeFixCache.get(attr);
2094
+ if (cached) return cached;
2095
+ const fix = {
2096
+ kind: "removeAttribute",
2097
+ attribute: attr,
2098
+ apply: ({ props }) => {
2099
+ if (!(attr in props)) return {
2100
+ applied: false,
2101
+ next: props
2102
+ };
2103
+ return {
2104
+ applied: true,
2105
+ next: omitProp(props, attr),
2106
+ previous: props
2107
+ };
2108
+ }
2109
+ };
2110
+ AriaPolicyEngine.#removeAttributeFixCache.set(attr, fix);
2111
+ return fix;
2112
+ }
2113
+ static #pipeline = [
2114
+ AriaPolicyEngine.#checkInvalidRoleOverride,
2115
+ AriaPolicyEngine.#checkRedundantRole,
2116
+ AriaPolicyEngine.#checkStandaloneRegion,
2117
+ AriaPolicyEngine.#checkAriaAttributeValues,
2118
+ AriaPolicyEngine.#checkInvalidAriaAttributes,
2119
+ AriaPolicyEngine.#checkNameProhibitedRoles,
2120
+ AriaPolicyEngine.#checkRequiredAriaProperties,
2121
+ AriaPolicyEngine.#checkNameRequiredRoles,
2122
+ AriaPolicyEngine.#checkRedundantAriaLevel,
2123
+ AriaPolicyEngine.#checkMissingLiveRegion,
2124
+ AriaPolicyEngine.#checkMissingAtomic,
2125
+ AriaPolicyEngine.#checkInvalidAriaRelevant,
2126
+ AriaPolicyEngine.#checkAriaHiddenOnFocusable,
2127
+ AriaPolicyEngine.#checkPresentationalAriaAttributes
2128
+ ];
2129
+ static #implicitOnlyRules = [
2130
+ AriaPolicyEngine.#checkAriaAttributeValues,
2131
+ AriaPolicyEngine.#checkInvalidAriaAttributes,
2132
+ AriaPolicyEngine.#checkNameProhibitedRoles,
2133
+ AriaPolicyEngine.#checkNameRequiredRoles,
2134
+ AriaPolicyEngine.#checkRedundantAriaLevel,
2135
+ AriaPolicyEngine.#checkAriaHiddenOnFocusable,
2136
+ AriaPolicyEngine.#checkPresentationalAriaAttributes
2137
+ ];
2138
+ static #checkInvalidRoleOverride({ tag, props, implicitRole }) {
2139
+ const role = props.role;
2140
+ if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS;
2141
+ if (isStrongImplicitRole(tag) && role === "region") {
2142
+ const diagnostic = HtmlDiagnostics.implicitRoleOverride(tag, implicitRole, role);
2143
+ return [{
2144
+ valid: false,
2145
+ fixable: true,
2146
+ severity: diagnostic.severity,
2147
+ fix: AriaPolicyEngine.#removeRole,
2148
+ diagnostic
2149
+ }];
2150
+ }
2151
+ return NO_VIOLATIONS;
2152
+ }
2153
+ static #checkRedundantRole({ tag, props, implicitRole }) {
2154
+ const role = props.role;
2155
+ if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS;
2156
+ const diagnostic = HtmlDiagnostics.implicitRoleRedundant(tag, implicitRole);
2157
+ return [{
2158
+ valid: false,
2159
+ fixable: true,
2160
+ severity: diagnostic.severity,
2161
+ fix: AriaPolicyEngine.#removeRole,
2162
+ diagnostic
2163
+ }];
2164
+ }
2165
+ static #checkStandaloneRegion({ tag, props, implicitRole }) {
2166
+ if (props.role !== "region") return NO_VIOLATIONS;
2167
+ if (!hasStandaloneRole(tag)) return NO_VIOLATIONS;
2168
+ const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
2169
+ return [{
2170
+ valid: false,
2171
+ fixable: true,
2172
+ severity: diagnostic.severity,
2173
+ fix: AriaPolicyEngine.#removeRole,
2174
+ diagnostic
2175
+ }];
2176
+ }
2177
+ static #checkInvalidAriaAttributes({ tag, props, effectiveRole }) {
2178
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
2179
+ const results = [];
2180
+ iterate.forEachEntry(props, (key) => {
2181
+ if (!key.startsWith("aria-")) return;
2182
+ if (isGlobalAriaAttribute(key)) return;
2183
+ if (isAriaAttributeValidForRole(key, effectiveRole)) return;
2184
+ results.push({
2185
+ valid: false,
2186
+ severity: "warning",
2187
+ fixable: true,
2188
+ attribute: key,
2189
+ diagnostic: AriaDiagnostics.attributeInvalid(key, effectiveRole ?? tag),
2190
+ fix: AriaPolicyEngine.#makeRemoveAttributeFix(key)
2191
+ });
2192
+ });
2193
+ return results;
2194
+ }
2195
+ static #isValidAriaValue(value, type) {
2196
+ switch (type.kind) {
2197
+ case "boolean": return value === "true" || value === "false" || value === true || value === false;
2198
+ case "tristate": return value === "true" || value === "false" || value === "mixed" || value === true || value === false;
2199
+ case "number": return strictNumeric(value) !== void 0;
2200
+ case "integer": {
2201
+ const n = strictNumeric(value);
2202
+ if (n === void 0 || !Number.isInteger(n)) return false;
2203
+ if (type.min !== void 0 && n < type.min) return false;
2204
+ if (type.max !== void 0 && n > type.max) return false;
2205
+ return true;
2206
+ }
2207
+ case "enum": return typeof value === "string" && type.values.has(value);
2208
+ }
2209
+ }
2210
+ static #describeExpected(type) {
2211
+ switch (type.kind) {
2212
+ case "boolean": return "\"true\" or \"false\"";
2213
+ case "tristate": return "\"true\", \"false\", or \"mixed\"";
2214
+ case "number": return "a finite number";
2215
+ case "integer": {
2216
+ const parts = ["an integer"];
2217
+ if (type.min !== void 0 && type.max !== void 0) parts.push(`between ${type.min} and ${type.max}`);
2218
+ else if (type.min !== void 0) parts.push(`≥ ${type.min}`);
2219
+ else if (type.max !== void 0) parts.push(`≤ ${type.max}`);
2220
+ return parts.join(" ");
2221
+ }
2222
+ case "enum": return [...type.values].map((v) => `"${v}"`).join(", ");
2223
+ }
2224
+ }
2225
+ static #checkAriaAttributeValues({ props, effectiveRole }) {
2226
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
2227
+ const results = [];
2228
+ iterate.forEachEntry(props, (key, value) => {
2229
+ if (!key.startsWith("aria-")) return;
2230
+ const type = ARIA_VALUE_TYPES.get(key);
2231
+ if (!isNonNull(type)) return;
2232
+ if (AriaPolicyEngine.#isValidAriaValue(value, type)) return;
2233
+ results.push({
2234
+ valid: false,
2235
+ fixable: true,
2236
+ severity: "warning",
2237
+ attribute: key,
2238
+ diagnostic: AriaDiagnostics.invalidAttributeValue(key, value, AriaPolicyEngine.#describeExpected(type)),
2239
+ fix: AriaPolicyEngine.#makeRemoveAttributeFix(key)
2240
+ });
2241
+ });
2242
+ return results;
2243
+ }
2244
+ static #checkRedundantAriaLevel({ tag, props, effectiveRole }) {
2245
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
2246
+ const implicitLevel = HEADING_IMPLICIT_LEVELS.get(tag);
2247
+ if (!isNonNull(implicitLevel)) return NO_VIOLATIONS;
2248
+ const raw = props["aria-level"];
2249
+ if (!isNonNull(raw)) return NO_VIOLATIONS;
2250
+ const n = strictNumeric(raw);
2251
+ if (n === void 0 || n !== implicitLevel) return NO_VIOLATIONS;
2252
+ return [{
2253
+ valid: false,
2254
+ fixable: true,
2255
+ severity: "warning",
2256
+ attribute: "aria-level",
2257
+ diagnostic: AriaDiagnostics.redundantAriaLevel(tag, implicitLevel),
2258
+ fix: AriaPolicyEngine.#makeRemoveAttributeFix("aria-level")
2259
+ }];
2260
+ }
2261
+ static #checkNameProhibitedRoles({ props, effectiveRole }) {
2262
+ if (!effectiveRole || effectiveRole === "none" || effectiveRole === "presentation" || !NAME_PROHIBITED_ROLES.has(effectiveRole)) return NO_VIOLATIONS;
2263
+ const results = [];
2264
+ for (const key of NAME_PROHIBITED_ATTRIBUTES) {
2265
+ if (!(key in props)) continue;
2266
+ results.push({
2267
+ valid: false,
2268
+ fixable: true,
2269
+ severity: "warning",
2270
+ attribute: key,
2271
+ diagnostic: AriaDiagnostics.nameProhibited(key, effectiveRole),
2272
+ fix: AriaPolicyEngine.#makeRemoveAttributeFix(key)
2273
+ });
2274
+ }
2275
+ return results;
2276
+ }
2277
+ static #checkNameRequiredRoles({ tag, props, effectiveRole }) {
2278
+ if (!effectiveRole || !NAME_REQUIRED_ROLES.has(effectiveRole)) return NO_VIOLATIONS;
2279
+ if ("aria-label" in props || "aria-labelledby" in props) return NO_VIOLATIONS;
2280
+ if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return NO_VIOLATIONS;
2281
+ return [{
2282
+ valid: false,
2283
+ fixable: false,
2284
+ severity: "warning",
2285
+ diagnostic: AriaDiagnostics.missingAccessibleName(tag)
2286
+ }];
2287
+ }
2288
+ static #requiredAriaPropertiesRule = {
2289
+ attributesByRole: REQUIRED_ARIA_PROPERTIES,
2290
+ diagnosticFor: (attribute, role) => AriaDiagnostics.requiredProperty(attribute, role)
2291
+ };
2292
+ static #checkRequiredAriaProperties(context) {
2293
+ if (!isNonNull(context.props.role)) return NO_VIOLATIONS;
2294
+ return checkRequiredAttributes(AriaPolicyEngine.#requiredAriaPropertiesRule, context);
2295
+ }
2296
+ static #checkAriaHiddenOnFocusable({ tag, props }) {
2297
+ if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return NO_VIOLATIONS;
2298
+ if (!isPotentiallyFocusable(tag, props)) return NO_VIOLATIONS;
2299
+ return [{
2300
+ valid: false,
2301
+ fixable: false,
2302
+ severity: "error",
2303
+ attribute: "aria-hidden",
2304
+ diagnostic: AriaDiagnostics.ariaHiddenOnFocusable(tag)
2305
+ }];
2306
+ }
2307
+ static #checkPresentationalAriaAttributes({ tag, props, effectiveRole }) {
2308
+ if (effectiveRole !== "none" && effectiveRole !== "presentation") return NO_VIOLATIONS;
2309
+ const results = [];
2310
+ iterate.forEachEntry(props, (key) => {
2311
+ if (!key.startsWith("aria-")) return;
2312
+ if (key === "aria-hidden") return;
2313
+ results.push({
2314
+ valid: false,
2315
+ fixable: true,
2316
+ severity: "warning",
2317
+ attribute: key,
2318
+ diagnostic: AriaDiagnostics.attributeOnPresentational(key, tag),
2319
+ fix: AriaPolicyEngine.#makeRemoveAttributeFix(key)
2320
+ });
2321
+ });
2322
+ return results;
2323
+ }
2324
+ static #checkMissingLiveRegion({ effectiveRole, props }) {
2325
+ if (!effectiveRole) return NO_VIOLATIONS;
2326
+ const impliedLive = LIVE_REGION_ROLES.get(effectiveRole);
2327
+ if (!impliedLive) return NO_VIOLATIONS;
2328
+ if ("aria-live" in props) return NO_VIOLATIONS;
2329
+ return [{
2330
+ valid: false,
2331
+ fixable: true,
2332
+ severity: "warning",
2333
+ fix: {
2334
+ kind: "injectLive",
2335
+ attribute: "aria-live",
2336
+ apply: (ctx) => ({
2337
+ applied: true,
2338
+ next: {
2339
+ ...ctx.props,
2340
+ "aria-live": impliedLive
2341
+ },
2342
+ previous: ctx.props
2343
+ })
2344
+ },
2345
+ diagnostic: AriaDiagnostics.missingLiveRegion(effectiveRole, impliedLive)
2346
+ }];
2347
+ }
2348
+ static #missingAtomicRule = {
2349
+ attributesByRole: ATOMIC_REQUIREMENTS,
2350
+ diagnosticFor: (_attribute, role) => AriaDiagnostics.missingAtomic(role)
2351
+ };
2352
+ static #checkMissingAtomic(context) {
2353
+ return checkRequiredAttributes(AriaPolicyEngine.#missingAtomicRule, context);
2354
+ }
2355
+ static #normalizeRelevantAllFix = {
2356
+ kind: "normalizeRelevantAll",
2357
+ apply: ({ props: p }) => ({
2358
+ applied: true,
2359
+ next: {
2360
+ ...p,
2361
+ "aria-relevant": "all"
2362
+ },
2363
+ previous: p
2364
+ })
2365
+ };
2366
+ static #checkInvalidAriaRelevant({ props }) {
2367
+ const relevant = props["aria-relevant"];
2368
+ if (relevant === void 0) return NO_VIOLATIONS;
2369
+ if (typeof relevant !== "string") return NO_VIOLATIONS;
2370
+ const tokens = relevant.trim().split(/\s+/);
2371
+ const invalid = tokens.filter((t) => !VALID_RELEVANT_TOKENS.has(t));
2372
+ if (invalid.length > 0) return [{
2373
+ valid: false,
2374
+ fixable: true,
2375
+ severity: "warning",
2376
+ attribute: "aria-relevant",
2377
+ diagnostic: AriaDiagnostics.relevantInvalidTokens(invalid),
2378
+ fix: AriaPolicyEngine.#makeRemoveAttributeFix("aria-relevant")
2379
+ }];
2380
+ if (tokens.includes("all") && tokens.length > 1) return [{
2381
+ valid: false,
2382
+ fixable: true,
2383
+ severity: "warning",
2384
+ attribute: "aria-relevant",
2385
+ diagnostic: AriaDiagnostics.relevantSuperseded(),
2386
+ fix: AriaPolicyEngine.#normalizeRelevantAllFix
2387
+ }];
2388
+ return NO_VIOLATIONS;
2389
+ }
2390
+ };
2391
+ //#endregion
2392
+ //#region ../../lib/contract/src/aria/factories.ts
2393
+ /**
2394
+ * Builds a correctly-literal-typed `fixable: false` `AriaResult`. Exists so a rule author can
2395
+ * extract shared branch logic (severity/attribute/message computed once, reused across multiple
2396
+ * `return`s) without TypeScript silently widening `valid: false`/`fixable: false` to `boolean`
2397
+ * the moment those values leave an object-literal-in-return-position — the widening only happens
2398
+ * on plain object literals; a function's declared return type narrows unconditionally.
2399
+ */
2400
+ function invalidWithoutFix(input) {
2401
+ return {
2402
+ valid: false,
2403
+ fixable: false,
2404
+ severity: input.severity,
2405
+ ...isDefined(input.attribute) && { attribute: input.attribute },
2406
+ ...isDefined(input.message) && { message: input.message },
2407
+ ...isDefined(input.diagnostic) && { diagnostic: input.diagnostic }
2408
+ };
2409
+ }
2410
+ /** Same as {@link invalidWithoutFix}, for the `fixable: true` branch — requires a `fix`. */
2411
+ function invalidWithFix(input) {
2412
+ return {
2413
+ valid: false,
2414
+ fixable: true,
2415
+ severity: input.severity,
2416
+ ...isDefined(input.attribute) && { attribute: input.attribute },
2417
+ ...isDefined(input.message) && { message: input.message },
2418
+ ...isDefined(input.diagnostic) && { diagnostic: input.diagnostic },
2419
+ fix: input.fix
2420
+ };
2421
+ }
2422
+ function removeProp(props, key) {
2423
+ const next = { ...props };
2424
+ delete next[key];
2425
+ return next;
2426
+ }
2427
+ /**
2428
+ * Builds an `AriaFix` that strips a single attribute — the shape `dangerousHrefRule`-style
2429
+ * "strip this attribute when it's dangerous/redundant" rules need. A no-op (`applied: false`) when
2430
+ * the attribute isn't present, so applying the fix twice (or applying it when nothing triggered it)
2431
+ * is always safe. Frozen — a fix is a value object; nothing should mutate `kind`/`attribute`/`apply`
2432
+ * after construction.
2433
+ */
2434
+ function removeAttributeFix(attribute) {
2435
+ return Object.freeze({
2436
+ kind: "removeAttribute",
2437
+ attribute,
2438
+ apply: ({ props }) => {
2439
+ if (!(attribute in props)) return {
2440
+ applied: false,
2441
+ next: props
2442
+ };
2443
+ return {
2444
+ applied: true,
2445
+ next: removeProp(props, attribute),
2446
+ previous: props
2447
+ };
2448
+ }
2449
+ });
2450
+ }
2451
+ function defineRuleMetadata(rule, metadata) {
2452
+ const descriptors = {};
2453
+ for (const key of Object.keys(metadata)) descriptors[key] = {
2454
+ value: metadata[key],
2455
+ enumerable: false
2456
+ };
2457
+ Object.defineProperties(rule, descriptors);
2458
+ return rule;
2459
+ }
2460
+ /**
2461
+ * Convenience factory for the single most common `enforcement.aria`/`enforcement.rules` shape:
2462
+ * "strip this attribute when some condition on the element's own props holds" — covers
2463
+ * security-style guards (a dangerous URL scheme on `href`) and redundant-attribute rules alike,
2464
+ * without hand-writing the rule function, the `AriaFix`, and the `invalidWithFix` call each time.
2465
+ * A rule with no fix (a warn-only advisory) still needs the raw `AriaRule` shape directly — this
2466
+ * factory is deliberately scoped to the strip-on-match case, not a general rule builder.
2467
+ */
2468
+ function createRemoveAttributeRule(attribute, options) {
2469
+ const { when, severity = "warning", message, diagnostic, readsProps, tags } = options;
2470
+ const fix = removeAttributeFix(attribute);
2471
+ const rule = (context) => {
2472
+ if (!when(context)) return [];
2473
+ return [invalidWithFix({
2474
+ severity,
2475
+ attribute,
2476
+ ...isDefined(message) && { message },
2477
+ ...isDefined(diagnostic) && { diagnostic: diagnostic(context) },
2478
+ fix
2479
+ })];
2480
+ };
2481
+ return defineRuleMetadata(rule, {
2482
+ ...isDefined(readsProps) && { readsProps },
2483
+ ...isDefined(tags) && { tags }
2484
+ });
2485
+ }
2486
+ //#endregion
2487
+ //#region ../../lib/contract/src/children/get-type-name.ts
2488
+ function getTypeName(value) {
2489
+ if (value === null) return "null";
2490
+ if (value === void 0) return "undefined";
2491
+ const primitive = typeof value;
2492
+ if (primitive !== "object") return primitive;
2493
+ const name = value.constructor?.name;
2494
+ return isString(name) && name !== "Object" ? name : "object";
2495
+ }
2496
+ //#endregion
2497
+ //#region ../../lib/contract/src/children/normalize-child-rule.ts
2498
+ function normalizeCardinality(input, impliesSingleton) {
2499
+ const min = input?.min ?? 0;
2500
+ const max = input?.max ?? (impliesSingleton ? 1 : Infinity);
2501
+ if (min === 0 && max === Infinity) return { kind: "unbounded" };
2502
+ if (min > max) throw new RangeError(`normalizeChildRule: min (${min}) cannot exceed max (${max})`);
2503
+ return {
2504
+ kind: "bounded",
2505
+ min,
2506
+ max
2507
+ };
2508
+ }
2509
+ function normalizeChildRule(rule) {
2510
+ const position = rule.position ?? "any";
2511
+ const impliesSingleton = position === "first" || position === "last";
2512
+ const cardinality = normalizeCardinality(rule.cardinality, impliesSingleton);
2513
+ if (impliesSingleton && (cardinality.kind === "unbounded" || cardinality.max > 1)) throw new RangeError(`normalizeChildRule: rule "${rule.name}" sets position="${position}" with an unbounded or >1 max — position="first"|"last" implies max=1.`);
2514
+ return {
2515
+ ...rule,
2516
+ position,
2517
+ cardinality
2518
+ };
2519
+ }
2520
+ //#endregion
2521
+ //#region ../../lib/contract/src/children/rules-matcher.ts
2522
+ /** Reads child.type without assuming a framework — works for React elements and Vue vnodes. */
2523
+ function getChildType(child) {
2524
+ if (!isObject(child) || !("type" in child)) return void 0;
2525
+ return child.type;
2526
+ }
2527
+ /**
2528
+ * Separates rules into an O(1) type-dispatch index and a linear-scan remainder.
2529
+ *
2530
+ * Rules without a type field go straight to untypedIndices.
2531
+ * Rules whose type is shared with another rule are removed from the index and
2532
+ * demoted to untypedIndices — the linear path handles ambiguous multi-matches
2533
+ * correctly, the index cannot.
2534
+ *
2535
+ * INVARIANT: a rule with a unique `type` matches on `child.type === rule.type` alone — its
2536
+ * `match` predicate is **not** called on the fast path. A rule whose `match` needs to narrow
2537
+ * further than its `type` (e.g. also on `child.props`) must therefore either omit `type` (linear
2538
+ * path) or share it with another rule (demoted). See `ChildRuleInput`.
2539
+ */
2540
+ function buildPartialIndex(rules) {
2541
+ const typeIndex = /* @__PURE__ */ new Map();
2542
+ const duplicateTypes = /* @__PURE__ */ new Set();
2543
+ const untypedIndices = [];
2544
+ iterate.forEach(rules, (rule, ri) => {
2545
+ const t = rule.type;
2546
+ if (t === void 0) untypedIndices.push(ri);
2547
+ else if (typeIndex.has(t)) duplicateTypes.add(t);
2548
+ else typeIndex.set(t, ri);
2549
+ });
2550
+ if (duplicateTypes.size > 0) {
2551
+ iterate.forEachSet(duplicateTypes, (t) => {
2552
+ typeIndex.delete(t);
2553
+ });
2554
+ iterate.forEach(rules, (rule, ri) => {
2555
+ if (duplicateTypes.has(rule.type)) untypedIndices.push(ri);
2556
+ });
2557
+ }
2558
+ return {
2559
+ typeIndex,
2560
+ untypedIndices
2561
+ };
2562
+ }
2563
+ var RuleMatcher = class {
2564
+ #rules;
2565
+ #typeIndex;
2566
+ #untypedIndices;
2567
+ constructor(rules) {
2568
+ this.#rules = rules;
2569
+ const { typeIndex, untypedIndices } = buildPartialIndex(rules);
2570
+ this.#typeIndex = typeIndex;
2571
+ this.#untypedIndices = untypedIndices;
2572
+ }
2573
+ match(children) {
2574
+ const forward = /* @__PURE__ */ new Map();
2575
+ const reverse = /* @__PURE__ */ new Map();
2576
+ const unexpectedIndices = /* @__PURE__ */ new Set();
2577
+ const ambiguousIndices = /* @__PURE__ */ new Set();
2578
+ iterate.forEach(this.#rules, (_, ri) => {
2579
+ reverse.set(ri, /* @__PURE__ */ new Set());
2580
+ });
2581
+ iterate.forEach(children, (child, ci) => {
2582
+ const t = getChildType(child);
2583
+ if (t !== void 0) {
2584
+ const ri = this.#typeIndex.get(t);
2585
+ if (ri !== void 0) {
2586
+ let childEntry = forward.get(ci);
2587
+ if (!childEntry) {
2588
+ childEntry = /* @__PURE__ */ new Set();
2589
+ forward.set(ci, childEntry);
2590
+ }
2591
+ childEntry.add(ri);
2592
+ reverse.get(ri).add(ci);
2593
+ }
2594
+ }
2595
+ iterate.forEach(this.#untypedIndices, (ri) => {
2596
+ if (!this.#rules[ri].match(child)) return;
2597
+ let childEntry = forward.get(ci);
2598
+ if (!childEntry) {
2599
+ childEntry = /* @__PURE__ */ new Set();
2600
+ forward.set(ci, childEntry);
2601
+ }
2602
+ childEntry.add(ri);
2603
+ reverse.get(ri).add(ci);
2604
+ });
2605
+ const entry = forward.get(ci);
2606
+ if (!entry) unexpectedIndices.add(ci);
2607
+ else if (entry.size > 1) ambiguousIndices.add(ci);
2608
+ });
2609
+ return {
2610
+ matrix: { childToRules: {
2611
+ forward,
2612
+ reverse
2613
+ } },
2614
+ unexpectedIndices,
2615
+ ambiguousIndices
2616
+ };
2617
+ }
2618
+ };
2619
+ //#endregion
2620
+ //#region ../../lib/contract/src/children/rule-validator.ts
2621
+ var RuleValidator = class RuleValidator extends InvariantBase {
2622
+ #context;
2623
+ constructor(context, diagnostics) {
2624
+ super(diagnostics);
2625
+ this.#context = context;
2626
+ }
2627
+ validate(rules, matrix, childCount) {
2628
+ const firstIndex = 0;
2629
+ const lastIndex = childCount - 1;
2630
+ iterate.forEach(rules, (rule, ri) => {
2631
+ const matches = matrix.childToRules.reverse.get(ri);
2632
+ const matchCount = matches.size;
2633
+ this.#validateCardinality(rule, matchCount);
2634
+ if (matchCount === 0) return;
2635
+ this.#validatePositions(rule, matches, firstIndex, lastIndex);
2636
+ });
2637
+ }
2638
+ #validateCardinality(rule, matchCount) {
2639
+ const { cardinality, name } = rule;
2640
+ if (cardinality.kind !== "bounded") return;
2641
+ const { min, max } = cardinality;
2642
+ if (matchCount < min) {
2643
+ this.violate(ContractDiagnostics.cardinalityMin(name, min, this.#context));
2644
+ return;
2645
+ }
2646
+ if (matchCount > max) this.violate(ContractDiagnostics.cardinalityMax(name, max, this.#context));
2647
+ }
2648
+ #validatePositions(rule, matches, firstIndex, lastIndex) {
2649
+ const { name, position } = rule;
2650
+ iterate.forEachSet(matches, (index) => {
2651
+ if (RuleValidator.#isValidPosition(index, position, firstIndex, lastIndex)) return;
2652
+ this.violate(ContractDiagnostics.positionViolation(name, position, index, this.#context));
2653
+ });
2654
+ }
2655
+ static #isValidPosition(matchIndex, position, firstIndex, lastIndex) {
2656
+ switch (position) {
2657
+ case "first": return matchIndex === firstIndex;
2658
+ case "last": return matchIndex === lastIndex;
2659
+ case "any": return true;
2660
+ default: return assertNever(position);
2661
+ }
2662
+ }
2663
+ };
2664
+ //#endregion
2665
+ //#region ../../lib/contract/src/children/children-evaluator.ts
2666
+ const isTextLike = (child) => isString(child) || isNumber(child);
2667
+ var ChildrenEvaluator = class extends InvariantBase {
2668
+ #context;
2669
+ /** Rules with a static cardinality — normalized once, reused on every evaluate() call. */
2670
+ #staticRules;
2671
+ /** Rules whose cardinality is `dynamic(...)` — re-resolved against a ChildRuleContext per call. */
2672
+ #dynamicRuleInputs;
2673
+ #matcher;
2674
+ #ruleValidator;
2675
+ #exclusiveChildren;
2676
+ #allowText;
2677
+ constructor(rules, diagnostics, context = "Component", options = {}) {
2678
+ super(diagnostics);
2679
+ this.#context = context;
2680
+ this.#exclusiveChildren = options.exclusiveChildren ?? false;
2681
+ this.#allowText = options.allowText ?? true;
2682
+ const staticRuleInputs = [];
2683
+ const dynamicRuleInputs = [];
2684
+ iterate.forEach(rules, (rule) => {
2685
+ if (isDynamicRule(rule.cardinality)) dynamicRuleInputs.push(rule);
2686
+ else staticRuleInputs.push(rule);
2687
+ });
2688
+ this.#dynamicRuleInputs = dynamicRuleInputs;
2689
+ this.#staticRules = staticRuleInputs.map((r) => normalizeChildRule(r));
2690
+ this.#ruleValidator = new RuleValidator(context, diagnostics);
2691
+ this.#matcher = this.#dynamicRuleInputs.length === 0 ? new RuleMatcher(this.#staticRules) : void 0;
2692
+ }
2693
+ /**
2694
+ * @param context Required when any rule has a `dynamic(...)` cardinality —
2695
+ * supplies the resolved tag/props those rules are evaluated against.
2696
+ */
2697
+ evaluate(children, context) {
2698
+ if (!this.warnActive) return;
2699
+ const { rules, matcher } = this.#resolveRules(context);
2700
+ const { matrix, unexpectedIndices: rawUnexpectedIndices, ambiguousIndices } = matcher.match(children);
2701
+ this.#ruleValidator.validate(rules, matrix, children.length);
2702
+ const unexpectedIndices = new Set([...rawUnexpectedIndices].filter((ci) => isTextLike(children[ci]) ? this.#allowText === false : this.#exclusiveChildren));
2703
+ if (unexpectedIndices.size === 0 && ambiguousIndices.size === 0) return;
2704
+ const violating = [...unexpectedIndices, ...ambiguousIndices].sort((a, b) => a - b);
2705
+ iterate.forEach(violating, (ci) => {
2706
+ const typeName = getTypeName(children[ci]);
2707
+ if (unexpectedIndices.has(ci)) this.violate(ContractDiagnostics.unexpectedChild(typeName, ci, this.#context));
2708
+ else {
2709
+ const names = [...matrix.childToRules.forward.get(ci)].map((ri) => rules[ri]?.name ?? `#${ri}`);
2710
+ this.violate(ContractDiagnostics.ambiguousChild(typeName, ci, names, this.#context));
2711
+ }
2712
+ });
2713
+ }
2714
+ #resolveRules(context) {
2715
+ if (this.#dynamicRuleInputs.length === 0) return {
2716
+ rules: this.#staticRules,
2717
+ matcher: this.#matcher
2718
+ };
2719
+ if (context === void 0) throw new RangeError(`ChildrenEvaluator [${this.#context}]: rule(s) have a dynamic(...) cardinality — evaluate() requires a context argument to resolve them.`);
2720
+ const resolvedDynamicRules = this.#dynamicRuleInputs.map((r) => {
2721
+ const cardinality = resolveRule(r.cardinality, context);
2722
+ return normalizeChildRule({
2723
+ ...r,
2724
+ cardinality
2725
+ });
2726
+ });
2727
+ const rules = [...this.#staticRules, ...resolvedDynamicRules];
2728
+ return {
2729
+ rules,
2730
+ matcher: new RuleMatcher(rules)
2731
+ };
2732
+ }
2733
+ };
2734
+ //#endregion
2735
+ //#region ../../lib/contract/src/props/make-state-normalizer.ts
2736
+ /**
2737
+ * Builds one of the eight built-in state-prop normalizers. A truthy state injects the `aria-*` /
2738
+ * `data-*` pair; the false state is handled per `falseState`; an explicitly supplied `aria-*` /
2739
+ * `data-*` value is never overwritten (the normalizer only fills when the key is `undefined`).
2740
+ */
2741
+ function makeStateNormalizer({ state, aria, data, falseState = "omit" }) {
2742
+ return (props) => {
2743
+ const value = props[state];
2744
+ if (falseState === "synthesize" ? isNullish(value) : !value) return {};
2745
+ const out = {};
2746
+ if (isUndefined(props[aria])) out[aria] = value ? "true" : "false";
2747
+ if (value && isUndefined(props[data])) out[data] = "";
2748
+ return out;
2749
+ };
2750
+ }
2751
+ makeStateNormalizer({
2752
+ state: "active",
2753
+ aria: "aria-current",
2754
+ data: "data-active"
2755
+ });
2756
+ const disabledProps = makeStateNormalizer({
2757
+ state: "disabled",
2758
+ aria: "aria-disabled",
2759
+ data: "data-disabled"
2760
+ });
2761
+ makeStateNormalizer({
2762
+ state: "expanded",
2763
+ aria: "aria-expanded",
2764
+ data: "data-expanded",
2765
+ falseState: "synthesize"
2766
+ });
2767
+ const invalidProps = makeStateNormalizer({
2768
+ state: "invalid",
2769
+ aria: "aria-invalid",
2770
+ data: "data-invalid"
2771
+ });
2772
+ makeStateNormalizer({
2773
+ state: "loading",
2774
+ aria: "aria-busy",
2775
+ data: "data-loading"
2776
+ });
2777
+ makeStateNormalizer({
2778
+ state: "pressed",
2779
+ aria: "aria-pressed",
2780
+ data: "data-pressed",
2781
+ falseState: "synthesize"
2782
+ });
2783
+ const readonlyProps = makeStateNormalizer({
2784
+ state: "readOnly",
2785
+ aria: "aria-readonly",
2786
+ data: "data-readonly"
2787
+ });
2788
+ makeStateNormalizer({
2789
+ state: "selected",
2790
+ aria: "aria-selected",
2791
+ data: "data-selected",
2792
+ falseState: "synthesize"
2793
+ });
2794
+ //#endregion
2795
+ //#region ../core/src/html/contracts/categories.ts
2796
+ /**
2797
+ * HTML content-model tag groups shared across multiple contracts.
2798
+ *
2799
+ * Shared here means referenced by more than one contract, or by the top-level
2800
+ * tag → contract map in `index.ts`. Contract-specific tag lists that only one
2801
+ * module needs (e.g. `pContract`'s block-level blocklist) stay local to that module.
2802
+ */
2803
+ /**
2804
+ * HTML elements classified as metadata content.
2805
+ *
2806
+ * Metadata content (`<script>` and `<template>`) is permitted in almost every
2807
+ * HTML content model and is therefore accepted by many contracts.
2808
+ */
2809
+ const METADATA_TAGS = ["script", "template"];
2810
+ /**
2811
+ * Elements whose content model is character data only.
2812
+ *
2813
+ * Includes raw text elements (`<script>`, `<style>`), escapable raw text
2814
+ * elements (`<textarea>`, `<title>`), and `<option>`.
2815
+ */
2816
+ const TEXT_ONLY_TAGS = [
2817
+ "option",
2818
+ "script",
2819
+ "style",
2820
+ "textarea",
2821
+ "title"
2822
+ ];
2823
+ /**
2824
+ * HTML elements with implicit landmark roles.
2825
+ *
2826
+ * `<section>` and `<form>` are intentionally excluded because their landmark
2827
+ * semantics depend on having an accessible name.
2828
+ */
2829
+ const LANDMARK_TAGS = [
2830
+ "article",
2831
+ "aside",
2832
+ "footer",
2833
+ "header",
2834
+ "main",
2835
+ "nav"
2836
+ ];
2837
+ /**
2838
+ * Interactive content as defined by the HTML Living Standard.
2839
+ *
2840
+ * Used to enforce restrictions such as "must not contain interactive content"
2841
+ * for elements like `<button>` and `<a>`.
2842
+ *
2843
+ * Only direct children are checked; descendant traversal is outside the scope
2844
+ * of the contract engine.
2845
+ */
2846
+ const INTERACTIVE_CONTENT_TAGS = [
2847
+ "a",
2848
+ "button",
2849
+ "input",
2850
+ "select",
2851
+ "textarea",
2852
+ "label"
2853
+ ];
2854
+ /**
2855
+ * HTML labelable form controls.
2856
+ *
2857
+ * Used by `<label>` to validate implicit and explicit control associations.
2858
+ * `input[type="hidden"]` is excluded separately because labelability depends
2859
+ * on element attributes rather than tag name alone.
2860
+ */
2861
+ const LABELABLE_TAGS = [
2862
+ "button",
2863
+ "input",
2864
+ "meter",
2865
+ "output",
2866
+ "progress",
2867
+ "select",
2868
+ "textarea"
2869
+ ];
2870
+ /**
2871
+ * Interactive elements that are never labelable.
2872
+ *
2873
+ * Used by `labelContract` to enforce the HTML rule that a `<label>` may contain
2874
+ * no interactive content other than its labeled control.
2875
+ *
2876
+ * Conditionally interactive elements (for example, `audio[controls]`) are
2877
+ * excluded because their interactivity depends on attributes rather than tag
2878
+ * name alone.
2879
+ */
2880
+ const OTHER_INTERACTIVE_TAGS = [
2881
+ "a",
2882
+ "details",
2883
+ "embed",
2884
+ "iframe"
2885
+ ];
2886
+ /**
2887
+ * Elements that implicitly terminate a `<p>` element.
2888
+ *
2889
+ * This contract intentionally uses a blocklist rather than attempting to model
2890
+ * the full phrasing-content category. Missing an obscure block-level element is
2891
+ * preferable to rejecting valid inline content because the allowlist is
2892
+ * incomplete.
2893
+ */
2894
+ const P_BLOCKED_TAGS = [
2895
+ "address",
2896
+ "article",
2897
+ "aside",
2898
+ "blockquote",
2899
+ "details",
2900
+ "dialog",
2901
+ "div",
2902
+ "dl",
2903
+ "fieldset",
2904
+ "figure",
2905
+ "footer",
2906
+ "form",
2907
+ "h1",
2908
+ "h2",
2909
+ "h3",
2910
+ "h4",
2911
+ "h5",
2912
+ "h6",
2913
+ "header",
2914
+ "hr",
2915
+ "main",
2916
+ "nav",
2917
+ "ol",
2918
+ "p",
2919
+ "pre",
2920
+ "section",
2921
+ "table",
2922
+ "ul"
2923
+ ];
2924
+ //#endregion
2925
+ //#region ../core/src/html/anchor-rules.ts
2926
+ const DANGEROUS_URL_SCHEMES = [
2927
+ "javascript:",
2928
+ "data:",
2929
+ "vbscript:"
2930
+ ];
2931
+ function normalizeForSchemeCheck(href) {
2932
+ return href.replace(/[\x00-\x20]/g, "").toLowerCase();
2933
+ }
2934
+ function isDangerousUrl(href) {
2935
+ if (!isString(href)) return false;
2936
+ const normalized = normalizeForSchemeCheck(href);
2937
+ return DANGEROUS_URL_SCHEMES.some((scheme) => normalized.startsWith(scheme));
2938
+ }
2939
+ const ANCHOR_RULES = [
2940
+ createRemoveAttributeRule("href", {
2941
+ when: ({ props }) => isDangerousUrl(props.href),
2942
+ severity: "warning",
2943
+ diagnostic: ({ props }) => HtmlDiagnostics.anchor.dangerousHref(props.href),
2944
+ readsProps: ["href"],
2945
+ tags: ["a"]
2946
+ }),
2947
+ Object.assign(({ props }) => {
2948
+ if (props.role !== "button" || !isString(props.href) || props.href.length === 0) return [];
2949
+ const diagnostic = AnchorAccessibilityDiagnostics.roleButtonWithHref();
2950
+ return [invalidWithoutFix({
2951
+ severity: diagnostic.severity,
2952
+ attribute: "role",
2953
+ diagnostic
2954
+ })];
2955
+ }, {
2956
+ readsProps: ["role", "href"],
2957
+ tags: ["a"]
2958
+ }),
2959
+ Object.assign(({ props }) => {
2960
+ const ariaDisabled = props["aria-disabled"];
2961
+ const isDisabled = ariaDisabled === true || ariaDisabled === "true";
2962
+ const hasHref = isString(props.href) && props.href.length > 0;
2963
+ if (!isDisabled || !hasHref) return [];
2964
+ const diagnostic = AnchorAccessibilityDiagnostics.ariaDisabledInert();
2965
+ return [invalidWithoutFix({
2966
+ severity: diagnostic.severity,
2967
+ attribute: "aria-disabled",
2968
+ diagnostic
2969
+ })];
2970
+ }, {
2971
+ readsProps: ["aria-disabled", "href"],
2972
+ tags: ["a"]
2973
+ })
2974
+ ];
2975
+ //#endregion
2976
+ //#region ../core/src/html/spec/vocabulary/input.ts
2977
+ const TEXT_INPUT_TYPES = [
2978
+ "text",
2979
+ "search",
2980
+ "url",
2981
+ "tel",
2982
+ "email",
2983
+ "password"
2984
+ ];
2985
+ const NUMERIC_INPUT_TYPES = [
2986
+ "number",
2987
+ "range",
2988
+ "date",
2989
+ "month",
2990
+ "week",
2991
+ "time",
2992
+ "datetime-local"
2993
+ ];
2994
+ const HTML_INPUT_TYPES = /* @__PURE__ */ new Set([
2995
+ ...TEXT_INPUT_TYPES,
2996
+ ...NUMERIC_INPUT_TYPES,
2997
+ "checkbox",
2998
+ "radio",
2999
+ "file",
3000
+ "color",
3001
+ "hidden",
3002
+ "button",
3003
+ "submit",
3004
+ "reset",
3005
+ "image"
3006
+ ]);
3007
+ //#endregion
3008
+ //#region ../core/src/html/spec/attributes/input.ts
3009
+ const INPUT_ATTRIBUTE_TYPE_POLICIES = [
3010
+ {
3011
+ attribute: "checked",
3012
+ allowedTypes: ["checkbox", "radio"]
3013
+ },
3014
+ {
3015
+ attribute: "multiple",
3016
+ allowedTypes: ["email", "file"]
3017
+ },
3018
+ {
3019
+ attribute: "maxLength",
3020
+ allowedTypes: TEXT_INPUT_TYPES
3021
+ },
3022
+ {
3023
+ attribute: "minLength",
3024
+ allowedTypes: TEXT_INPUT_TYPES
3025
+ },
3026
+ {
3027
+ attribute: "pattern",
3028
+ allowedTypes: TEXT_INPUT_TYPES
3029
+ },
3030
+ {
3031
+ attribute: "min",
3032
+ allowedTypes: NUMERIC_INPUT_TYPES
3033
+ },
3034
+ {
3035
+ attribute: "max",
3036
+ allowedTypes: NUMERIC_INPUT_TYPES
3037
+ },
3038
+ {
3039
+ attribute: "step",
3040
+ allowedTypes: NUMERIC_INPUT_TYPES
3041
+ },
3042
+ {
3043
+ attribute: "accept",
3044
+ allowedTypes: ["file"]
3045
+ },
3046
+ {
3047
+ attribute: "capture",
3048
+ allowedTypes: ["file"]
3049
+ },
3050
+ {
3051
+ attribute: "size",
3052
+ allowedTypes: TEXT_INPUT_TYPES
3053
+ },
3054
+ {
3055
+ attribute: "alt",
3056
+ allowedTypes: ["image"]
3057
+ },
3058
+ {
3059
+ attribute: "height",
3060
+ allowedTypes: ["image"]
3061
+ },
3062
+ {
3063
+ attribute: "width",
3064
+ allowedTypes: ["image"]
3065
+ }
3066
+ ];
3067
+ //#endregion
3068
+ //#region ../core/src/html/spec/constraints/input.ts
3069
+ const REQUIRED_READONLY_CONFLICT = {
3070
+ props: ["required", "readOnly"],
3071
+ diagnostic: () => InputAccessibilityDiagnostics.requiredReadOnlyConflict()
3072
+ };
3073
+ const INPUT_MUTUALLY_EXCLUSIVE_POLICIES = [REQUIRED_READONLY_CONFLICT];
3074
+ //#endregion
3075
+ //#region ../core/src/html/spec/validators/attribute-type-validator.ts
3076
+ const DEFAULT_INPUT_TYPE = "text";
3077
+ function createInputAttributeTypeRule({ attribute, allowedTypes }) {
3078
+ const rule = ({ tag, props, variantKeys }) => {
3079
+ if (tag !== "input" || !(attribute in props)) return [];
3080
+ if (variantKeys?.has(attribute)) return [];
3081
+ const type = typeof props.type === "string" ? props.type : DEFAULT_INPUT_TYPE;
3082
+ if (allowedTypes.includes(type)) return [];
3083
+ const diagnostic = HtmlDiagnostics.input.attributeIgnoredForType(attribute, type, allowedTypes);
3084
+ return [{
3085
+ valid: false,
3086
+ fixable: true,
3087
+ severity: diagnostic.severity,
3088
+ fix: removeAttributeFix(attribute),
3089
+ diagnostic
3090
+ }];
3091
+ };
3092
+ return Object.assign(rule, {
3093
+ readsProps: ["type", attribute],
3094
+ tags: ["input"]
3095
+ });
3096
+ }
3097
+ //#endregion
3098
+ //#region ../core/src/html/spec/validators/mutually-exclusive-validator.ts
3099
+ function isBooleanAttrSet(value) {
3100
+ return value !== void 0 && value !== null && value !== false;
3101
+ }
3102
+ function createMutuallyExclusiveRule({ props: conflictingProps, diagnostic: createDiagnostic }) {
3103
+ const [first, second] = conflictingProps;
3104
+ const rule = ({ tag, props }) => {
3105
+ if (tag !== "input" || !isBooleanAttrSet(props[first]) || !isBooleanAttrSet(props[second])) return [];
3106
+ const diagnostic = createDiagnostic();
3107
+ return [{
3108
+ valid: false,
3109
+ fixable: false,
3110
+ severity: diagnostic.severity,
3111
+ diagnostic
3112
+ }];
3113
+ };
3114
+ return Object.assign(rule, {
3115
+ readsProps: conflictingProps,
3116
+ tags: ["input"]
3117
+ });
3118
+ }
3119
+ //#endregion
3120
+ //#region ../core/src/html/input-rules.ts
3121
+ const policyByAttribute = Object.fromEntries(INPUT_ATTRIBUTE_TYPE_POLICIES.map((policy) => [policy.attribute, policy]));
3122
+ function policyFor(attribute) {
3123
+ return policyByAttribute[attribute];
3124
+ }
3125
+ const supportedInputTypeRule = Object.assign(({ tag, props }) => {
3126
+ if (tag !== "input" || typeof props.type !== "string") return [];
3127
+ const type = props.type;
3128
+ if (HTML_INPUT_TYPES.has(type)) return [];
3129
+ const diagnostic = HtmlDiagnostics.input.unsupportedType(type);
3130
+ return [{
3131
+ valid: false,
3132
+ fixable: false,
3133
+ severity: diagnostic.severity,
3134
+ diagnostic
3135
+ }];
3136
+ }, {
3137
+ readsProps: ["type"],
3138
+ tags: ["input"]
3139
+ });
3140
+ const checkedRequiresCheckableTypeRule = createInputAttributeTypeRule(policyFor("checked"));
3141
+ const multipleRequiresSupportedTypeRule = createInputAttributeTypeRule(policyFor("multiple"));
3142
+ const maxLengthRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("maxLength"));
3143
+ const minLengthRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("minLength"));
3144
+ const patternRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("pattern"));
3145
+ const minRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("min"));
3146
+ const maxRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("max"));
3147
+ const stepRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("step"));
3148
+ const acceptRequiresFileTypeRule = createInputAttributeTypeRule(policyFor("accept"));
3149
+ const captureRequiresFileTypeRule = createInputAttributeTypeRule(policyFor("capture"));
3150
+ const sizeRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("size"));
3151
+ const altRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("alt"));
3152
+ const heightRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("height"));
3153
+ const widthRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("width"));
3154
+ const inputAccessibleNameRule = Object.assign(({ tag, props }) => {
3155
+ if (tag !== "input" || props.type === "hidden") return [];
3156
+ if ("aria-label" in props || "aria-labelledby" in props) return [];
3157
+ const diagnostic = typeof props.placeholder === "string" && props.placeholder.length > 0 ? InputAccessibilityDiagnostics.placeholderIsNotLabel() : InputAccessibilityDiagnostics.missingAccessibleName();
3158
+ return [{
3159
+ valid: false,
3160
+ fixable: false,
3161
+ severity: diagnostic.severity,
3162
+ diagnostic
3163
+ }];
3164
+ }, {
3165
+ readsProps: [
3166
+ "type",
3167
+ "aria-label",
3168
+ "aria-labelledby",
3169
+ "placeholder"
3170
+ ],
3171
+ tags: ["input"]
3172
+ });
3173
+ const PASSWORD_AUTOCOMPLETE_VALUES = ["current-password", "new-password"];
3174
+ const INPUT_RULES = [
3175
+ supportedInputTypeRule,
3176
+ checkedRequiresCheckableTypeRule,
3177
+ multipleRequiresSupportedTypeRule,
3178
+ maxLengthRequiresTextTypeRule,
3179
+ minLengthRequiresTextTypeRule,
3180
+ patternRequiresTextTypeRule,
3181
+ minRequiresNumericTypeRule,
3182
+ maxRequiresNumericTypeRule,
3183
+ stepRequiresNumericTypeRule,
3184
+ acceptRequiresFileTypeRule,
3185
+ captureRequiresFileTypeRule,
3186
+ sizeRequiresTextTypeRule,
3187
+ altRequiresImageTypeRule,
3188
+ heightRequiresImageTypeRule,
3189
+ widthRequiresImageTypeRule,
3190
+ inputAccessibleNameRule,
3191
+ Object.assign(({ tag, props }) => {
3192
+ if (tag !== "input" || props.type !== "password") return [];
3193
+ const autoComplete = props.autoComplete;
3194
+ const tokens = typeof autoComplete === "string" ? autoComplete.split(" ") : [];
3195
+ if (PASSWORD_AUTOCOMPLETE_VALUES.some((value) => tokens.includes(value))) return [];
3196
+ const diagnostic = InputAccessibilityDiagnostics.passwordMissingAutocomplete();
3197
+ return [{
3198
+ valid: false,
3199
+ fixable: false,
3200
+ severity: diagnostic.severity,
3201
+ diagnostic
3202
+ }];
3203
+ }, {
3204
+ readsProps: ["type", "autoComplete"],
3205
+ tags: ["input"]
3206
+ }),
3207
+ createMutuallyExclusiveRule(REQUIRED_READONLY_CONFLICT)
3208
+ ];
3209
+ //#endregion
3210
+ //#region ../core/src/html/spec/types.ts
3211
+ function resolveAllowedRoles(spec, props) {
3212
+ const policy = spec.allowedRoles;
3213
+ if (!policy) return void 0;
3214
+ switch (policy.kind) {
3215
+ case "fixed": return policy.roles;
3216
+ case "byProp": {
3217
+ const raw = props[policy.prop];
3218
+ const value = typeof raw === "string" && raw in policy.map ? raw : policy.fallback;
3219
+ return policy.map[value];
3220
+ }
3221
+ case "dynamic": return policy.resolve({ props });
3222
+ }
3223
+ }
3224
+ //#endregion
3225
+ //#region ../core/src/html/spec/roles/input.ts
3226
+ const ALLOWED_INPUT_ROLES = {
3227
+ checkbox: [
3228
+ "menuitemcheckbox",
3229
+ "option",
3230
+ "switch",
3231
+ "button"
3232
+ ],
3233
+ radio: ["menuitemradio"],
3234
+ range: [],
3235
+ number: [],
3236
+ search: [],
3237
+ text: [
3238
+ "combobox",
3239
+ "searchbox",
3240
+ "spinbutton"
3241
+ ],
3242
+ email: [],
3243
+ tel: [],
3244
+ url: [],
3245
+ button: [
3246
+ "checkbox",
3247
+ "combobox",
3248
+ "gridcell",
3249
+ "link",
3250
+ "menuitem",
3251
+ "menuitemcheckbox",
3252
+ "menuitemradio",
3253
+ "option",
3254
+ "radio",
3255
+ "separator",
3256
+ "slider",
3257
+ "switch",
3258
+ "tab",
3259
+ "treeitem"
3260
+ ],
3261
+ submit: [
3262
+ "checkbox",
3263
+ "combobox",
3264
+ "gridcell",
3265
+ "link",
3266
+ "menuitem",
3267
+ "menuitemcheckbox",
3268
+ "menuitemradio",
3269
+ "option",
3270
+ "radio",
3271
+ "separator",
3272
+ "slider",
3273
+ "switch",
3274
+ "tab",
3275
+ "treeitem"
3276
+ ],
3277
+ reset: [
3278
+ "checkbox",
3279
+ "combobox",
3280
+ "gridcell",
3281
+ "link",
3282
+ "menuitem",
3283
+ "menuitemcheckbox",
3284
+ "menuitemradio",
3285
+ "option",
3286
+ "radio",
3287
+ "separator",
3288
+ "slider",
3289
+ "switch",
3290
+ "tab",
3291
+ "treeitem"
3292
+ ],
3293
+ image: [
3294
+ "checkbox",
3295
+ "gridcell",
3296
+ "link",
3297
+ "menuitem",
3298
+ "menuitemcheckbox",
3299
+ "menuitemradio",
3300
+ "option",
3301
+ "radio",
3302
+ "separator",
3303
+ "slider",
3304
+ "switch",
3305
+ "tab",
3306
+ "treeitem"
3307
+ ],
3308
+ hidden: []
3309
+ };
3310
+ //#endregion
3311
+ //#region ../core/src/html/spec/elements/input.ts
3312
+ const LIST_ELIGIBLE_TYPES = /* @__PURE__ */ new Set([
3313
+ "text",
3314
+ "search",
3315
+ "tel",
3316
+ "url",
3317
+ "email"
3318
+ ]);
3319
+ function rolesForType(type) {
3320
+ return ALLOWED_INPUT_ROLES[type in ALLOWED_INPUT_ROLES ? type : "text"];
3321
+ }
3322
+ const inputElementSpec = {
3323
+ tag: "input",
3324
+ allowedRoles: {
3325
+ kind: "dynamic",
3326
+ resolve: ({ props }) => {
3327
+ const type = isString(props.type) ? props.type : "text";
3328
+ if (isNonNull(props.list) && LIST_ELIGIBLE_TYPES.has(type)) return [];
3329
+ return rolesForType(type);
3330
+ }
3331
+ },
3332
+ attributes: INPUT_ATTRIBUTE_TYPE_POLICIES,
3333
+ mutuallyExclusive: INPUT_MUTUALLY_EXCLUSIVE_POLICIES
3334
+ };
3335
+ //#endregion
3336
+ //#region ../core/src/html/spec/roles/img.ts
3337
+ const IMG_DECORATIVE_ROLES = ["none", "presentation"];
3338
+ const IMG_NAMED_ROLES = [
3339
+ "button",
3340
+ "checkbox",
3341
+ "link",
3342
+ "math",
3343
+ "menuitem",
3344
+ "menuitemcheckbox",
3345
+ "menuitemradio",
3346
+ "meter",
3347
+ "option",
3348
+ "progressbar",
3349
+ "radio",
3350
+ "scrollbar",
3351
+ "separator",
3352
+ "slider",
3353
+ "switch",
3354
+ "tab",
3355
+ "treeitem"
3356
+ ];
3357
+ //#endregion
3358
+ //#region ../core/src/html/spec/elements/img.ts
3359
+ const imgElementSpec = {
3360
+ tag: "img",
3361
+ allowedRoles: {
3362
+ kind: "dynamic",
3363
+ resolve: ({ props }) => {
3364
+ if (isNullish(props.alt)) return IMG_DECORATIVE_ROLES;
3365
+ return props.alt === "" ? [] : IMG_NAMED_ROLES;
3366
+ }
3367
+ }
3368
+ };
3369
+ //#endregion
3370
+ //#region ../core/src/html/spec/roles/select.ts
3371
+ const ALLOWED_SELECT_ROLES = ["menu"];
3372
+ //#endregion
3373
+ //#region ../core/src/html/spec/elements/select.ts
3374
+ function isListBoxSelect(props) {
3375
+ const { multiple, size } = props;
3376
+ if (isNonNull(multiple) && multiple !== false && (!isString(multiple) || multiple.toLowerCase() !== "false")) return true;
3377
+ const parsed = isNumber(size) ? size : isString(size) ? Number(size) : NaN;
3378
+ return Number.isFinite(parsed) && parsed > 1;
3379
+ }
3380
+ const selectElementSpec = {
3381
+ tag: "select",
3382
+ allowedRoles: {
3383
+ kind: "dynamic",
3384
+ resolve: ({ props }) => isListBoxSelect(props) ? [] : ALLOWED_SELECT_ROLES
3385
+ }
3386
+ };
3387
+ //#endregion
3388
+ //#region ../core/src/html/spec/elements/table.ts
3389
+ const tableElementSpec = {
3390
+ tag: "table",
3391
+ allowedRoles: {
3392
+ kind: "fixed",
3393
+ roles: ["grid", "treegrid"]
3394
+ }
3395
+ };
3396
+ //#endregion
3397
+ //#region ../core/src/html/role-restrictions.ts
3398
+ const ALLOWED_ROLES = {
3399
+ article: [
3400
+ "application",
3401
+ "document",
3402
+ "feed",
3403
+ "main",
3404
+ "none",
3405
+ "presentation",
3406
+ "region"
3407
+ ],
3408
+ aside: [
3409
+ "feed",
3410
+ "none",
3411
+ "note",
3412
+ "presentation",
3413
+ "region",
3414
+ "search"
3415
+ ],
3416
+ footer: [
3417
+ "group",
3418
+ "none",
3419
+ "presentation"
3420
+ ],
3421
+ header: [
3422
+ "group",
3423
+ "none",
3424
+ "presentation"
3425
+ ],
3426
+ main: [],
3427
+ nav: [
3428
+ "menu",
3429
+ "menubar",
3430
+ "none",
3431
+ "presentation",
3432
+ "tablist"
3433
+ ],
3434
+ a: [
3435
+ "button",
3436
+ "checkbox",
3437
+ "menuitem",
3438
+ "menuitemcheckbox",
3439
+ "menuitemradio",
3440
+ "option",
3441
+ "radio",
3442
+ "switch",
3443
+ "tab",
3444
+ "treeitem"
3445
+ ],
3446
+ button: [
3447
+ "checkbox",
3448
+ "combobox",
3449
+ "gridcell",
3450
+ "link",
3451
+ "menuitem",
3452
+ "menuitemcheckbox",
3453
+ "menuitemradio",
3454
+ "option",
3455
+ "radio",
3456
+ "separator",
3457
+ "slider",
3458
+ "switch",
3459
+ "tab",
3460
+ "treeitem"
3461
+ ],
3462
+ h1: [
3463
+ "tab",
3464
+ "presentation",
3465
+ "none"
3466
+ ],
3467
+ h2: [
3468
+ "tab",
3469
+ "presentation",
3470
+ "none"
3471
+ ],
3472
+ h3: [
3473
+ "tab",
3474
+ "presentation",
3475
+ "none"
3476
+ ],
3477
+ h4: [
3478
+ "tab",
3479
+ "presentation",
3480
+ "none"
3481
+ ],
3482
+ h5: [
3483
+ "tab",
3484
+ "presentation",
3485
+ "none"
3486
+ ],
3487
+ h6: [
3488
+ "tab",
3489
+ "presentation",
3490
+ "none"
3491
+ ],
3492
+ ul: [
3493
+ "group",
3494
+ "listbox",
3495
+ "menu",
3496
+ "menubar",
3497
+ "none",
3498
+ "presentation",
3499
+ "radiogroup",
3500
+ "tablist",
3501
+ "toolbar",
3502
+ "tree"
3503
+ ],
3504
+ ol: [
3505
+ "group",
3506
+ "listbox",
3507
+ "menu",
3508
+ "menubar",
3509
+ "none",
3510
+ "presentation",
3511
+ "radiogroup",
3512
+ "tablist",
3513
+ "toolbar",
3514
+ "tree"
3515
+ ],
3516
+ li: [
3517
+ "menuitem",
3518
+ "menuitemcheckbox",
3519
+ "menuitemradio",
3520
+ "option",
3521
+ "none",
3522
+ "presentation",
3523
+ "radio",
3524
+ "separator",
3525
+ "tab",
3526
+ "treeitem"
3527
+ ],
3528
+ dialog: ["alertdialog"],
3529
+ fieldset: [
3530
+ "none",
3531
+ "presentation",
3532
+ "radiogroup"
3533
+ ],
3534
+ label: []
3535
+ };
3536
+ const ELEMENT_SPECS = {
3537
+ input: inputElementSpec,
3538
+ img: imgElementSpec,
3539
+ select: selectElementSpec,
3540
+ table: tableElementSpec
3541
+ };
3542
+ function getAllowedRoles(tag, props) {
3543
+ const spec = ELEMENT_SPECS[tag];
3544
+ if (spec) return resolveAllowedRoles(spec, props);
3545
+ return ALLOWED_ROLES[tag];
3546
+ }
3547
+ const removeRoleFix = {
3548
+ kind: "removeRole",
3549
+ apply: ({ props }) => {
3550
+ if (!("role" in props)) return {
3551
+ applied: false,
3552
+ next: props
3553
+ };
3554
+ const { role: _role, ...rest } = props;
3555
+ return {
3556
+ applied: true,
3557
+ next: rest,
3558
+ previous: props
3559
+ };
3560
+ }
3561
+ };
3562
+ const roleNotPermittedRule = Object.assign(({ tag, props, implicitRole }) => {
3563
+ const role = props.role;
3564
+ if (typeof role !== "string" || role.length === 0 || role === implicitRole) return [];
3565
+ const allowed = getAllowedRoles(tag, props);
3566
+ if (allowed === void 0 || allowed.includes(role)) return [];
3567
+ const diagnostic = HtmlDiagnostics.roleNotPermitted(tag, role, allowed);
3568
+ return [{
3569
+ valid: false,
3570
+ fixable: true,
3571
+ severity: diagnostic.severity,
3572
+ fix: removeRoleFix,
3573
+ diagnostic
3574
+ }];
3575
+ }, { readsProps: [
3576
+ "role",
3577
+ "type",
3578
+ "alt",
3579
+ "list",
3580
+ "multiple",
3581
+ "size"
3582
+ ] });
3583
+ //#endregion
3584
+ //#region ../core/src/html/aria-rules.ts
3585
+ function defineAriaRule(tags, rule) {
3586
+ return Object.assign(rule, { tags });
3587
+ }
3588
+ const LANDMARK_TAG_SET = new Set(LANDMARK_TAGS);
3589
+ const removeLandmarkRoleOverride = {
3590
+ kind: "removeRole",
3591
+ apply: ({ props }) => {
3592
+ if (!("role" in props)) return {
3593
+ applied: false,
3594
+ next: props
3595
+ };
3596
+ const { role: _r, ...rest } = props;
3597
+ return {
3598
+ applied: true,
3599
+ next: rest,
3600
+ previous: props
3601
+ };
3602
+ }
3603
+ };
3604
+ const landmarkRoleRule = defineAriaRule(LANDMARK_TAGS, ({ tag, props, implicitRole }) => {
3605
+ if (!LANDMARK_TAG_SET.has(tag) || !implicitRole) return [];
3606
+ const { role } = props;
3607
+ if (!role || role === implicitRole) return [];
3608
+ const diagnostic = HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role);
3609
+ return [{
3610
+ valid: false,
3611
+ fixable: true,
3612
+ severity: diagnostic.severity,
3613
+ fix: removeLandmarkRoleOverride,
3614
+ diagnostic
3615
+ }];
3616
+ });
3617
+ function requireAccessibleName({ tag, props }) {
3618
+ if ("aria-label" in props || "aria-labelledby" in props) return [];
3619
+ return [{
3620
+ valid: false,
3621
+ fixable: false,
3622
+ severity: "warning",
3623
+ diagnostic: AriaDiagnostics.missingAccessibleName(tag)
3624
+ }];
3625
+ }
3626
+ const NAMED_LANDMARK_TAGS = ["nav", "aside"];
3627
+ const NAMED_LANDMARK_TAG_SET = new Set(NAMED_LANDMARK_TAGS);
3628
+ const landmarkAccessibleNameRule = defineAriaRule(NAMED_LANDMARK_TAGS, (ctx) => {
3629
+ if (!ctx.implicitRole || !NAMED_LANDMARK_TAG_SET.has(ctx.tag)) return [];
3630
+ return requireAccessibleName(ctx);
3631
+ });
3632
+ const HTML_ARIA_RULES = [
3633
+ landmarkRoleRule,
3634
+ landmarkAccessibleNameRule,
3635
+ roleNotPermittedRule,
3636
+ ...INPUT_RULES,
3637
+ ...ANCHOR_RULES
3638
+ ];
3639
+ //#endregion
3640
+ //#region ../core/src/html/contracts/helpers.ts
3641
+ /** Narrows an unknown value to the minimal vnode shape used by contract helpers. */
3642
+ function isVNodeLike(child) {
3643
+ return isObject(child) && "type" in child;
3644
+ }
3645
+ /**
3646
+ * Creates a matcher that accepts any element except the supplied HTML tags.
3647
+ *
3648
+ * Tag resolution is semantic-aware, so Praxis components are matched by their
3649
+ * resolved HTML tag rather than their component type.
3650
+ */
3651
+ function isOpenContent(...blockedTags) {
3652
+ const set = new Set(blockedTags);
3653
+ return (child) => {
3654
+ if (!isVNodeLike(child)) return false;
3655
+ const tag = getTag(child);
3656
+ return tag === void 0 || !set.has(tag);
3657
+ };
3658
+ }
3659
+ const metadataMatch = isTag(...METADATA_TAGS);
3660
+ /** Creates a child rule matching HTML metadata content (`<script>` and `<template>`). */
3661
+ function metadata(name = "metadata") {
3662
+ return {
3663
+ name,
3664
+ match: metadataMatch
3665
+ };
3666
+ }
3667
+ /**
3668
+ * Creates an optional singleton child rule (`max: 1`).
3669
+ *
3670
+ * Used as the building block for positional variants such as `firstOptional()`.
3671
+ */
3672
+ function optional(name, tag) {
3673
+ return {
3674
+ name,
3675
+ match: isTag(tag),
3676
+ cardinality: { max: 1 }
3677
+ };
3678
+ }
3679
+ function firstOptional(name, tag) {
3680
+ return {
3681
+ ...optional(name, tag),
3682
+ position: "first"
3683
+ };
3684
+ }
3685
+ /**
3686
+ * Creates an enforcement contract with the default diagnostics configuration.
3687
+ */
3688
+ function contract(children, options) {
3689
+ return {
3690
+ diagnostics: warnDiagnostics,
3691
+ children,
3692
+ ...options
3693
+ };
3694
+ }
3695
+ /**
3696
+ * Creates a closed content model.
3697
+ *
3698
+ * Only the supplied child rules are permitted; all other children are rejected.
3699
+ */
3700
+ function closedContract(children) {
3701
+ return contract(children, { exclusiveChildren: true });
3702
+ }
3703
+ /** Creates an ARIA-only enforcement contract. */
3704
+ function ariaContract(aria) {
3705
+ return {
3706
+ diagnostics: warnDiagnostics,
3707
+ aria
3708
+ };
3709
+ }
3710
+ /**
3711
+ * Creates a contract with an optional leading child followed by open content.
3712
+ *
3713
+ * Used by elements such as `<details>` and `<fieldset>` whose first child has
3714
+ * special semantics.
3715
+ */
3716
+ function firstChildContract(name, tag) {
3717
+ return contract([firstOptional(name, tag), {
3718
+ name: "content",
3719
+ match: isOpenContent(tag)
3720
+ }]);
3721
+ }
3722
+ /**
3723
+ * Returns a property from a vnode-like child, if present.
3724
+ */
3725
+ function getChildProp(child, key) {
3726
+ if (!isVNodeLike(child)) return void 0;
3727
+ const { props } = child;
3728
+ if (!isObject(props)) return void 0;
3729
+ return Reflect.get(props, key);
3730
+ }
3731
+ //#endregion
3732
+ //#region ../core/src/html/contracts/aria/landmarks.ts
3733
+ /**
3734
+ * Elements with an unconditional landmark role (`<article>`, `<aside>`, `<footer>`,
3735
+ * `<header>`, `<main>`, `<nav>`).
3736
+ *
3737
+ * - `role="<implicit>"` → warning: redundant, removed (built-in engine behaviour).
3738
+ * - `role="<anything else>"` → error: overrides the fixed landmark, removed.
3739
+ * - `<nav>` and `<aside>` without an accessible name → warning (commonly multiplied).
3740
+ */
3741
+ const landmarkContract = ariaContract([landmarkRoleRule, landmarkAccessibleNameRule]);
3742
+ //#endregion
3743
+ //#region ../core/src/html/contracts/aria/widgets.ts
3744
+ /**
3745
+ * `<dialog>` — must have an accessible name (aria-label or aria-labelledby).
3746
+ * APG: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
3747
+ * Without a name, assistive technology cannot identify the dialog in the page outline
3748
+ * or when focus moves into it.
3749
+ */
3750
+ const dialogContract = ariaContract([requireAccessibleName]);
3751
+ //#endregion
3752
+ //#region ../core/src/html/contracts/html/document.ts
3753
+ /**
3754
+ * `<head>` — metadata content only.
3755
+ */
3756
+ const headContract = closedContract([{
3757
+ name: "metadata",
3758
+ match: isTag("base", "link", "meta", "noscript", "script", "style", "template", "title")
3759
+ }]);
3760
+ /**
3761
+ * `<html>` — one `<head>` and one `<body>`.
3762
+ */
3763
+ const htmlContract = closedContract([{
3764
+ name: "head",
3765
+ match: isTag("head"),
3766
+ cardinality: {
3767
+ min: 1,
3768
+ max: 1
3769
+ },
3770
+ position: "first"
3771
+ }, {
3772
+ name: "body",
3773
+ match: isTag("body"),
3774
+ cardinality: {
3775
+ min: 1,
3776
+ max: 1
3777
+ }
3778
+ }]);
3779
+ /**
3780
+ * Void elements — the HTML spec forbids any children, including text.
3781
+ */
3782
+ const voidContract = contract([], {
3783
+ exclusiveChildren: true,
3784
+ allowText: false
3785
+ });
3786
+ /**
3787
+ * Raw text (`<script>`, `<style>`), escapable raw text (`<textarea>`, `<title>`), and
3788
+ * `<option>` — only text nodes (strings or numbers) are permitted as children. No
3789
+ * explicit rule is needed: `exclusiveChildren` rejects every element (nothing matches
3790
+ * the empty rule list) while `allowText` stays at its default `true`.
3791
+ */
3792
+ const textOnlyContract = contract([], { exclusiveChildren: true });
3793
+ //#endregion
3794
+ //#region ../core/src/html/contracts/html/forms.ts
3795
+ /**
3796
+ * `<select>` — direct children must be `<option>`, `<optgroup>`, `<hr>`, `<script>`,
3797
+ * or `<template>`.
3798
+ */
3799
+ const selectContract = closedContract([{
3800
+ name: "option",
3801
+ match: isTag("option", "optgroup", "hr", ...METADATA_TAGS)
3802
+ }]);
3803
+ /**
3804
+ * `<optgroup>` — direct children must be `<option>`, `<script>`, or `<template>`.
3805
+ */
3806
+ const optgroupContract = closedContract([{
3807
+ name: "option",
3808
+ match: isTag("option", ...METADATA_TAGS)
3809
+ }]);
3810
+ /**
3811
+ * `<datalist>` — direct children must be `<option>`, `<script>`, or `<template>`.
3812
+ */
3813
+ const datalistContract = closedContract([{
3814
+ name: "option",
3815
+ match: isTag("option", ...METADATA_TAGS)
3816
+ }]);
3817
+ /**
3818
+ * `<button>` — must not directly contain interactive content (`<a>`, another `<button>`,
3819
+ * `<input>`, `<select>`, `<textarea>`, or `<label>`) per the HTML5 spec.
3820
+ */
3821
+ const buttonContract = closedContract([{
3822
+ name: "content",
3823
+ match: isOpenContent(...INTERACTIVE_CONTENT_TAGS)
3824
+ }]);
3825
+ /**
3826
+ * `<a>` — same interactive-content restriction as `<button>`: must not directly contain
3827
+ * `<a>`, `<button>`, `<input>`, `<select>`, `<textarea>`, or `<label>`.
3828
+ */
3829
+ const anchorContract = closedContract([{
3830
+ name: "content",
3831
+ match: isOpenContent(...INTERACTIVE_CONTENT_TAGS)
3832
+ }]);
3833
+ const labelableTagMatch = isTag(...LABELABLE_TAGS);
3834
+ function isLabelableControl(child) {
3835
+ if (!labelableTagMatch(child)) return false;
3836
+ if (getTag(child) !== "input") return true;
3837
+ const type = getChildProp(child, "type");
3838
+ return !isString(type) || type !== "hidden";
3839
+ }
3840
+ function hasAccessibleNameProp(props) {
3841
+ return "aria-label" in props || "aria-labelledby" in props;
3842
+ }
3843
+ function isNonEmptyTextChild(child) {
3844
+ if (isNumber(child)) return true;
3845
+ return isString(child) && child.trim().length > 0;
3846
+ }
3847
+ /**
3848
+ * `<label>` — at most one labelable form control descendant (`<button>`, `<input>`
3849
+ * excluding `type="hidden"`, `<meter>`, `<output>`, `<progress>`, `<select>`, `<textarea>`);
3850
+ * a nested `<label>` is rejected outright, as is any other interactive content (`<a>`,
3851
+ * `<details>`, `<embed>`, `<iframe>`) that isn't the labeled control; any other phrasing
3852
+ * content is permitted. Requires an accessible name: either `aria-label`/`aria-labelledby`,
3853
+ * or visible (non-whitespace) text among its direct children.
3854
+ *
3855
+ * The text check only looks at direct children (matching every other rule in this
3856
+ * contract, and the engine's own model — it doesn't walk into a descendant control to
3857
+ * find, say, an `<option>`'s text), so a label whose only content is a nested element
3858
+ * with its own accessible name (e.g. `<label><input type="image" alt="Submit" /></label>`)
3859
+ * still reports as missing a name. That's a known, narrower check than the full HTML AAM
3860
+ * accessible-name computation, not a bug.
3861
+ */
3862
+ const labelContract = contract([
3863
+ {
3864
+ name: "control",
3865
+ match: isLabelableControl,
3866
+ cardinality: { max: 1 }
3867
+ },
3868
+ {
3869
+ name: "nested-label",
3870
+ match: isTag("label"),
3871
+ cardinality: { max: 0 }
3872
+ },
3873
+ {
3874
+ name: "other-interactive-content",
3875
+ match: isTag(...OTHER_INTERACTIVE_TAGS),
3876
+ cardinality: { max: 0 }
3877
+ },
3878
+ {
3879
+ name: "accessible-name",
3880
+ match: isNonEmptyTextChild,
3881
+ cardinality: dynamic((ctx) => hasAccessibleNameProp(ctx.props) ? { min: 0 } : { min: 1 })
3882
+ }
3883
+ ]);
3884
+ //#endregion
3885
+ //#region ../core/src/html/contracts/html/grouping.ts
3886
+ /**
3887
+ * `<details>` — permits an optional leading `<summary>` followed by flow content.
3888
+ */
3889
+ const detailsContract = firstChildContract("summary", "summary");
3890
+ /**
3891
+ * `<fieldset>` — permits an optional leading `<legend>` followed by flow content.
3892
+ */
3893
+ const fieldsetContract = firstChildContract("legend", "legend");
3894
+ //#endregion
3895
+ //#region ../core/src/html/contracts/html/lists.ts
3896
+ /**
3897
+ * `<ul>`, `<ol>`, `<menu>` — direct children must be `<li>`, `<script>`, or `<template>`.
3898
+ */
3899
+ const listContract = closedContract([{
3900
+ name: "list-item",
3901
+ match: isTag("li", ...METADATA_TAGS)
3902
+ }]);
3903
+ /**
3904
+ * `<dl>` — direct children must be `<dt>`, `<dd>`, `<div>` (as group wrapper),
3905
+ * `<script>`, or `<template>`.
3906
+ */
3907
+ const dlContract = closedContract([
3908
+ {
3909
+ name: "term",
3910
+ match: isTag("dt")
3911
+ },
3912
+ {
3913
+ name: "description",
3914
+ match: isTag("dd")
3915
+ },
3916
+ {
3917
+ name: "group",
3918
+ match: isTag("div")
3919
+ },
3920
+ metadata()
3921
+ ]);
3922
+ //#endregion
3923
+ //#region ../core/src/html/contracts/html/media.ts
3924
+ /**
3925
+ * `<picture>` — permits zero or more `<source>` elements followed by a single
3926
+ * `<img>` fallback.
3927
+ *
3928
+ * `<img>` must be the final child.
3929
+ */
3930
+ const pictureContract = closedContract([{
3931
+ name: "source",
3932
+ match: isTag("source", ...METADATA_TAGS)
3933
+ }, {
3934
+ name: "image",
3935
+ match: isTag("img"),
3936
+ cardinality: {
3937
+ min: 1,
3938
+ max: 1
3939
+ },
3940
+ position: "last"
3941
+ }]);
3942
+ /**
3943
+ * `<figure>` — permits at most one `<figcaption>` and arbitrary flow content.
3944
+ *
3945
+ * The `<figcaption>` may appear as either the first or last child.
3946
+ */
3947
+ const figureContract = contract([{
3948
+ name: "caption",
3949
+ match: isTag("figcaption"),
3950
+ cardinality: { max: 1 }
3951
+ }, {
3952
+ name: "content",
3953
+ match: isOpenContent("figcaption")
3954
+ }]);
3955
+ /**
3956
+ * `<object>` — permits `<param>` elements followed by transparent fallback
3957
+ * content.
3958
+ *
3959
+ * Relative ordering is not currently validated.
3960
+ */
3961
+ const objectContract = contract([{
3962
+ name: "param",
3963
+ match: isTag("param")
3964
+ }, {
3965
+ name: "content",
3966
+ match: isOpenContent("param")
3967
+ }]);
3968
+ /**
3969
+ * `<audio>` and `<video>` — permit media source definitions, timed text tracks,
3970
+ * metadata, and fallback content.
3971
+ */
3972
+ const mediaContract = contract([
3973
+ {
3974
+ name: "source",
3975
+ match: isTag("source")
3976
+ },
3977
+ {
3978
+ name: "track",
3979
+ match: isTag("track")
3980
+ },
3981
+ metadata(),
3982
+ {
3983
+ name: "content",
3984
+ match: isOpenContent("source", "track", ...METADATA_TAGS)
3985
+ }
3986
+ ]);
3987
+ //#endregion
3988
+ //#region ../core/src/html/contracts/html/tables.ts
3989
+ /**
3990
+ * `<table>` — permits the HTML table sectioning elements as direct children.
3991
+ *
3992
+ * `<caption>` is optional and must be first. `<thead>` and `<tfoot>` may each
3993
+ * appear at most once.
3994
+ *
3995
+ * Relative ordering of the remaining sections is not currently validated.
3996
+ */
3997
+ const tableContract = closedContract([
3998
+ firstOptional("caption", "caption"),
3999
+ {
4000
+ name: "colgroup",
4001
+ match: isTag("colgroup")
4002
+ },
4003
+ {
4004
+ name: "thead",
4005
+ match: isTag("thead"),
4006
+ cardinality: { max: 1 }
4007
+ },
4008
+ {
4009
+ name: "tbody",
4010
+ match: isTag("tbody")
4011
+ },
4012
+ {
4013
+ name: "tfoot",
4014
+ match: isTag("tfoot"),
4015
+ cardinality: { max: 1 }
4016
+ },
4017
+ {
4018
+ name: "table-row",
4019
+ match: isTag("tr", ...METADATA_TAGS)
4020
+ }
4021
+ ]);
4022
+ /**
4023
+ * `<thead>`, `<tbody>`, and `<tfoot>` — permit table rows as direct children.
4024
+ */
4025
+ const tableBodyContract = closedContract([{
4026
+ name: "table-row",
4027
+ match: isTag("tr", ...METADATA_TAGS)
4028
+ }]);
4029
+ /**
4030
+ * `<tr>` — permits table cells as direct children.
4031
+ */
4032
+ const tableRowContract = closedContract([{
4033
+ name: "table-cell",
4034
+ match: isTag("td", "th", ...METADATA_TAGS)
4035
+ }]);
4036
+ /**
4037
+ * `<colgroup>` — permits column definitions as direct children.
4038
+ */
4039
+ const colgroupContract = closedContract([{
4040
+ name: "column",
4041
+ match: isTag("col", "template")
4042
+ }]);
4043
+ //#endregion
4044
+ //#region ../core/src/html/contracts/html/text.ts
4045
+ /**
4046
+ * `<p>` — permits phrasing content only.
4047
+ *
4048
+ * Implemented as a pragmatic blocklist of elements that terminate a paragraph.
4049
+ */
4050
+ const pContract = closedContract([{
4051
+ name: "content",
4052
+ match: isOpenContent(...P_BLOCKED_TAGS)
4053
+ }]);
4054
+ //#endregion
4055
+ //#region ../core/src/html/contracts/build-map.ts
4056
+ /**
4057
+ * Builds a lookup table from grouped keys.
4058
+ *
4059
+ * Each key in a group is associated with the group's shared value. Used to
4060
+ * expand compact declarations into a flat lookup record.
4061
+ */
4062
+ function buildMap(groups) {
4063
+ return Object.fromEntries(groups.flatMap(([keys, value]) => keys.map((key) => [key, value])));
4064
+ }
4065
+ /**
4066
+ * Built-in HTML element contracts keyed by tag name.
4067
+ *
4068
+ * Pass a contract directly to `createContractComponent`:
4069
+ *
4070
+ * ```ts
4071
+ * const List = createContractComponent({
4072
+ * tag: 'ul',
4073
+ * enforcement: htmlContracts.ul,
4074
+ * })
4075
+ * ```
4076
+ *
4077
+ * Contracts default to `strict: 'warn'`. Override individual options by
4078
+ * spreading the contract into a new enforcement object.
4079
+ */
4080
+ const htmlContracts = {
4081
+ ...buildMap([
4082
+ [VOID_TAGS, voidContract],
4083
+ [TEXT_ONLY_TAGS, textOnlyContract],
4084
+ [LANDMARK_TAGS, landmarkContract],
4085
+ [[
4086
+ "ul",
4087
+ "ol",
4088
+ "menu"
4089
+ ], listContract],
4090
+ [["audio", "video"], mediaContract],
4091
+ [[
4092
+ "thead",
4093
+ "tbody",
4094
+ "tfoot"
4095
+ ], tableBodyContract]
4096
+ ]),
4097
+ table: tableContract,
4098
+ tr: tableRowContract,
4099
+ colgroup: colgroupContract,
4100
+ dl: dlContract,
4101
+ select: selectContract,
4102
+ optgroup: optgroupContract,
4103
+ datalist: datalistContract,
4104
+ picture: pictureContract,
4105
+ figure: figureContract,
4106
+ details: detailsContract,
4107
+ fieldset: fieldsetContract,
4108
+ dialog: dialogContract,
4109
+ object: objectContract,
4110
+ button: buttonContract,
4111
+ a: anchorContract,
4112
+ label: labelContract,
4113
+ p: pContract,
4114
+ head: headContract,
4115
+ html: htmlContract
4116
+ };
4117
+ //#endregion
4118
+ //#region ../core/src/html/evaluators.ts
4119
+ const htmlDiagnostics = warnDiagnostics;
4120
+ function buildEvaluatorMap() {
4121
+ const map = /* @__PURE__ */ new Map();
4122
+ iterate.forEachEntry(htmlContracts, (tag, { children, exclusiveChildren, allowText }) => {
4123
+ if (children?.length || exclusiveChildren || allowText === false) map.set(tag, new ChildrenEvaluator(children ?? [], htmlDiagnostics, `<${tag}>`, {
4124
+ exclusiveChildren,
4125
+ allowText
4126
+ }));
4127
+ });
4128
+ return map;
4129
+ }
4130
+ const HTML_EVALUATORS = buildEvaluatorMap();
4131
+ function getHtmlChildrenEvaluator(tag) {
4132
+ return typeof tag === "string" ? HTML_EVALUATORS.get(tag) : void 0;
4133
+ }
4134
+ //#endregion
4135
+ //#region ../core/src/html/prop-normalizers.ts
4136
+ const HTML_FORM_NORMALIZERS = /* @__PURE__ */ new Map([
4137
+ ["button", [disabledProps]],
4138
+ ["input", [
4139
+ disabledProps,
4140
+ readonlyProps,
4141
+ invalidProps
4142
+ ]],
4143
+ ["select", [disabledProps]],
4144
+ ["textarea", [disabledProps, readonlyProps]],
4145
+ ["fieldset", [disabledProps]],
4146
+ ["optgroup", [disabledProps]]
4147
+ ]);
4148
+ function getHtmlPropNormalizers(tag) {
4149
+ return typeof tag === "string" ? HTML_FORM_NORMALIZERS.get(tag) : void 0;
4150
+ }
4151
+ //#endregion
4152
+ //#region ../../lib/styling/src/cva.ts
4153
+ function cva$1(base, config) {
4154
+ const fn = cva(base, config);
4155
+ return (props) => cn(fn(props));
4156
+ }
4157
+ //#endregion
4158
+ //#region ../../lib/styling/src/static-class-resolver.ts
4159
+ var StaticClassResolver = class {
4160
+ #baseClass;
4161
+ #cache = new LRUCache(200);
4162
+ #resolveTag;
4163
+ constructor(baseClass, tagMap) {
4164
+ this.#baseClass = Array.isArray(baseClass) ? baseClass.join(" ") : baseClass;
4165
+ this.#resolveTag = tagMap ? (tag) => {
4166
+ const extra = tagMap[tag];
4167
+ if (!extra) return this.#baseClass;
4168
+ const extraStr = Array.isArray(extra) ? extra.join(" ") : extra;
4169
+ return `${this.#baseClass} ${extraStr}`;
4170
+ } : () => this.#baseClass;
4171
+ }
4172
+ resolve(tag, skipTagMap = false) {
4173
+ if (typeof tag !== "string" || skipTagMap) return this.#baseClass;
4174
+ const cached = this.#cache.get(tag);
4175
+ if (cached !== void 0) return cached;
4176
+ const result = this.#resolveTag(tag);
4177
+ this.#cache.set(tag, result);
4178
+ return result;
4179
+ }
4180
+ };
4181
+ //#endregion
4182
+ //#region ../../lib/styling/src/variant-class-resolver.ts
4183
+ /**
4184
+ * Runtime CVA class resolution with LRU + precomputed-map caching.
4185
+ *
4186
+ * Distinct from `variant-pass/` — `createVariantPass` is a composable pipeline stage for
4187
+ * build-time / plugin use (a `VariantConfig` → classes function), while this is the memoized
4188
+ * runtime resolver `createClassPipeline` calls per render.
4189
+ *
4190
+ * The precomputed map (`compileVariantLookup`, injected by the vite plugin) covers the
4191
+ * **no-recipe** combinations only — its keys are variant props alone, whereas this resolver's
4192
+ * cache keys are `recipe | variant props`, so a recipe-active call never hits a precomputed
4193
+ * entry and falls through to `#compute`. Recipe resolution stays fully runtime by design.
4194
+ */
4195
+ var VariantClassResolver = class VariantClassResolver {
4196
+ #cvaFn;
4197
+ #recipeMap;
4198
+ #variantKeys;
4199
+ #precomputedClasses;
4200
+ #cache = new LRUCache(1e3);
4201
+ constructor(cvaFn, recipeMap, variantKeys, precomputedClasses) {
4202
+ this.#cvaFn = cvaFn ?? null;
4203
+ this.#recipeMap = Object.freeze(recipeMap ?? {});
4204
+ this.#variantKeys = variantKeys ?? null;
4205
+ this.#precomputedClasses = precomputedClasses ?? null;
4206
+ }
4207
+ resolve({ props, recipe }) {
4208
+ const normalizedKey = recipe ?? "__none__";
4209
+ const cacheKey = this.#createCacheKey(props, normalizedKey);
4210
+ if (this.#precomputedClasses !== null) {
4211
+ const precomputed = this.#precomputedClasses[cacheKey];
4212
+ if (precomputed !== void 0) return precomputed;
4213
+ }
4214
+ const cached = this.#cache.get(cacheKey);
4215
+ if (cached !== void 0) return cached;
4216
+ const result = this.#compute(props, recipe);
4217
+ this.#cache.set(cacheKey, result);
4218
+ return result;
4219
+ }
4220
+ #compute(props, recipe) {
4221
+ if (!this.#cvaFn) return "";
4222
+ if (recipe === void 0) return this.#cvaFn(props);
4223
+ const preset = this.#recipeMap[recipe];
4224
+ if (!preset) return this.#cvaFn(props);
4225
+ return this.#cvaFn({
4226
+ ...preset,
4227
+ ...props
4228
+ });
4229
+ }
4230
+ #createCacheKey(props, recipe) {
4231
+ if (this.#variantKeys !== null) {
4232
+ let key = recipe;
4233
+ iterate.forEachSet(this.#variantKeys, (k) => {
4234
+ if (k in props) key += `|${k}:${VariantClassResolver.#serializeValue(props[k])}`;
4235
+ });
4236
+ return key;
4237
+ }
4238
+ let key = recipe;
4239
+ iterate.forEach(Object.keys(props).sort(), (k) => {
4240
+ key += `|${k}:${VariantClassResolver.#serializeValue(props[k])}`;
4241
+ });
4242
+ return key;
4243
+ }
4244
+ static #serializeValue(value) {
4245
+ if (value === void 0) return "u";
4246
+ if (value === null) return "n";
4247
+ if (typeof value === "boolean") return `b:${value}`;
4248
+ if (typeof value === "string") return `s:${value}`;
4249
+ return `x:${String(value)}`;
4250
+ }
4251
+ };
4252
+ //#endregion
4253
+ //#region ../../lib/styling/src/create-class-pipeline.ts
4254
+ function createClassPipeline(resolved) {
4255
+ const baseClass = resolved.baseClassName ?? "";
4256
+ const cvaFn = resolved.variants ? cva$1("", {
4257
+ variants: resolved.variants,
4258
+ defaultVariants: resolved.defaultVariants,
4259
+ compoundVariants: resolved.compoundVariants
4260
+ }) : null;
4261
+ const variantKeys = resolved.variants ? new Set(Object.keys(resolved.variants)) : void 0;
4262
+ const staticResolver = new StaticClassResolver(baseClass, resolved.tagMap);
4263
+ const variantResolver = new VariantClassResolver(cvaFn, resolved.recipeMap, variantKeys, resolved.precomputedClasses);
4264
+ return function resolveClasses(tag, props, className, recipe) {
4265
+ const staticClasses = staticResolver.resolve(tag, recipe !== void 0);
4266
+ const variantClasses = variantResolver.resolve({
4267
+ props,
4268
+ recipe
4269
+ });
4270
+ if (!className) return (staticClasses && variantClasses ? `${staticClasses} ${variantClasses}` : staticClasses || variantClasses) || void 0;
4271
+ return cn(staticClasses, variantClasses, className);
4272
+ };
4273
+ }
4274
+ //#endregion
4275
+ //#region ../core/src/options/resolve-factory-options.ts
4276
+ const EMPTY_VARIANT_KEYS = /* @__PURE__ */ new Set();
4277
+ function toNormalizeFn(normalize) {
4278
+ if (!Array.isArray(normalize)) return normalize;
4279
+ const fns = normalize;
4280
+ if (fns.length === 0) return void 0;
4281
+ if (fns.length === 1) return fns[0];
4282
+ return ((props) => {
4283
+ let acc = props;
4284
+ for (const fn of fns) acc = fn(acc);
4285
+ return acc;
4286
+ });
4287
+ }
4288
+ function composeNormalizers(normalizers, fn) {
4289
+ if (!normalizers?.length) return fn;
4290
+ return ((props) => {
4291
+ const patched = { ...props };
4292
+ for (const normalizer of normalizers) Object.assign(patched, normalizer(patched));
4293
+ const resolved = patched;
4294
+ return fn ? fn(resolved) : resolved;
4295
+ });
4296
+ }
4297
+ function whenDefined(key, value) {
4298
+ return value === void 0 ? {} : { [key]: value };
4299
+ }
4300
+ function mergeAriaRules(aria, rules) {
4301
+ if (!aria?.length) return rules;
4302
+ if (!rules?.length) return aria;
4303
+ return [...aria, ...rules];
4304
+ }
4305
+ function resolveFactoryOptions(options = {}) {
4306
+ const { styling, enforcement } = options;
4307
+ const composedNormalizeFn = composeNormalizers(enforcement?.props, toNormalizeFn(options.normalize));
4308
+ const variantKeys = styling?.variants === void 0 ? EMPTY_VARIANT_KEYS : new Set(Object.keys(styling.variants));
4309
+ return Object.freeze({
4310
+ defaultTag: options.tag ?? "div",
4311
+ diagnostics: resolveDiagnostics(enforcement?.diagnostics, options.diagnostics ?? silentDiagnostics),
4312
+ variantKeys,
4313
+ ...whenDefined("displayName", options.name),
4314
+ ...whenDefined("defaultProps", options.defaults),
4315
+ ...whenDefined("baseClassName", styling?.base),
4316
+ ...whenDefined("tagMap", styling?.tags),
4317
+ ...whenDefined("recipeMap", styling?.presets),
4318
+ ...whenDefined("variants", styling?.variants),
4319
+ ...whenDefined("defaultVariants", styling?.defaults),
4320
+ ...whenDefined("compoundVariants", styling?.compounds),
4321
+ ...whenDefined("normalizeFn", composedNormalizeFn),
4322
+ ...whenDefined("ariaRules", mergeAriaRules(enforcement?.aria, enforcement?.rules)),
4323
+ ...whenDefined("childRules", enforcement?.children),
4324
+ ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
4325
+ ...whenDefined("allowText", enforcement?.allowText),
4326
+ ...whenDefined("allowedAs", enforcement?.allowedAs),
4327
+ ...whenDefined("precomputedClasses", styling?.precomputedClasses)
4328
+ });
4329
+ }
4330
+ //#endregion
4331
+ //#region ../core/src/options/validate-factory-options.ts
4332
+ /**
4333
+ * Construction-time validation of the variant surface.
4334
+ *
4335
+ * A `presets` selection or `defaults` entry that references a variant key — or a
4336
+ * value of a key — not declared in `variants` resolves to no class at runtime,
4337
+ * silently. TypeScript catches this in typed usage, but untyped JS consumers and
4338
+ * `as`-cast escapes bypass it. This mirrors the type contract at runtime: warn
4339
+ * (`strict: 'warn'`) or throw (`strict: 'throw'`/`true`); a no-op when `false`.
4340
+ *
4341
+ * Runs once per factory (not per render). Render-time checks — unknown
4342
+ * `recipe`, undefined variant value at the call site — are a separate
4343
+ * follow-up (they require `strict` threaded into the class resolver).
4344
+ */
4345
+ function validateFactoryOptions(resolved, diagnostics) {
4346
+ if (!diagnostics.warnActive) return;
4347
+ const name = resolved.displayName ?? "Component";
4348
+ const { variants } = resolved;
4349
+ const checkSelection = (label, selection) => {
4350
+ iterate.forEachEntry(selection, (dim, value) => {
4351
+ if (value === void 0 || value === null) return;
4352
+ if (!variants || !Object.hasOwn(variants, dim)) {
4353
+ diagnostics.error(ContractDiagnostics.unknownVariantDim(name, label, dim));
4354
+ return;
4355
+ }
4356
+ const states = variants[dim];
4357
+ const stateKey = String(value);
4358
+ if (!Object.hasOwn(states, stateKey)) diagnostics.error(ContractDiagnostics.unknownVariantValue(name, label, dim, stateKey, Object.keys(states)));
4359
+ });
4360
+ };
4361
+ const { recipeMap } = resolved;
4362
+ if (recipeMap) iterate.forEachEntry(recipeMap, (recipeKey, selection) => {
4363
+ checkSelection(`preset "${recipeKey}"`, selection);
4364
+ });
4365
+ if (resolved.defaultVariants) checkSelection("defaults", resolved.defaultVariants);
4366
+ }
4367
+ //#endregion
4368
+ //#region ../core/src/options/validate-render-props.ts
4369
+ function label(name) {
4370
+ return name ? `[${name}]` : "[createContractComponent]";
4371
+ }
4372
+ function validateRenderProps(diagnostics, options, props, recipeKey) {
4373
+ const { recipeMap, variants, displayName } = options;
4374
+ const tag = label(displayName);
4375
+ if (recipeKey !== void 0 && (!recipeMap || !Object.hasOwn(recipeMap, recipeKey))) diagnostics.error(ContractDiagnostics.unknownRecipeKey(tag, recipeKey));
4376
+ if (variants) iterate.forEachKey(variants, (key) => {
4377
+ if (!Object.hasOwn(props, key)) return;
4378
+ const value = props[key];
4379
+ if (value === void 0 || value === null) return;
4380
+ const dim = variants[key];
4381
+ if (dim && !Object.hasOwn(dim, String(value))) diagnostics.error(ContractDiagnostics.invalidVariantValue(tag, key, String(value)));
4382
+ });
4383
+ }
4384
+ //#endregion
4385
+ //#region ../../lib/pipeline-kit/src/factory/define-pipeline.ts
4386
+ /** Wraps a PipelineFactory, memoizing the built Pipeline by the resolved config's object
4387
+ * identity. Resolved options are already frozen per-component throughout this codebase
4388
+ * (e.g. resolveFactoryOptions's `Object.freeze(...)`), so a WeakMap keyed on that same frozen
4389
+ * reference is a safe, free cache — calling a factory twice with the same resolved reference
4390
+ * doesn't redundantly rebuild internal resolvers. Modeled on the defineConfig-style helper
4391
+ * convention (Vite/Vitest) for clean generic inference at the call site.
4392
+ *
4393
+ * Note: the return type is intentionally the plain `PipelineFactory`, not wrapped in
4394
+ * type-fest's `Simplify` — Simplify flattens object/intersection types for cleaner hover
4395
+ * tooltips, but applying it to a callable type breaks the contextual typing TypeScript needs
4396
+ * to infer the returned function's own parameter type. */
4397
+ function definePipeline(factory) {
4398
+ const cache = /* @__PURE__ */ new WeakMap();
4399
+ return (resolved) => {
4400
+ const cached = cache.get(resolved);
4401
+ if (cached !== void 0) return cached;
4402
+ const pipeline = factory(resolved);
4403
+ cache.set(resolved, pipeline);
4404
+ return pipeline;
4405
+ };
4406
+ }
4407
+ //#endregion
4408
+ //#region ../core/src/factory/pipelines/generic.ts
4409
+ const createTagPipeline = (resolved) => makeResolveTag(resolved.defaultTag);
4410
+ const createPropsPipeline = (resolved) => (props) => mergeProps$2(resolved.defaultProps, props);
4411
+ const createHtmlPropNormalizersPipeline = () => getHtmlPropNormalizers;
4412
+ const createHtmlChildrenEvaluatorPipeline = () => getHtmlChildrenEvaluator;
4413
+ const createStylingClassPipeline = (resolved) => createClassPipeline(resolved);
4414
+ const memoizedTagPipeline = definePipeline(createTagPipeline);
4415
+ const memoizedPropsPipeline = definePipeline(createPropsPipeline);
4416
+ const memoizedHtmlPropNormalizersPipeline = definePipeline(createHtmlPropNormalizersPipeline);
4417
+ const memoizedHtmlChildrenEvaluatorPipeline = definePipeline(createHtmlChildrenEvaluatorPipeline);
4418
+ const memoizedClassPipeline = definePipeline(createStylingClassPipeline);
4419
+ //#endregion
4420
+ //#region ../core/src/factory/pipelines/aria.ts
4421
+ function resolveAriaRules(resolved) {
4422
+ return [.../* @__PURE__ */ new Set([...HTML_ARIA_RULES, ...resolved.ariaRules ?? []])];
4423
+ }
4424
+ const createAriaPipeline = (resolved) => {
4425
+ const rules = resolveAriaRules(resolved);
4426
+ const engine = new AriaPolicyEngine(resolved.diagnostics, {
4427
+ ...rules.length > 0 && { rules },
4428
+ variantKeys: resolved.variantKeys
4429
+ });
4430
+ return (tag, props, extraProps) => engine.validate(tag, props, extraProps);
4431
+ };
4432
+ const memoizedAriaPipeline = definePipeline(createAriaPipeline);
4433
+ function resolveAriaPassthrough(_tag, props, _extraProps) {
4434
+ return { props };
4435
+ }
4436
+ //#endregion
4437
+ //#region ../core/src/factory/plugin-diagnostics.ts
4438
+ const PluginDiagnostics = {
4439
+ invalidShape(received) {
4440
+ const got = isNull(received) ? "null" : typeof received;
4441
+ return {
4442
+ code: DiagnosticCode.PluginInvalidShape,
4443
+ category: DiagnosticCategory.Internal,
4444
+ message: `[praxis-kit] Plugin factory must return an object with a 'pipeline' function. Got: ${got}.`
4445
+ };
4446
+ },
4447
+ pipelineReturnType(received) {
4448
+ const got = isNull(received) ? "null" : Array.isArray(received) ? "array" : typeof received;
4449
+ return {
4450
+ code: DiagnosticCode.PluginPipelineReturnType,
4451
+ category: DiagnosticCategory.Internal,
4452
+ message: `[praxis-kit] Plugin pipeline must return a string. Got: ${got}.`
4453
+ };
4454
+ }
4455
+ };
4456
+ //#endregion
4457
+ //#region ../core/src/factory/plugin-invariants.ts
4458
+ function assertPluginShape(result) {
4459
+ if (isNull(result) || !isObject(result)) throwDiagnostics.error(PluginDiagnostics.invalidShape(result));
4460
+ if (!isFunction(result.pipeline)) throwDiagnostics.error(PluginDiagnostics.invalidShape(result));
4461
+ }
4462
+ function guardPipeline(pipeline) {
4463
+ if (process.env.NODE_ENV === "production") return pipeline;
4464
+ return function guardedPipeline(tag, props, className, recipe) {
4465
+ const result = pipeline(tag, props, className, recipe);
4466
+ if (!isString(result)) throwDiagnostics.error(PluginDiagnostics.pipelineReturnType(result));
4467
+ return result;
4468
+ };
4469
+ }
4470
+ //#endregion
4471
+ //#region ../core/src/factory/pipelines/styling.ts
4472
+ function resolveClassPlugin(factory, resolved, diagnostics) {
4473
+ if (!factory) return {
4474
+ pluginResult: void 0,
4475
+ classPipeline: memoizedClassPipeline(resolved)
4476
+ };
4477
+ const pluginResult = factory(resolved, diagnostics);
4478
+ assertPluginShape(pluginResult);
4479
+ return {
4480
+ pluginResult,
4481
+ classPipeline: guardPipeline(pluginResult.pipeline)
4482
+ };
4483
+ }
4484
+ //#endregion
4485
+ //#region ../core/src/factory/create-polymorphic.ts
4486
+ function eraseResolvedShape(resolved) {
4487
+ return resolved;
4488
+ }
4489
+ function createPolymorphic(options = {}) {
4490
+ const baseResolved = resolveFactoryOptions(options);
4491
+ const anyBaseResolved = eraseResolvedShape(baseResolved);
4492
+ const resolved = Object.freeze({
4493
+ ...baseResolved,
4494
+ htmlPropNormalizersFn: memoizedHtmlPropNormalizersPipeline(anyBaseResolved),
4495
+ htmlChildrenEvaluatorFn: memoizedHtmlChildrenEvaluatorPipeline(anyBaseResolved)
4496
+ });
4497
+ const anyResolved = eraseResolvedShape(resolved);
4498
+ if (process.env.NODE_ENV !== "production") validateFactoryOptions(resolved, resolved.diagnostics);
4499
+ const { pluginResult, classPipeline } = resolveClassPlugin(options.styling?.plugin, anyResolved, resolved.diagnostics);
4500
+ const resolveTag = memoizedTagPipeline(anyResolved);
4501
+ const resolveProps = memoizedPropsPipeline(anyResolved);
4502
+ const resolveAriaFn = options.enforcement !== void 0 ? memoizedAriaPipeline(anyResolved) : resolveAriaPassthrough;
4503
+ const methods = {
4504
+ resolveTag,
4505
+ resolveProps,
4506
+ resolveClasses(tag, props, className, recipe) {
4507
+ if (process.env.NODE_ENV !== "production") validateRenderProps(resolved.diagnostics, resolved, props, recipe);
4508
+ return classPipeline(tag, props, className, recipe) || void 0;
4509
+ },
4510
+ resolveAria(tag, props, extraProps) {
4511
+ return resolveAriaFn(tag, props, extraProps);
4512
+ }
4513
+ };
4514
+ return pluginResult ? {
4515
+ ...methods,
4516
+ options: resolved,
4517
+ hasStyling: true,
4518
+ classPlugin: pluginResult
4519
+ } : {
4520
+ ...methods,
4521
+ options: resolved
4522
+ };
4523
+ }
4524
+ //#endregion
4525
+ //#region ../core/src/resolver/resolver.ts
4526
+ function enforceAllowedAs(tag, allowedAs, diagnostics, displayName) {
4527
+ if (allowedAs.includes(tag)) return;
4528
+ if (!diagnostics) return;
4529
+ const component = displayName ?? String(tag);
4530
+ diagnostics.error(ContractDiagnostics.allowedAsViolation(String(tag), allowedAs, component));
4531
+ }
4532
+ //#endregion
4533
+ //#region ../../lib/adapter-utils/src/runtime/build-core-runtime.ts
4534
+ const EMPTY_SET = /* @__PURE__ */ new Set();
4535
+ function buildCoreRuntime(normalized) {
4536
+ const runtime = createPolymorphic(normalized);
4537
+ return {
4538
+ runtime,
4539
+ ownedKeys: "classPlugin" in runtime ? runtime.classPlugin.ownedKeys ?? EMPTY_SET : EMPTY_SET
4540
+ };
4541
+ }
4542
+ //#endregion
4543
+ //#region ../../lib/adapter-utils/src/runtime/build-engines.ts
4544
+ function buildEngines(diagnostics, childRules, context, childrenOptions) {
4545
+ const { exclusiveChildren, allowText } = childrenOptions ?? {};
4546
+ return childRules?.length || exclusiveChildren || allowText === false ? { childrenEvaluator: new ChildrenEvaluator(childRules ?? [], diagnostics, context, {
4547
+ exclusiveChildren,
4548
+ allowText
4549
+ }) } : {};
4550
+ }
4551
+ //#endregion
4552
+ //#region ../../lib/adapter-utils/src/runtime/resolve-adapter-common-options.ts
4553
+ /**
4554
+ * Resolves the two fields that every adapter's `normalizeOptions` must provide:
4555
+ * `name` (required string) and `diagnostics` (required Diagnostics instance).
4556
+ *
4557
+ * Called by each adapter's `normalizeOptions` and spread into the result:
4558
+ *
4559
+ * ```ts
4560
+ * function normalizeOptions(options): NormalizedOptions {
4561
+ * return { ...options, ...resolveAdapterCommonOptions(options) } as NormalizedOptions
4562
+ * }
4563
+ * ```
4564
+ *
4565
+ * The defaults (`'PolymorphicComponent'` and `throwDiagnostics`) match the convention used
4566
+ * by React, Vue, Preact, Solid, and Svelte. Adapters with different defaults
4567
+ * (e.g. Lit, Web: `silentDiagnostics`) pass an override argument.
4568
+ */
4569
+ function resolveAdapterCommonOptions(options, defaultName = "PolymorphicComponent", defaultDiagnostics = throwDiagnostics$1) {
4570
+ return {
4571
+ name: options.name ?? defaultName,
4572
+ diagnostics: resolveDiagnostics(options.enforcement?.diagnostics, defaultDiagnostics)
4573
+ };
4574
+ }
4575
+ //#endregion
4576
+ //#region ../../lib/adapter-utils/src/props/apply-filter.ts
4577
+ function applyFilter(props, filterProps, variantKeys) {
4578
+ const out = {};
4579
+ iterate.forEachEntry(props, (k) => {
4580
+ if (!Object.hasOwn(props, k)) return;
4581
+ if (filterProps(k, variantKeys)) return;
4582
+ out[k] = props[k];
4583
+ });
4584
+ return out;
4585
+ }
4586
+ //#endregion
4587
+ //#region ../../lib/adapter-utils/src/props/resolve-normalized-props.ts
4588
+ /**
4589
+ * The single canonical prop-normalization step for every render path — the five
4590
+ * VDOM adapters (React, Preact, Vue, Solid, Svelte) and the host-state path
4591
+ * shared by Lit, Web, and SSR.
4592
+ *
4593
+ * Ordering is fixed and load-bearing:
4594
+ *
4595
+ * ```text
4596
+ * mergedProps → HTML built-in normalizers → normalizeFn
4597
+ * ```
4598
+ *
4599
+ * `normalizeFn` is the primitive's composed `enforcement.props` normalizers plus
4600
+ * the caller's `normalize` option (folded together by `composeNormalizers`).
4601
+ * Running it last means a caller's `normalize` always observes — and can
4602
+ * override — an HTML built-in's output for the same key. This must be identical
4603
+ * across adapters: the same component with the same props has to normalize the
4604
+ * same way whether it renders through React or through SSR.
4605
+ *
4606
+ * `mergedProps` must already be the result of `runtime.resolveProps(rest)`. The
4607
+ * input object is never mutated; a copy is taken only when an HTML normalizer
4608
+ * actually runs — most tags have none (`htmlPropNormalizersFn` returns
4609
+ * `undefined` for every non-form element).
4610
+ */
4611
+ function resolveNormalizedProps(options, tag, mergedProps) {
4612
+ const htmlNormalizers = options.htmlPropNormalizersFn?.(tag);
4613
+ let base = mergedProps;
4614
+ if (htmlNormalizers?.length) {
4615
+ base = { ...mergedProps };
4616
+ for (const normalize of htmlNormalizers) Object.assign(base, normalize(base));
4617
+ }
4618
+ return typeof options.normalizeFn === "function" ? options.normalizeFn(base) : base;
4619
+ }
4620
+ //#endregion
4621
+ //#region ../../lib/adapter-utils/src/props/compose-filter.ts
4622
+ function composeFilter(ownedKeys, filterProps) {
4623
+ const defaultFilter = (key, variantKeys) => variantKeys.has(key) || ownedKeys.has(key);
4624
+ if (!filterProps) return defaultFilter;
4625
+ return (key, variantKeys) => defaultFilter(key, variantKeys) || filterProps(key, variantKeys);
4626
+ }
4627
+ //#endregion
4628
+ //#region ../../adapters/solid/src/slot/slot-validator.ts
4629
+ var SlotValidator = class extends InvariantBase {
4630
+ #name;
4631
+ constructor(name, diagnostics) {
4632
+ super(diagnostics);
4633
+ this.#name = name;
4634
+ }
4635
+ assertExclusive() {
4636
+ this.violate(SlotDiagnostics.exclusive(this.#name));
4637
+ }
4638
+ assertRenderFn(children) {
4639
+ if (typeof children !== "function") {
4640
+ this.violate(SlotDiagnostics.renderFnRequired(this.#name, typeof children));
4641
+ return false;
4642
+ }
4643
+ return true;
4644
+ }
4645
+ };
4646
+ //#endregion
4647
+ //#region ../../adapters/solid/src/build-runtime.ts
4648
+ function normalizeOptions(options) {
4649
+ return {
4650
+ ...options,
4651
+ ...resolveAdapterCommonOptions(options)
4652
+ };
4653
+ }
4654
+ function buildRuntime(options) {
4655
+ const normalized = normalizeOptions(options);
4656
+ const { runtime, ownedKeys } = buildCoreRuntime(normalized);
4657
+ const { childrenEvaluator } = buildEngines(normalized.diagnostics, normalized.enforcement?.children, normalized.name, {
4658
+ exclusiveChildren: normalized.enforcement?.exclusiveChildren,
4659
+ allowText: normalized.enforcement?.allowText
4660
+ });
4661
+ return {
4662
+ runtime,
4663
+ filterProps: composeFilter(ownedKeys, normalized.filterProps),
4664
+ slotValidator: new SlotValidator(normalized.name, normalized.diagnostics),
4665
+ ...childrenEvaluator !== void 0 && { childrenEvaluator }
4666
+ };
4667
+ }
4668
+ //#endregion
4669
+ //#region ../../adapters/solid/src/is-polymorphic-component.ts
4670
+ /**
4671
+ * Type guard narrowing a generated component's real (Solid-specific)
4672
+ * function type down to the public `PolymorphicComponent<G>` interface the
4673
+ * adapter exposes.
4674
+ *
4675
+ * `PolymorphicComponent<G>`'s call signatures reference `G`, which is
4676
+ * erased at runtime, so no guard can check them — the only thing this
4677
+ * interface asserts that's actually observable at runtime is "callable" and
4678
+ * an optional `displayName` of the right kind.
4679
+ */
4680
+ function isPolymorphicComponent(value) {
4681
+ if (!isFunction(value)) return false;
4682
+ if (!("displayName" in value)) return true;
4683
+ return value.displayName === void 0 || isString(value.displayName);
4684
+ }
4685
+ //#endregion
4686
+ //#region ../../adapters/solid/src/render.tsx
4687
+ const SPLIT_KEYS = [
4688
+ "as",
4689
+ "asChild",
4690
+ "children",
4691
+ "class",
4692
+ "recipe",
4693
+ "ref"
4694
+ ];
4695
+ function toChildArray(children) {
4696
+ if (children === void 0 || children === null) return [];
4697
+ if (Array.isArray(children)) return children;
4698
+ return [children];
4699
+ }
4700
+ function buildElementProps(props, classStr, ref, children) {
4701
+ const { role, ...rest } = props;
4702
+ return {
4703
+ ...rest,
4704
+ class: classStr,
4705
+ ...ref !== void 0 && { ref },
4706
+ ...children !== void 0 && { children },
4707
+ ...isKnownAriaRole(role) && { role }
4708
+ };
4709
+ }
4710
+ function buildSlotProps(props, classStr, ref) {
4711
+ const { role, ...rest } = props;
4712
+ return {
4713
+ ...rest,
4714
+ class: classStr,
4715
+ ...ref !== void 0 && { ref },
4716
+ ...isKnownAriaRole(role) && { role }
4717
+ };
4718
+ }
4719
+ function resolveTag(runtime, as) {
4720
+ return runtime.resolveTag(as);
4721
+ }
4722
+ function resolveDomProps(tag, elementProps, runtime, normalizedProps) {
4723
+ return isString(tag) ? runtime.resolveAria(tag, elementProps, normalizedProps).props : elementProps;
4724
+ }
4725
+ function tryRenderAsChild(known, filteredProps, resolvedClass, slotValidator) {
4726
+ if (!known.asChild) return null;
4727
+ if (known.as !== void 0) {
4728
+ slotValidator.assertExclusive();
4729
+ return null;
4730
+ }
4731
+ if (!slotValidator.assertRenderFn(known.children)) return null;
4732
+ const renderFn = known.children;
4733
+ return createMemo(() => renderFn(buildSlotProps(filteredProps(), resolvedClass(), known.ref)));
4734
+ }
4735
+ function render({ runtime, props, filterProps, slotValidator, childrenEvaluator }) {
4736
+ const [knownRaw, rest] = splitProps(props, SPLIT_KEYS);
4737
+ const known = knownRaw;
4738
+ const tag = createMemo(() => resolveTag(runtime, known.as));
4739
+ const mergedProps = createMemo(() => runtime.resolveProps(rest));
4740
+ const normalizedProps = createMemo(() => resolveNormalizedProps(runtime.options, tag(), mergedProps()));
4741
+ const resolvedClass = createMemo(() => runtime.resolveClasses(tag(), normalizedProps(), known.class, known.recipe));
4742
+ const filteredProps = createMemo(() => applyFilter(normalizedProps(), filterProps, runtime.options.variantKeys));
4743
+ const { allowedAs } = runtime.options;
4744
+ if (allowedAs !== void 0) createEffect(() => enforceAllowedAs(tag(), allowedAs, runtime.options.diagnostics, runtime.options.displayName));
4745
+ if (process.env.NODE_ENV !== "production" && childrenEvaluator) createEffect(() => childrenEvaluator.evaluate(toChildArray(known.children), {
4746
+ tag: tag(),
4747
+ props: normalizedProps()
4748
+ }));
4749
+ if (process.env.NODE_ENV !== "production") createEffect(() => runtime.options.htmlChildrenEvaluatorFn?.(tag())?.evaluate(toChildArray(known.children), {
4750
+ tag: tag(),
4751
+ props: normalizedProps()
4752
+ }));
4753
+ const slotResult = tryRenderAsChild(known, filteredProps, resolvedClass, slotValidator);
4754
+ if (slotResult !== null) return slotResult;
4755
+ const domProps = createMemo(() => {
4756
+ const ep = buildElementProps(filteredProps(), resolvedClass(), known.ref, known.children);
4757
+ return resolveDomProps(tag(), ep, runtime, normalizedProps());
4758
+ });
4759
+ return createComponent(Dynamic, mergeProps$1({ get component() {
4760
+ return tag();
4761
+ } }, () => domProps()));
4762
+ }
4763
+ //#endregion
4764
+ //#region ../../adapters/solid/src/to-solid-factory-options.ts
4765
+ /** Solid-specific addition on top of `FactoryOptions`. */
4766
+ const SOLID_FIELD_VALIDATORS = { filterProps: (v) => v === void 0 || isFunction(v) };
4767
+ /**
4768
+ * Type guard narrowing the generic `FactoryOptions` shape down to
4769
+ * `SolidFactoryOptions` — the type `buildRuntime` is declared against. See
4770
+ * `isFactoryOptionsLike` for what this does and doesn't validate.
4771
+ */
4772
+ function isSolidFactoryOptions(options) {
4773
+ return isFactoryOptionsLike(options, SOLID_FIELD_VALIDATORS);
4774
+ }
4775
+ //#endregion
4776
+ //#region ../../adapters/solid/src/create-contract-component.ts
4777
+ /**
4778
+ * Creates a polymorphic Solid component with praxis-kit contracts applied.
4779
+ *
4780
+ * ```tsx
4781
+ * const Button = createContractComponent({
4782
+ * tag: 'button',
4783
+ * name: 'Button',
4784
+ * styling: {
4785
+ * base: 'btn',
4786
+ * variants: { intent: { primary: 'btn--primary', ghost: 'btn--ghost' } },
4787
+ * defaults: { intent: 'primary' },
4788
+ * },
4789
+ * })
4790
+ *
4791
+ * <Button intent="ghost" as="a" href="/home">Home</Button>
4792
+ * ```
4793
+ *
4794
+ * `ref` is forwarded as an ordinary Solid ref callback. Pass `subComponents` to attach named
4795
+ * sub-components (`Card.Header`) and `onElement` to run setup once the real DOM element exists.
4796
+ */
4797
+ function createContractComponent(options) {
4798
+ invariant(isSolidFactoryOptions(options), "options is not a valid SolidFactoryOptions object");
4799
+ const bundle = buildRuntime(options);
4800
+ const { onElement } = options;
4801
+ const Component = (props) => {
4802
+ const propsWithRef = onElement ? mergeProps(props, { get ref() {
4803
+ const consumerRef = props.ref;
4804
+ return (el) => {
4805
+ if (typeof consumerRef === "function") consumerRef(el);
4806
+ const cleanup = onElement(el, () => props);
4807
+ if (cleanup) onCleanup(cleanup);
4808
+ };
4809
+ } }) : props;
4810
+ return render({
4811
+ ...bundle,
4812
+ props: propsWithRef
4813
+ });
4814
+ };
4815
+ applyDisplayName(Component, options.name);
4816
+ const assembled = finalizeComponent(Component, bundle.runtime.options.defaultTag, options.subComponents);
4817
+ invariant(isPolymorphicComponent(assembled), "Generated component failed to satisfy the PolymorphicComponent shape");
4818
+ return assembled;
4819
+ }
4820
+ //#endregion
4821
+ export { createContractComponent, defineContractComponent };