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