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