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