praxis-kit 3.1.1 → 4.0.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.
@@ -1,44 +1,14 @@
1
- // ../../lib/adapter-utils/src/define-component.ts
2
- function defineContractComponent(options) {
3
- return (factory) => factory(options);
4
- }
5
-
6
- // ../core/dist/chunk-KKSHJDE7.js
1
+ // ../../lib/primitive/src/tag/resolve-tag.ts
7
2
  function makeResolveTag(defaultTag) {
8
3
  return function tag(as) {
9
4
  return as ?? defaultTag;
10
5
  };
11
6
  }
12
- function assertNever(value) {
13
- throw new Error(`Unexpected value: ${String(value)}`);
14
- }
15
- function r(e) {
16
- var t, f, n = "";
17
- if ("string" == typeof e || "number" == typeof e) n += e;
18
- else if ("object" == typeof e) if (Array.isArray(e)) {
19
- var o = e.length;
20
- for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
21
- } else for (f in e) e[f] && (n && (n += " "), n += f);
22
- return n;
23
- }
24
- function clsx() {
25
- for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
26
- return n;
27
- }
28
- function cn(...inputs) {
29
- return clsx(...inputs);
30
- }
31
- function mergeProps(defaultProps, props) {
32
- return {
33
- ...defaultProps ?? {},
34
- ...props
35
- };
36
- }
37
- function isNull(value) {
38
- return value === null;
39
- }
40
- function isObject(value) {
41
- return !isNull(value) && typeof value === "object";
7
+
8
+ // ../../lib/primitive/src/utils/is-object.ts
9
+ function isObject(value, excludeArrays = false) {
10
+ if (value === null || typeof value !== "object") return false;
11
+ return excludeArrays ? !Array.isArray(value) : true;
42
12
  }
43
13
  function isString(value) {
44
14
  return typeof value === "string";
@@ -46,308 +16,197 @@ function isString(value) {
46
16
  function isNumber(value) {
47
17
  return typeof value === "number";
48
18
  }
49
- var EMPTY_VARIANT_KEYS = /* @__PURE__ */ new Set();
50
- function composeNormalizers(normalizers, fn) {
51
- if (!normalizers?.length) return fn;
52
- return ((props) => {
53
- const patched = normalizers.reduce(
54
- (acc, normalizer) => ({ ...acc, ...normalizer(acc) }),
55
- props
56
- );
57
- return fn ? fn(patched) : patched;
58
- });
19
+
20
+ // ../../lib/primitive/src/utils/assert-never.ts
21
+ function assertNever(value) {
22
+ throw new Error(`Unexpected value: ${String(value)}`);
59
23
  }
60
- function whenDefined(key, value) {
61
- return value === void 0 ? {} : { [key]: value };
24
+
25
+ // ../../lib/primitive/src/utils/cn.ts
26
+ import { clsx } from "clsx";
27
+ function cn(...inputs) {
28
+ return clsx(...inputs);
62
29
  }
63
- function resolveFactoryOptions(options = {}) {
64
- const { styling, enforcement } = options;
65
- const composedNormalizeFn = composeNormalizers(enforcement?.props, options.normalize);
66
- const variantKeys = styling?.variants === void 0 ? EMPTY_VARIANT_KEYS : new Set(Object.keys(styling.variants));
67
- return Object.freeze({
68
- defaultTag: options.tag ?? "div",
69
- strict: enforcement?.strict ?? false,
70
- variantKeys,
71
- ...whenDefined("displayName", options.name),
72
- ...whenDefined("defaultProps", options.defaults),
73
- ...whenDefined("baseClassName", styling?.base),
74
- ...whenDefined("tagMap", styling?.tags),
75
- ...whenDefined("recipeMap", styling?.presets),
76
- ...whenDefined("variants", styling?.variants),
77
- ...whenDefined("defaultVariants", styling?.defaults),
78
- ...whenDefined("compoundVariants", styling?.compounds),
79
- ...whenDefined("normalizeFn", composedNormalizeFn),
80
- ...whenDefined("ariaRules", enforcement?.aria),
81
- ...whenDefined("childRules", enforcement?.children),
82
- ...whenDefined("allowedAs", enforcement?.allowedAs),
83
- ...whenDefined("precomputedClasses", styling?.precomputedClasses)
84
- });
30
+
31
+ // ../../lib/primitive/src/utils/iterate.ts
32
+ function find(iterable, callback) {
33
+ for (const value of iterable) {
34
+ const result = callback(value);
35
+ if (result != null) {
36
+ return result;
37
+ }
38
+ }
39
+ return null;
85
40
  }
86
- function report(strict, message) {
87
- if (strict === false) return;
88
- const mode = strict === true ? "throw" : strict;
89
- if (mode === "throw") throw new Error(message);
90
- console.warn(message);
41
+ function some(iterable, predicate) {
42
+ for (const value of iterable) {
43
+ if (predicate(value)) return true;
44
+ }
45
+ return false;
91
46
  }
92
- function validateFactoryOptions(resolved) {
93
- const { strict } = resolved;
94
- if (strict === false) return;
95
- const name = resolved.displayName ?? "Component";
96
- const { variants } = resolved;
97
- const checkSelection = (label2, selection) => {
98
- const record = selection;
99
- for (const dim in record) {
100
- const value = record[dim];
101
- if (value === void 0 || value === null) continue;
102
- if (!variants || !Object.hasOwn(variants, dim)) {
103
- report(strict, `${name}: ${label2} references unknown variant "${dim}".`);
104
- continue;
105
- }
106
- const states = variants[dim];
107
- const stateKey = String(value);
108
- if (!Object.hasOwn(states, stateKey)) {
109
- report(
110
- strict,
111
- `${name}: ${label2} sets "${dim}" to unknown value "${stateKey}" (valid: ${Object.keys(states).join(", ")}).`
112
- );
113
- }
47
+ function every(iterable, predicate) {
48
+ let index = 0;
49
+ for (const value of iterable) {
50
+ if (!predicate(value, index++)) {
51
+ return false;
114
52
  }
115
- };
116
- const { recipeMap } = resolved;
117
- if (recipeMap) {
118
- for (const recipeKey in recipeMap) {
119
- checkSelection(`preset "${recipeKey}"`, recipeMap[recipeKey]);
53
+ }
54
+ return true;
55
+ }
56
+ function* filter(iterable, predicate) {
57
+ let index = 0;
58
+ for (const value of iterable) {
59
+ if (predicate(value, index++)) {
60
+ yield value;
120
61
  }
121
62
  }
122
- if (resolved.defaultVariants) checkSelection("defaults", resolved.defaultVariants);
123
63
  }
124
- var warned = /* @__PURE__ */ new Set();
125
- var pendingAsyncWarns = /* @__PURE__ */ new Set();
126
- var asyncWarnScheduled = false;
127
- function flushAsyncWarns() {
128
- asyncWarnScheduled = false;
129
- const messages = [...pendingAsyncWarns];
130
- pendingAsyncWarns.clear();
131
- for (const msg of messages) {
132
- console.warn(msg);
133
- }
134
- }
135
- function report2(strict, message) {
136
- if (!strict) return;
137
- const mode = strict === true ? "throw" : strict;
138
- if (mode === "throw") throw new Error(message);
139
- if (mode === "async-warn") {
140
- if (pendingAsyncWarns.has(message)) return;
141
- pendingAsyncWarns.add(message);
142
- if (!asyncWarnScheduled) {
143
- asyncWarnScheduled = true;
144
- queueMicrotask(flushAsyncWarns);
64
+ function* map(iterable, callback) {
65
+ let index = 0;
66
+ for (const value of iterable) {
67
+ yield callback(value, index++);
68
+ }
69
+ }
70
+ function forEach(iterable, callback) {
71
+ let index = 0;
72
+ for (const value of iterable) {
73
+ callback(value, index++);
74
+ }
75
+ }
76
+ function reduce(iterable, initial, callback) {
77
+ let accumulator = initial;
78
+ let index = 0;
79
+ for (const value of iterable) {
80
+ accumulator = callback(accumulator, value, index++);
81
+ }
82
+ return accumulator;
83
+ }
84
+ function collect(iterable, callback) {
85
+ const result = {};
86
+ let index = 0;
87
+ for (const value of iterable) {
88
+ const entry = callback(value, index++);
89
+ if (entry === null) {
90
+ return null;
145
91
  }
146
- return;
92
+ result[entry[0]] = entry[1];
147
93
  }
148
- if (warned.has(message)) return;
149
- warned.add(message);
150
- console.warn(message);
94
+ return result;
151
95
  }
152
- function label(name) {
153
- return name ? `[${name}]` : "[createContractComponent]";
96
+ function findLast(value, callback) {
97
+ for (let index = value.length - 1; index >= 0; index--) {
98
+ const result = callback(value[index], index);
99
+ if (result != null) {
100
+ return result;
101
+ }
102
+ }
103
+ return null;
154
104
  }
155
- function validateRenderProps(options, props, recipeKey) {
156
- const { strict, recipeMap, variants, displayName } = options;
157
- if (!strict) return;
158
- const tag = label(displayName);
159
- if (recipeKey !== void 0 && (!recipeMap || !Object.hasOwn(recipeMap, recipeKey))) {
160
- report2(strict, `${tag} Unknown recipeKey "${recipeKey}" \u2014 no preset with that name exists.`);
105
+ function* items(collection) {
106
+ for (let i = 0; i < collection.length; i++) {
107
+ const item = collection.item(i);
108
+ if (item !== null) {
109
+ yield item;
110
+ }
161
111
  }
162
- if (variants) {
163
- for (const key in variants) {
164
- if (!Object.hasOwn(props, key)) continue;
165
- const value = props[key];
166
- if (value === void 0 || value === null) continue;
167
- const dim = variants[key];
168
- if (dim && !Object.hasOwn(dim, String(value))) {
169
- report2(
170
- strict,
171
- `${tag} Variant "${key}=${String(value)}" is not a defined value for the "${key}" dimension.`
172
- );
112
+ }
113
+ function nodeList(list) {
114
+ return {
115
+ *[Symbol.iterator]() {
116
+ for (let i = 0; i < list.length; i++) {
117
+ const node = list.item(i);
118
+ if (node !== null) {
119
+ yield node;
120
+ }
173
121
  }
174
122
  }
123
+ };
124
+ }
125
+ function mapEntries(m) {
126
+ return m.entries();
127
+ }
128
+ function set(s) {
129
+ return s.values();
130
+ }
131
+ function hasOwn(object, key) {
132
+ return Object.hasOwn(object, key);
133
+ }
134
+ function* entries(object) {
135
+ for (const key in object) {
136
+ if (!hasOwn(object, key)) continue;
137
+ yield [key, object[key]];
175
138
  }
176
139
  }
177
- function panic(message) {
178
- throw new Error(message);
140
+ function* keys(object) {
141
+ for (const [key] of entries(object)) {
142
+ yield key;
143
+ }
179
144
  }
180
- function describe(value) {
181
- if (value === null) return "null";
182
- if (Array.isArray(value)) return "array";
183
- return typeof value;
145
+ function* values(object) {
146
+ for (const [, value] of entries(object)) {
147
+ yield value;
148
+ }
184
149
  }
185
- function assertPluginShape(result) {
186
- if (result === null || typeof result !== "object")
187
- panic(
188
- `[praxis-kit] Plugin factory must return an object with a 'pipeline' function. Got: ${result === null ? "null" : typeof result}.`
189
- );
190
- const plugin = result;
191
- if (typeof plugin.pipeline !== "function")
192
- panic(
193
- `[praxis-kit] Plugin factory return value is missing a 'pipeline' function. Got pipeline: ${typeof plugin.pipeline}.`
194
- );
150
+ function mapValues(object, callback) {
151
+ const result = {};
152
+ for (const [key, value] of entries(object)) {
153
+ result[key] = callback(value, key);
154
+ }
155
+ return result;
195
156
  }
196
- function guardPipeline(pipeline) {
197
- if (process.env.NODE_ENV === "production") return pipeline;
198
- return function guardedPipeline(tag, props, className, recipe) {
199
- const result = pipeline(tag, props, className, recipe);
200
- if (typeof result !== "string")
201
- panic(`[praxis-kit] Plugin pipeline must return a string. Got: ${describe(result)}.`);
202
- return result;
203
- };
157
+ function forEachEntry(object, callback) {
158
+ for (const [key, value] of entries(object)) {
159
+ callback(key, value);
160
+ }
204
161
  }
205
- var NOOP_CLASS_PIPELINE = (_tag, _props, className) => Array.isArray(className) ? className.join(" ") : className ?? "";
206
- function resolveClassPipeline(options, resolved, strict, capabilities) {
207
- const factory = options.styling?.plugin;
208
- if (!factory) {
209
- const createClassPipeline2 = capabilities?.createClassPipeline;
210
- const classPipeline2 = createClassPipeline2 ? createClassPipeline2(resolved) : NOOP_CLASS_PIPELINE;
211
- return { pluginResult: void 0, classPipeline: classPipeline2 };
162
+ function forEachKey(object, callback) {
163
+ for (const key of keys(object)) {
164
+ callback(key);
212
165
  }
213
- const pluginResult = factory(resolved, strict);
214
- assertPluginShape(pluginResult);
215
- const classPipeline = guardPipeline(pluginResult.pipeline);
216
- return { pluginResult, classPipeline };
217
166
  }
218
- function createRuntimeMethods(resolved, classPipeline, engine) {
219
- return {
220
- resolveTag: makeResolveTag(resolved.defaultTag),
221
- resolveProps(props) {
222
- return mergeProps(resolved.defaultProps, props);
223
- },
224
- resolveClasses(tag, props, className, recipe) {
225
- if (process.env.NODE_ENV !== "production") {
226
- validateRenderProps(resolved, props, recipe);
227
- }
228
- return classPipeline(tag, props, className, recipe);
229
- },
230
- resolveAria(tag, props) {
231
- if (!engine) return { props };
232
- const result = engine.validate(tag, props);
233
- return { props: result.props };
234
- }
235
- };
167
+ function forEachValue(object, callback) {
168
+ for (const value of values(object)) {
169
+ callback(value);
170
+ }
236
171
  }
237
- function createRuntimeObject(methods, resolved, pluginResult) {
238
- return pluginResult ? { ...methods, options: resolved, hasStyling: true, classPlugin: pluginResult } : { ...methods, options: resolved };
172
+ function forEachSet(s, callback) {
173
+ for (const value of s) {
174
+ callback(value);
175
+ }
239
176
  }
240
- function createPolymorphic(options = {}, capabilities) {
241
- const baseResolved = resolveFactoryOptions(options);
242
- const resolved = capabilities?.htmlPropNormalizersFn !== void 0 ? Object.freeze({
243
- ...baseResolved,
244
- htmlPropNormalizersFn: capabilities.htmlPropNormalizersFn
245
- }) : baseResolved;
246
- if (process.env.NODE_ENV !== "production") validateFactoryOptions(resolved);
247
- const { pluginResult, classPipeline } = resolveClassPipeline(
248
- options,
249
- resolved,
250
- resolved.strict,
251
- capabilities
252
- );
253
- const allAriaRules = [
254
- .../* @__PURE__ */ new Set([...capabilities?.htmlAriaRules ?? [], ...resolved.ariaRules ?? []])
255
- ];
256
- const engine = options.enforcement !== void 0 && capabilities?.AriaEngine ? new capabilities.AriaEngine(
257
- resolved.strict,
258
- allAriaRules.length ? { rules: allAriaRules } : void 0
259
- ) : null;
260
- const methods = createRuntimeMethods(resolved, classPipeline, engine);
261
- return createRuntimeObject(
262
- methods,
263
- resolved,
264
- pluginResult
265
- );
177
+ var iterate = Object.freeze({
178
+ entries,
179
+ filter,
180
+ find,
181
+ findLast,
182
+ forEach,
183
+ forEachEntry,
184
+ forEachKey,
185
+ forEachSet,
186
+ forEachValue,
187
+ items,
188
+ keys,
189
+ map,
190
+ mapEntries,
191
+ mapValues,
192
+ nodeList,
193
+ reduce,
194
+ collect,
195
+ set,
196
+ some,
197
+ every,
198
+ values
199
+ });
200
+
201
+ // ../../lib/primitive/src/utils/merge-props.ts
202
+ function mergeProps(defaultProps, props) {
203
+ return {
204
+ ...defaultProps ?? {},
205
+ ...props
206
+ };
266
207
  }
267
- var KNOWN_ARIA_ROLES = [
268
- "alert",
269
- "alertdialog",
270
- "application",
271
- "article",
272
- "banner",
273
- "blockquote",
274
- "button",
275
- "caption",
276
- "cell",
277
- "checkbox",
278
- "code",
279
- "columnheader",
280
- "combobox",
281
- "complementary",
282
- "contentinfo",
283
- "definition",
284
- "deletion",
285
- "dialog",
286
- "document",
287
- "emphasis",
288
- "feed",
289
- "figure",
290
- "form",
291
- "generic",
292
- "grid",
293
- "gridcell",
294
- "group",
295
- "heading",
296
- "img",
297
- "insertion",
298
- "link",
299
- "list",
300
- "listbox",
301
- "listitem",
302
- "log",
303
- "main",
304
- "marquee",
305
- "math",
306
- "menu",
307
- "menubar",
308
- "menuitem",
309
- "menuitemcheckbox",
310
- "menuitemradio",
311
- "meter",
312
- "navigation",
313
- "none",
314
- "note",
315
- "option",
316
- "paragraph",
317
- "presentation",
318
- "progressbar",
319
- "radio",
320
- "radiogroup",
321
- "region",
322
- "row",
323
- "rowgroup",
324
- "rowheader",
325
- "scrollbar",
326
- "search",
327
- "searchbox",
328
- "separator",
329
- "slider",
330
- "spinbutton",
331
- "status",
332
- "strong",
333
- "subscript",
334
- "superscript",
335
- "switch",
336
- "tab",
337
- "table",
338
- "tablist",
339
- "tabpanel",
340
- "term",
341
- "textbox",
342
- "time",
343
- "timer",
344
- "toolbar",
345
- "tooltip",
346
- "tree",
347
- "treegrid",
348
- "treeitem"
349
- ];
350
- var KNOWN_ARIA_ROLES_SET = new Set(KNOWN_ARIA_ROLES);
208
+
209
+ // ../../lib/primitive/src/constants/aria/global-aria-attributes.ts
351
210
  var GLOBAL_ARIA_ATTRIBUTES = /* @__PURE__ */ new Set([
352
211
  "aria-atomic",
353
212
  "aria-busy",
@@ -367,29 +226,59 @@ var GLOBAL_ARIA_ATTRIBUTES = /* @__PURE__ */ new Set([
367
226
  "aria-relevant",
368
227
  "aria-roledescription"
369
228
  ]);
229
+
230
+ // ../../lib/primitive/src/constants/aria/implicit-role-record.ts
370
231
  var IMPLICIT_ROLE_RECORD = {
232
+ // Landmarks
371
233
  article: "article",
372
234
  aside: "complementary",
373
235
  footer: "contentinfo",
374
236
  header: "banner",
375
237
  main: "main",
376
238
  nav: "navigation",
377
- button: "button",
239
+ // Interactive
378
240
  a: "link",
241
+ button: "button",
379
242
  select: "listbox",
243
+ textarea: "textbox",
244
+ // Headings
380
245
  h1: "heading",
381
246
  h2: "heading",
382
247
  h3: "heading",
383
248
  h4: "heading",
384
249
  h5: "heading",
385
250
  h6: "heading",
251
+ // Lists
386
252
  ul: "list",
387
253
  ol: "list",
388
254
  li: "listitem",
255
+ // Tables
389
256
  table: "table",
390
257
  tr: "row",
391
258
  td: "cell",
392
- th: "columnheader"
259
+ th: "columnheader",
260
+ // Structural / semantic
261
+ dialog: "dialog",
262
+ fieldset: "group",
263
+ figure: "figure",
264
+ meter: "meter",
265
+ output: "status",
266
+ progress: "progressbar"
267
+ };
268
+ var INPUT_TYPE_ROLE_MAP = {
269
+ checkbox: "checkbox",
270
+ radio: "radio",
271
+ range: "slider",
272
+ number: "spinbutton",
273
+ search: "searchbox",
274
+ text: "textbox",
275
+ email: "textbox",
276
+ tel: "textbox",
277
+ url: "textbox",
278
+ button: "button",
279
+ submit: "button",
280
+ reset: "button",
281
+ image: "button"
393
282
  };
394
283
  var STRONG_ROLES = [
395
284
  "main",
@@ -401,6 +290,8 @@ var STRONG_ROLES = [
401
290
  var STANDALONE_ROLES = ["article"];
402
291
  var STRONG_ROLES_SET = new Set(STRONG_ROLES);
403
292
  var STANDALONE_ROLES_SET = new Set(STANDALONE_ROLES);
293
+
294
+ // ../../lib/primitive/src/constants/aria/role-restricted-attributes.ts
404
295
  var ROLE_RESTRICTED_ATTRIBUTES = /* @__PURE__ */ new Map([
405
296
  [
406
297
  "aria-activedescendant",
@@ -550,9 +441,18 @@ var ROLE_RESTRICTED_ATTRIBUTES = /* @__PURE__ */ new Map([
550
441
  /* @__PURE__ */ new Set(["meter", "progressbar", "scrollbar", "separator", "slider", "spinbutton"])
551
442
  ]
552
443
  ]);
444
+
445
+ // ../../lib/primitive/src/guards/foundational/is-defined.ts
553
446
  function isUndefined(value) {
554
447
  return value === void 0;
555
448
  }
449
+
450
+ // ../../lib/primitive/src/guards/foundational/is-null.ts
451
+ function isNull(value) {
452
+ return value === null;
453
+ }
454
+
455
+ // ../../lib/primitive/src/guards/aria/is-aria-attribute.ts
556
456
  function isGlobalAriaAttribute(attr) {
557
457
  return GLOBAL_ARIA_ATTRIBUTES.has(attr);
558
458
  }
@@ -562,6 +462,8 @@ function isAriaAttributeValidForRole(attr, role) {
562
462
  if (isUndefined(role)) return false;
563
463
  return allowedRoles.has(role);
564
464
  }
465
+
466
+ // ../../lib/primitive/src/guards/aria/is-aria-role.ts
565
467
  function isStrongImplicitRole(tag) {
566
468
  if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
567
469
  return STRONG_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
@@ -570,116 +472,543 @@ function isStandaloneTag(tag) {
570
472
  if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
571
473
  return STANDALONE_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
572
474
  }
573
-
574
- // ../core/dist/chunk-2NJ5XLOA.js
575
- var IMPLICIT_ROLE_RECORD2 = {
576
- article: "article",
577
- aside: "complementary",
578
- footer: "contentinfo",
579
- header: "banner",
580
- main: "main",
581
- nav: "navigation",
582
- button: "button",
583
- a: "link",
584
- select: "listbox",
585
- h1: "heading",
586
- h2: "heading",
587
- h3: "heading",
588
- h4: "heading",
589
- h5: "heading",
590
- h6: "heading",
591
- ul: "list",
592
- ol: "list",
593
- li: "listitem",
594
- table: "table",
595
- tr: "row",
596
- td: "cell",
597
- th: "columnheader"
598
- };
599
- function getImplicitRole(tag) {
600
- if (tag in IMPLICIT_ROLE_RECORD2) return IMPLICIT_ROLE_RECORD2[tag];
601
- return void 0;
475
+ function getInputImplicitRole(type) {
476
+ if (type == null || !(type in INPUT_TYPE_ROLE_MAP)) return void 0;
477
+ return INPUT_TYPE_ROLE_MAP[type];
602
478
  }
603
- var COMPONENT_DEFAULT_TAG = /* @__PURE__ */ Symbol.for("praxis.component-default-tag");
604
- function getAsProp(child) {
605
- if (!isObject(child) || !("props" in child)) return void 0;
606
- const props = child.props;
607
- if (!isObject(props)) return void 0;
608
- const as = props.as;
609
- return isString(as) && as !== "" ? as : void 0;
479
+ function getConditionalImplicitRole(tag, ariaLabel, ariaLabelledBy) {
480
+ const isNamed = typeof ariaLabel === "string" || typeof ariaLabelledBy === "string";
481
+ if (!isNamed) return void 0;
482
+ if (tag === "section") return "region";
483
+ if (tag === "form") return "form";
484
+ return void 0;
610
485
  }
611
- function getTag(child) {
612
- if (!isObject(child) || !("type" in child)) return void 0;
613
- const t = child.type;
614
- if (isString(t)) return t;
615
- if (typeof t === "function" || isObject(t)) {
616
- const defaultTag = t[COMPONENT_DEFAULT_TAG];
617
- if (!isString(defaultTag)) return void 0;
618
- return getAsProp(child) ?? defaultTag;
486
+
487
+ // ../../lib/contract/src/aria/aria-role-policy.ts
488
+ function getImplicitRole(tag, props) {
489
+ if (tag in IMPLICIT_ROLE_RECORD) return IMPLICIT_ROLE_RECORD[tag];
490
+ if (tag === "input") return getInputImplicitRole(props?.type);
491
+ if (tag === "img") return props?.alt === "" ? "none" : "img";
492
+ if (tag === "section" || tag === "form") {
493
+ return getConditionalImplicitRole(tag, props?.["aria-label"], props?.["aria-labelledby"]);
619
494
  }
620
495
  return void 0;
621
496
  }
622
- function isTag(...args) {
623
- if (isString(args[0])) {
624
- const set2 = new Set(args);
625
- return (child2) => {
626
- const tag2 = getTag(child2);
627
- return tag2 !== void 0 && set2.has(tag2);
628
- };
497
+
498
+ // ../../lib/contract/src/strict/invariant-base.ts
499
+ var InvariantBase = class {
500
+ diagnostics;
501
+ constructor(diagnostics) {
502
+ this.diagnostics = diagnostics;
629
503
  }
630
- const [child, ...tags] = args;
631
- const set = new Set(tags);
632
- const tag = getTag(child);
633
- return tag !== void 0 && set.has(tag);
634
- }
635
- var pendingAsyncWarns2 = /* @__PURE__ */ new Set();
636
- var asyncWarnScheduled2 = false;
637
- function flushAsyncWarns2() {
638
- asyncWarnScheduled2 = false;
639
- const messages = [...pendingAsyncWarns2];
640
- pendingAsyncWarns2.clear();
641
- for (const msg of messages) {
642
- console.warn(msg);
643
- }
644
- }
645
- function scheduleAsyncWarn(message) {
646
- if (pendingAsyncWarns2.has(message)) return;
647
- pendingAsyncWarns2.add(message);
648
- if (!asyncWarnScheduled2) {
649
- asyncWarnScheduled2 = true;
650
- queueMicrotask(flushAsyncWarns2);
651
- }
652
- }
653
- var StrictBase = class {
654
- strict;
655
- constructor(strict) {
656
- this.strict = strict;
657
- }
658
- violate(message) {
659
- if (this.strict === true || this.strict === "throw") {
660
- throw new Error(message);
661
- }
662
- this.warn(message);
504
+ get active() {
505
+ return this.diagnostics.active;
506
+ }
507
+ violate(input) {
508
+ this.diagnostics.error(input);
663
509
  }
664
- // Always caps at console.warn — never throws. ARIA 'warning' violations route here
510
+ // Always caps at warn — never throws. ARIA 'warning' violations route here
665
511
  // so they surface even in strict='throw' mode without aborting a render.
666
- warn(message) {
667
- if (!this.strict) return;
668
- if (this.strict === "async-warn") {
669
- scheduleAsyncWarn(message);
670
- return;
671
- }
672
- console.warn(message);
512
+ warn(input) {
513
+ this.diagnostics.warn(input);
673
514
  }
674
- invariant(condition, message) {
675
- if (!condition) {
676
- this.violate(message);
677
- }
515
+ invariant(condition, input) {
516
+ if (!condition) this.violate(input);
678
517
  }
679
518
  };
680
- function isNull2(value) {
681
- return value === null;
519
+
520
+ // ../../lib/diagnostics/src/category.ts
521
+ var DiagnosticCategory = /* @__PURE__ */ ((DiagnosticCategory2) => {
522
+ DiagnosticCategory2[DiagnosticCategory2["Contract"] = 0] = "Contract";
523
+ DiagnosticCategory2[DiagnosticCategory2["HTML"] = 1] = "HTML";
524
+ DiagnosticCategory2[DiagnosticCategory2["ARIA"] = 2] = "ARIA";
525
+ DiagnosticCategory2[DiagnosticCategory2["Composition"] = 3] = "Composition";
526
+ DiagnosticCategory2[DiagnosticCategory2["Rendering"] = 4] = "Rendering";
527
+ DiagnosticCategory2[DiagnosticCategory2["Accessibility"] = 5] = "Accessibility";
528
+ DiagnosticCategory2[DiagnosticCategory2["Performance"] = 6] = "Performance";
529
+ DiagnosticCategory2[DiagnosticCategory2["Internal"] = 7] = "Internal";
530
+ DiagnosticCategory2[DiagnosticCategory2["Deprecation"] = 8] = "Deprecation";
531
+ DiagnosticCategory2[DiagnosticCategory2["Lint"] = 9] = "Lint";
532
+ return DiagnosticCategory2;
533
+ })(DiagnosticCategory || {});
534
+
535
+ // ../../lib/diagnostics/src/error.ts
536
+ var PraxisError = class extends Error {
537
+ diagnostic;
538
+ constructor(diagnostic) {
539
+ super(diagnostic.message);
540
+ this.name = "PraxisError";
541
+ this.diagnostic = diagnostic;
542
+ }
543
+ };
544
+
545
+ // ../../lib/diagnostics/src/severity.ts
546
+ var Severity = /* @__PURE__ */ ((Severity2) => {
547
+ Severity2[Severity2["Debug"] = 0] = "Debug";
548
+ Severity2[Severity2["Info"] = 1] = "Info";
549
+ Severity2[Severity2["Warning"] = 2] = "Warning";
550
+ Severity2[Severity2["Error"] = 3] = "Error";
551
+ Severity2[Severity2["Fatal"] = 4] = "Fatal";
552
+ return Severity2;
553
+ })(Severity || {});
554
+
555
+ // ../../lib/diagnostics/src/policy.ts
556
+ var DefaultPolicy = class {
557
+ reportThreshold;
558
+ throwThreshold;
559
+ constructor({
560
+ reportThreshold = 1 /* Info */,
561
+ throwThreshold = 4 /* Fatal */
562
+ } = {}) {
563
+ this.reportThreshold = reportThreshold;
564
+ this.throwThreshold = throwThreshold;
565
+ }
566
+ resolve(diagnostic) {
567
+ if (diagnostic.severity >= this.throwThreshold) return 2 /* Throw */;
568
+ if (diagnostic.severity >= this.reportThreshold) return 1 /* Report */;
569
+ return 0 /* Ignore */;
570
+ }
571
+ };
572
+
573
+ // ../../lib/diagnostics/src/diagnostics.ts
574
+ var Diagnostics = class {
575
+ reporter;
576
+ policy;
577
+ // Pre-computed at construction time: true if Warning-level diagnostics are not ignored.
578
+ active;
579
+ constructor(reporter, policy = new DefaultPolicy()) {
580
+ this.reporter = reporter;
581
+ this.policy = policy;
582
+ this.active = policy.resolve({ severity: 2 /* Warning */ }) !== 0 /* Ignore */;
583
+ }
584
+ report(diagnostic) {
585
+ const enforcement = this.policy.resolve(diagnostic);
586
+ if (enforcement === 0 /* Ignore */) return diagnostic;
587
+ if (enforcement === 2 /* Throw */) throw new PraxisError(diagnostic);
588
+ this.reporter.report(diagnostic);
589
+ return diagnostic;
590
+ }
591
+ warn(input) {
592
+ return this.report({ ...input, severity: 2 /* Warning */ });
593
+ }
594
+ error(input) {
595
+ return this.report({ ...input, severity: 3 /* Error */ });
596
+ }
597
+ info(input) {
598
+ return this.report({ ...input, severity: 1 /* Info */ });
599
+ }
600
+ };
601
+
602
+ // ../../lib/diagnostics/src/formatter.ts
603
+ function formatDiagnostic(diagnostic) {
604
+ const level = Severity[diagnostic.severity];
605
+ const category = DiagnosticCategory[diagnostic.category];
606
+ const prefix = category !== void 0 ? `[${category}] ` : "";
607
+ return `${level} ${diagnostic.code}: ${prefix}${diagnostic.message}`;
682
608
  }
609
+
610
+ // ../../lib/diagnostics/src/console-reporter.ts
611
+ var ConsoleReporter = class {
612
+ report(diagnostic) {
613
+ const message = formatDiagnostic(diagnostic);
614
+ switch (diagnostic.severity) {
615
+ case 0 /* Debug */:
616
+ console.debug(message);
617
+ break;
618
+ case 1 /* Info */:
619
+ console.info(message);
620
+ break;
621
+ case 2 /* Warning */:
622
+ console.warn(message);
623
+ break;
624
+ case 3 /* Error */:
625
+ case 4 /* Fatal */:
626
+ console.error(message);
627
+ break;
628
+ }
629
+ }
630
+ };
631
+
632
+ // ../../lib/diagnostics/src/null-reporter.ts
633
+ var nullReporter = {
634
+ report() {
635
+ }
636
+ };
637
+
638
+ // ../../lib/diagnostics/src/presets.ts
639
+ var ignoreAllPolicy = {
640
+ resolve(_) {
641
+ return 0 /* Ignore */;
642
+ }
643
+ };
644
+ var warnOnlyReporter = {
645
+ report(diagnostic) {
646
+ console.warn(formatDiagnostic(diagnostic));
647
+ }
648
+ };
649
+ var silentDiagnostics = new Diagnostics(nullReporter, ignoreAllPolicy);
650
+ var warnDiagnostics = new Diagnostics(
651
+ warnOnlyReporter,
652
+ new DefaultPolicy({ reportThreshold: 2 /* Warning */, throwThreshold: 4 /* Fatal */ })
653
+ );
654
+ var throwDiagnostics = new Diagnostics(
655
+ new ConsoleReporter(),
656
+ new DefaultPolicy({ reportThreshold: 2 /* Warning */, throwThreshold: 3 /* Error */ })
657
+ );
658
+
659
+ // ../../lib/contract/src/diagnostics/aria.ts
660
+ var AriaDiagnostics = {
661
+ /** Generic bridge for violations produced by external AriaRule functions. */
662
+ fromViolation(v) {
663
+ return {
664
+ code: "ARIA2002" /* AriaViolation */,
665
+ category: 2 /* ARIA */,
666
+ message: v.message
667
+ };
668
+ },
669
+ attributeInvalid(key, role) {
670
+ return {
671
+ code: "ARIA2003" /* AriaAttributeInvalid */,
672
+ category: 2 /* ARIA */,
673
+ message: `"${key}" is not valid on role="${role}". It will be removed.`,
674
+ rationale: "Invalid ARIA attributes are ignored by assistive technology and may trigger accessibility-tree warnings in browser devtools.",
675
+ suggestions: [
676
+ {
677
+ title: "Remove the attribute",
678
+ description: `"${key}" is not in the allowed attribute set for role="${role}".`
679
+ }
680
+ ]
681
+ };
682
+ },
683
+ missingLiveRegion(role, impliedLive) {
684
+ return {
685
+ code: "ARIA2004" /* AriaMissingLiveRegion */,
686
+ category: 2 /* ARIA */,
687
+ message: `role="${role}" implies aria-live="${impliedLive}" but it is missing. It has been injected.`,
688
+ rationale: "Live-region roles announce dynamic content changes to screen readers. Without aria-live the politeness level is unspecified and announcements may be silent.",
689
+ suggestions: [
690
+ {
691
+ title: `Add aria-live="${impliedLive}"`,
692
+ description: `role="${role}" conventionally implies aria-live="${impliedLive}".`,
693
+ fix: `aria-live="${impliedLive}"`
694
+ }
695
+ ]
696
+ };
697
+ },
698
+ missingAtomic(role) {
699
+ return {
700
+ code: "ARIA2005" /* AriaMissingAtomic */,
701
+ category: 2 /* ARIA */,
702
+ 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.`,
703
+ rationale: "aria-atomic controls whether assistive technology announces the entire live region or only the changed nodes. Omitting it leaves the behaviour browser-defined."
704
+ };
705
+ },
706
+ relevantInvalidTokens(invalid) {
707
+ const quoted = invalid.map((t) => `"${t}"`).join(", ");
708
+ return {
709
+ code: "ARIA2006" /* AriaRelevantInvalidToken */,
710
+ category: 2 /* ARIA */,
711
+ message: `aria-relevant contains invalid token(s): ${quoted}. Valid tokens are: additions, removals, text, all.`,
712
+ rationale: "aria-relevant accepts a space-separated list of change types. Unrecognised tokens are silently ignored by assistive technology, making the attribute ineffective.",
713
+ suggestions: [
714
+ {
715
+ title: "Use only valid tokens",
716
+ description: "Valid values are: additions, removals, text, all (or a space-separated combination)."
717
+ }
718
+ ]
719
+ };
720
+ },
721
+ relevantSuperseded() {
722
+ return {
723
+ code: "ARIA2007" /* AriaRelevantSuperseded */,
724
+ category: 2 /* ARIA */,
725
+ message: 'aria-relevant includes "all" alongside other tokens. "all" supersedes additions, removals, and text \u2014 use aria-relevant="all" alone.',
726
+ rationale: '"all" is equivalent to "additions removals text". Combining it with other tokens is redundant and may confuse readers of the markup.',
727
+ suggestions: [
728
+ {
729
+ title: 'Use aria-relevant="all"',
730
+ fix: 'aria-relevant="all"'
731
+ }
732
+ ]
733
+ };
734
+ },
735
+ missingAccessibleName(tag) {
736
+ return {
737
+ code: "ARIA2009" /* AriaMissingAccessibleName */,
738
+ category: 2 /* ARIA */,
739
+ message: `<${tag}> has no accessible name. Add aria-label or aria-labelledby.`,
740
+ 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.",
741
+ suggestions: [
742
+ {
743
+ title: "Add aria-label",
744
+ description: `Add aria-label="\u2026" directly to the <${tag}> element.`
745
+ },
746
+ {
747
+ title: "Add aria-labelledby",
748
+ description: "Point aria-labelledby at the id of an existing heading or label element."
749
+ }
750
+ ]
751
+ };
752
+ },
753
+ attributeOnPresentational(attr, tag) {
754
+ return {
755
+ code: "ARIA2010" /* AriaAttributeOnPresentational */,
756
+ category: 2 /* ARIA */,
757
+ message: `"${attr}" is not allowed on a presentational <${tag}>. Presentational elements are invisible to assistive technology.`,
758
+ 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.',
759
+ suggestions: [
760
+ {
761
+ title: "Remove the attribute",
762
+ description: `"${attr}" has no effect when the element has role="none" or role="presentation".`
763
+ }
764
+ ]
765
+ };
766
+ },
767
+ ariaHiddenOnFocusable(tag) {
768
+ return {
769
+ code: "ARIA2011" /* AriaHiddenOnFocusable */,
770
+ category: 2 /* ARIA */,
771
+ 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.`,
772
+ rationale: 'aria-hidden removes an element from the accessibility tree while leaving it keyboard-reachable. This creates a "ghost" \u2014 a focusable element assistive technology cannot describe.',
773
+ suggestions: [
774
+ {
775
+ title: "Remove aria-hidden",
776
+ description: "If the element should be hidden from all users, use the HTML hidden attribute or CSS display:none instead."
777
+ },
778
+ {
779
+ title: "Make the element non-focusable",
780
+ description: 'If the element is intentionally decorative, add tabindex="-1" and disable it so it is not reachable by keyboard.'
781
+ }
782
+ ]
783
+ };
784
+ },
785
+ invalidAttributeValue(attr, value, expected) {
786
+ const got = value === null ? "null" : value === void 0 ? "undefined" : typeof value === "string" ? `"${value}"` : String(value);
787
+ return {
788
+ code: "ARIA2013" /* AriaInvalidAttributeValue */,
789
+ category: 2 /* ARIA */,
790
+ message: `"${attr}" has an invalid value (${got}). Expected: ${expected}.`,
791
+ rationale: "ARIA attributes with invalid values are silently ignored by assistive technology, making the markup semantically inert.",
792
+ suggestions: [
793
+ {
794
+ title: `Use a valid value for ${attr}`,
795
+ description: `Valid values are: ${expected}.`
796
+ }
797
+ ]
798
+ };
799
+ },
800
+ redundantAriaLevel(tag, level) {
801
+ return {
802
+ code: "ARIA2014" /* AriaRedundantLevelAttribute */,
803
+ category: 2 /* ARIA */,
804
+ message: `aria-level="${level}" is redundant on <${tag}>: the element already has an implicit heading level of ${level}. Remove the attribute.`,
805
+ 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>).',
806
+ suggestions: [
807
+ {
808
+ title: "Remove aria-level",
809
+ description: `<${tag}> already implies aria-level="${level}".`
810
+ }
811
+ ]
812
+ };
813
+ },
814
+ requiredProperty(attr, role) {
815
+ return {
816
+ code: "ARIA2012" /* AriaRequiredProperty */,
817
+ category: 2 /* ARIA */,
818
+ message: `"${attr}" is required for role="${role}" but is missing.`,
819
+ 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.`,
820
+ suggestions: [
821
+ {
822
+ title: `Add ${attr}`,
823
+ description: `role="${role}" requires "${attr}" to be present.`
824
+ }
825
+ ]
826
+ };
827
+ },
828
+ invalidRole(role, tag) {
829
+ return {
830
+ code: "ARIA2008" /* AriaInvalidRole */,
831
+ category: 2 /* ARIA */,
832
+ message: `Invalid role "${role ?? ""}" on <${tag}>.`,
833
+ rationale: "An unrecognised or misapplied ARIA role is ignored by assistive technology and may degrade the accessibility of the element."
834
+ };
835
+ }
836
+ };
837
+
838
+ // ../../lib/contract/src/diagnostics/contract.ts
839
+ var ContractDiagnostics = {
840
+ unexpectedChild(typeName, index, context) {
841
+ return {
842
+ code: "COMP1004" /* UnexpectedChild */,
843
+ category: 0 /* Contract */,
844
+ component: context,
845
+ message: `${context}: unexpected child "${typeName}" at index ${index}.`
846
+ };
847
+ },
848
+ ambiguousChild(typeName, index, ruleNames, context) {
849
+ const quoted = ruleNames.map((n) => `"${n}"`).join(" and ");
850
+ return {
851
+ code: "COMP1005" /* AmbiguousChild */,
852
+ category: 0 /* Contract */,
853
+ component: context,
854
+ message: `${context}: child "${typeName}" at index ${index} matches multiple child rules: ${quoted}.`
855
+ };
856
+ },
857
+ cardinalityMin(ruleName, min, context) {
858
+ return {
859
+ code: "COMP1006" /* CardinalityMin */,
860
+ category: 0 /* Contract */,
861
+ component: context,
862
+ message: `${context}: "${ruleName}" requires at least ${min}.`
863
+ };
864
+ },
865
+ cardinalityMax(ruleName, max, context) {
866
+ return {
867
+ code: "COMP1007" /* CardinalityMax */,
868
+ category: 0 /* Contract */,
869
+ component: context,
870
+ message: `${context}: "${ruleName}" allows at most ${max}.`
871
+ };
872
+ },
873
+ positionViolation(ruleName, position, index, context) {
874
+ return {
875
+ code: "COMP1008" /* PositionViolation */,
876
+ category: 0 /* Contract */,
877
+ component: context,
878
+ message: `${context}: "${ruleName}" must be ${position}, got index ${index}`
879
+ };
880
+ },
881
+ unknownVariantDim(component, label2, dim) {
882
+ return {
883
+ code: "COMP1010" /* ContractUnknownVariantDim */,
884
+ category: 0 /* Contract */,
885
+ message: `${component}: ${label2} references unknown variant "${dim}".`
886
+ };
887
+ },
888
+ unknownVariantValue(component, label2, dim, value, valid) {
889
+ return {
890
+ code: "COMP1011" /* ContractUnknownVariantValue */,
891
+ category: 0 /* Contract */,
892
+ message: `${component}: ${label2} sets "${dim}" to unknown value "${value}" (valid: ${valid.join(", ")}).`
893
+ };
894
+ },
895
+ unknownRecipeKey(component, key) {
896
+ return {
897
+ code: "COMP1012" /* ContractUnknownRecipeKey */,
898
+ category: 0 /* Contract */,
899
+ message: `${component} Unknown recipeKey "${key}" \u2014 no preset with that name exists.`
900
+ };
901
+ },
902
+ invalidVariantValue(component, key, value) {
903
+ return {
904
+ code: "COMP1013" /* ContractInvalidVariantValue */,
905
+ category: 0 /* Contract */,
906
+ message: `${component} Variant "${key}=${value}" is not a defined value for the "${key}" dimension.`
907
+ };
908
+ },
909
+ allowedAsViolation(tag, allowedAs, component) {
910
+ const allowed = allowedAs.map((t) => `"${String(t)}"`).join(", ");
911
+ return {
912
+ code: "COMP1009" /* AllowedAsViolation */,
913
+ category: 0 /* Contract */,
914
+ component,
915
+ message: `<${component}>: "as" prop received "${tag}" but only [${allowed}] are allowed.`
916
+ };
917
+ }
918
+ };
919
+
920
+ // ../../lib/contract/src/diagnostics/html.ts
921
+ var HtmlDiagnostics = {
922
+ emptyRole(tag) {
923
+ return {
924
+ code: "HTML3002" /* HtmlEmptyRole */,
925
+ category: 1 /* HTML */,
926
+ message: `<${tag}> has an explicit empty role="". Omit the attribute instead.`
927
+ };
928
+ },
929
+ implicitRoleRedundant(tag, implicitRole) {
930
+ return {
931
+ code: "HTML3003" /* HtmlImplicitRoleRedundant */,
932
+ category: 1 /* HTML */,
933
+ message: `<${tag}> already has implicit role="${implicitRole}". Avoid redundant role assignment.`
934
+ };
935
+ },
936
+ implicitRoleOverride(tag, implicitRole, role) {
937
+ return {
938
+ code: "HTML3004" /* HtmlImplicitRoleOverride */,
939
+ category: 1 /* HTML */,
940
+ message: `<${tag}> should not override its implicit role="${implicitRole}" with role="${role}".`
941
+ };
942
+ },
943
+ standaloneRegionOverride(tag, implicitRole) {
944
+ return {
945
+ code: "HTML3005" /* HtmlStandaloneRegionOverride */,
946
+ category: 1 /* HTML */,
947
+ message: `<${tag}> is a self-contained element with implicit role="${implicitRole}". Assigning role="region" has been removed.`
948
+ };
949
+ },
950
+ landmarkRoleOverride(tag, implicitRole, role) {
951
+ return {
952
+ code: "HTML3006" /* HtmlLandmarkRoleOverride */,
953
+ category: 1 /* HTML */,
954
+ message: `<${tag}> has a fixed landmark role="${implicitRole}". role="${role}" overrides it and confuses assistive technology. The override has been removed.`
955
+ };
956
+ },
957
+ invalidChild(child, parent, allowed) {
958
+ return {
959
+ code: "HTML3007" /* HtmlInvalidChild */,
960
+ category: 1 /* HTML */,
961
+ message: `<${child}> is not a valid direct child of <${parent}>. Allowed: ${allowed}.`
962
+ };
963
+ }
964
+ };
965
+
966
+ // ../../lib/contract/src/diagnostics/slot.ts
967
+ var SlotDiagnostics = {
968
+ exclusive(name) {
969
+ return {
970
+ code: "SLOT1001" /* SlotExclusive */,
971
+ category: 0 /* Contract */,
972
+ component: name,
973
+ message: `${name}: "as" and "asChild" are mutually exclusive`
974
+ };
975
+ },
976
+ singleChildRequired(name, elementTerm) {
977
+ return {
978
+ code: "SLOT1002" /* SlotSingleChild */,
979
+ category: 0 /* Contract */,
980
+ component: name,
981
+ message: `${name}: asChild requires a ${elementTerm} child`
982
+ };
983
+ },
984
+ singleChildExceeded(name, elementTerm, count) {
985
+ return {
986
+ code: "SLOT1002" /* SlotSingleChild */,
987
+ category: 0 /* Contract */,
988
+ component: name,
989
+ message: `${name}: asChild requires exactly one ${elementTerm} child, got ${count}`
990
+ };
991
+ },
992
+ discardedChildren(name, elementTerm, count) {
993
+ const suffix = count === 1 ? "" : "ren";
994
+ return {
995
+ code: "SLOT1003" /* SlotDiscardedChildren */,
996
+ category: 0 /* Contract */,
997
+ component: name,
998
+ message: `${name}: asChild discarded ${count} non-element child${suffix} \u2014 only ${elementTerm}s are valid asChild children.`
999
+ };
1000
+ },
1001
+ renderFnRequired(name, received) {
1002
+ return {
1003
+ code: "SLOT1004" /* SlotRenderFn */,
1004
+ category: 0 /* Contract */,
1005
+ component: name,
1006
+ message: `${name}: asChild requires a render function as children, got ${received}`
1007
+ };
1008
+ }
1009
+ };
1010
+
1011
+ // ../../lib/contract/src/aria/polymorphic-validator.ts
683
1012
  var VALID = [{ valid: true }];
684
1013
  function isIntrinsicTag(tag) {
685
1014
  return isString(tag);
@@ -688,26 +1017,28 @@ function omitProp(obj, key) {
688
1017
  const { [key]: _, ...rest } = obj;
689
1018
  return rest;
690
1019
  }
691
- var AriaPolicyEngine = class _AriaPolicyEngine extends StrictBase {
1020
+ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
692
1021
  #extraRules;
693
1022
  #planCache = /* @__PURE__ */ new Map();
694
1023
  static #MAX_CACHE = 100;
695
1024
  // Memoized AriaFix objects keyed by attribute name — the ARIA attribute set is
696
1025
  // finite so this Map is bounded and avoids recreating closures on every cache miss.
697
1026
  static #removeAttributeFixCache = /* @__PURE__ */ new Map();
698
- constructor(strict = "warn", options) {
699
- super(strict);
1027
+ constructor(diagnostics, options) {
1028
+ super(diagnostics);
700
1029
  this.#extraRules = options?.rules ?? [];
701
1030
  }
702
1031
  static #normalizeEmptyRole(tag, props) {
703
1032
  if (props.role !== "") return { normalized: false };
1033
+ const d = HtmlDiagnostics.emptyRole(tag);
704
1034
  return {
705
1035
  normalized: true,
706
1036
  result: {
707
1037
  props: omitProp(props, "role"),
708
1038
  violations: [
709
1039
  {
710
- message: `<${tag}> has an explicit empty role="". Omit the attribute instead.`,
1040
+ message: d.message,
1041
+ diagnostic: d,
711
1042
  tag,
712
1043
  role: "",
713
1044
  attribute: void 0,
@@ -720,10 +1051,9 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends StrictBase {
720
1051
  }
721
1052
  static #deriveContext(tag, props) {
722
1053
  if (!isIntrinsicTag(tag)) return { proceed: false, result: { props, violations: [] } };
723
- const implicitRole = getImplicitRole(tag);
724
- const hasExplicitLiveRole = !implicitRole && _AriaPolicyEngine.#LIVE_REGION_ROLES.has(props.role ?? "");
725
- if (!implicitRole && !hasExplicitLiveRole)
726
- return { proceed: false, result: { props, violations: [] } };
1054
+ const implicitRole = getImplicitRole(tag, props);
1055
+ const hasRole = implicitRole != null || isString(props.role) && props.role.length > 0;
1056
+ if (!hasRole) return { proceed: false, result: { props, violations: [] } };
727
1057
  const normalized = _AriaPolicyEngine.#normalizeEmptyRole(tag, props);
728
1058
  if (normalized.normalized) return { proceed: false, result: normalized.result };
729
1059
  const effectiveRole = props.role ?? implicitRole;
@@ -738,25 +1068,36 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends StrictBase {
738
1068
  static #runRules(rules, context) {
739
1069
  const violations = [];
740
1070
  const fixes = [];
741
- for (const rule of rules) {
742
- for (const result of rule(context)) {
743
- if (!result.valid) {
744
- violations.push({
745
- message: result.message ?? `Invalid role "${context.props.role}" on <${context.tag}>`,
746
- tag: context.tag,
747
- role: context.props.role,
748
- attribute: result.attribute,
749
- severity: result.severity,
750
- phase: "evaluate"
751
- });
752
- if (result.fixable) fixes.push(result.fix);
753
- }
754
- }
755
- }
1071
+ iterate.forEach(rules, (rule) => {
1072
+ iterate.forEach(rule(context), (result) => {
1073
+ if (result.valid) return;
1074
+ const {
1075
+ tag,
1076
+ props: { role }
1077
+ } = context;
1078
+ const { message, attribute, severity } = result;
1079
+ const resolvedMessage = message ?? result.diagnostic?.message;
1080
+ const fallbackDiag = resolvedMessage == null ? AriaDiagnostics.invalidRole(role, tag) : void 0;
1081
+ violations.push({
1082
+ message: resolvedMessage ?? fallbackDiag.message,
1083
+ tag,
1084
+ role,
1085
+ attribute,
1086
+ severity,
1087
+ phase: "evaluate",
1088
+ ...result.diagnostic != null && { diagnostic: result.diagnostic },
1089
+ ...fallbackDiag != null && { diagnostic: fallbackDiag }
1090
+ });
1091
+ if (result.fixable) fixes.push(result.fix);
1092
+ });
1093
+ });
756
1094
  return { violations, fixes };
757
1095
  }
758
1096
  static #getRules(context) {
759
- return _AriaPolicyEngine.#hasRole(context.props) ? _AriaPolicyEngine.#pipeline : [_AriaPolicyEngine.#checkInvalidAriaAttributes];
1097
+ if (_AriaPolicyEngine.#hasRole(context.props) || context.effectiveRole != null && _AriaPolicyEngine.#LIVE_REGION_ROLES.has(context.effectiveRole)) {
1098
+ return _AriaPolicyEngine.#pipeline;
1099
+ }
1100
+ return _AriaPolicyEngine.#implicitOnlyRules;
760
1101
  }
761
1102
  static evaluate(tag, props) {
762
1103
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
@@ -781,10 +1122,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends StrictBase {
781
1122
  return { props: next, violations };
782
1123
  }
783
1124
  report(violations) {
784
- for (const v of violations) {
785
- if (v.severity === "error") this.violate(v.message);
786
- else this.warn(v.message);
787
- }
1125
+ iterate.forEach(violations, (v) => {
1126
+ const d = v.diagnostic ?? AriaDiagnostics.fromViolation(v);
1127
+ if (v.severity === "error") this.violate(d);
1128
+ else this.warn(d);
1129
+ });
788
1130
  }
789
1131
  // Cache key covers only the aria-relevant subset of props (tag + role + aria-* attrs).
790
1132
  // Non-aria props (className, onClick, etc.) do not affect ARIA decisions and are excluded
@@ -796,27 +1138,26 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends StrictBase {
796
1138
  if (!isIntrinsicTag(tag)) return null;
797
1139
  const parts = [tag];
798
1140
  if (typeof props.role === "string") parts.push(`role:${props.role}`);
1141
+ if (tag === "input" && typeof props.type === "string") parts.push(`type:${props.type}`);
1142
+ if (tag === "img") parts.push(`alt:${props.alt === "" ? "empty" : "present"}`);
799
1143
  const ariaEntries = [];
800
- for (const k in props) {
801
- if (!Object.hasOwn(props, k) || !k.startsWith("aria-")) continue;
802
- const v = props[k];
803
- if (!isString(v) && !isNumber(v) && typeof v !== "boolean") continue;
1144
+ iterate.forEachEntry(props, (k, v) => {
1145
+ if (!k.startsWith("aria-")) return;
1146
+ if (!isString(v) && !isNumber(v) && typeof v !== "boolean") return;
804
1147
  ariaEntries.push(`${k}:${String(v)}`);
805
- }
1148
+ });
806
1149
  if (ariaEntries.length > 0) parts.push(...ariaEntries.sort());
807
1150
  return parts.join("|");
808
1151
  }
809
1152
  static #computePlan(inputProps, resultProps) {
810
1153
  const removals = /* @__PURE__ */ new Set();
811
1154
  const updates = {};
812
- for (const key in inputProps) {
813
- if (Object.hasOwn(inputProps, key) && !(key in resultProps)) removals.add(key);
814
- }
815
- for (const key in resultProps) {
816
- if (!Object.hasOwn(resultProps, key)) continue;
817
- const resultVal = resultProps[key];
1155
+ iterate.forEachKey(inputProps, (key) => {
1156
+ if (!(key in resultProps)) removals.add(key);
1157
+ });
1158
+ iterate.forEachEntry(resultProps, (key, resultVal) => {
818
1159
  if (inputProps[key] !== resultVal) updates[key] = resultVal;
819
- }
1160
+ });
820
1161
  return { removals, updates };
821
1162
  }
822
1163
  static #applyPlan(props, removals, updates) {
@@ -824,15 +1165,15 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends StrictBase {
824
1165
  const hasUpdates = Object.keys(updates).length > 0;
825
1166
  if (!hasRemovals && !hasUpdates) return props;
826
1167
  const next = {};
827
- for (const k in props) {
828
- if (Object.hasOwn(props, k) && !removals.has(k)) next[k] = props[k];
829
- }
1168
+ iterate.forEachEntry(props, (k, v) => {
1169
+ if (!removals.has(k)) next[k] = v;
1170
+ });
830
1171
  Object.assign(next, updates);
831
1172
  return next;
832
1173
  }
833
1174
  validate(tag, props) {
834
1175
  const key = _AriaPolicyEngine.#createPlanKey(tag, props);
835
- if (!isNull2(key)) {
1176
+ if (!isNull(key)) {
836
1177
  const cached = this.#planCache.get(key);
837
1178
  if (cached !== void 0) {
838
1179
  this.#planCache.delete(key);
@@ -846,7 +1187,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends StrictBase {
846
1187
  }
847
1188
  const result = this.#extraRules.length ? _AriaPolicyEngine.#evaluateWithRules(tag, props, this.#extraRules) : _AriaPolicyEngine.evaluate(tag, props);
848
1189
  if (result.violations.length > 0) this.report(result.violations);
849
- if (!isNull2(key)) {
1190
+ if (!isNull(key)) {
850
1191
  const { removals, updates } = _AriaPolicyEngine.#computePlan(
851
1192
  props,
852
1193
  result.props
@@ -867,12 +1208,12 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends StrictBase {
867
1208
  if (fixes.length === 0) return props;
868
1209
  const sorted = [...fixes].sort((a, b) => (a.priority ?? Infinity) - (b.priority ?? Infinity));
869
1210
  let next = props;
870
- for (const { apply } of sorted) {
1211
+ iterate.forEach(sorted, ({ apply }) => {
871
1212
  const effectiveRole = next.role ?? implicitRole;
872
1213
  const fixContext = { tag, implicitRole, effectiveRole, props: next };
873
1214
  const fixResult = apply(fixContext);
874
1215
  if (fixResult.applied) next = fixResult.next;
875
- }
1216
+ });
876
1217
  return next;
877
1218
  }
878
1219
  static #removeRole = {
@@ -900,10 +1241,26 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends StrictBase {
900
1241
  _AriaPolicyEngine.#checkInvalidRoleOverride,
901
1242
  _AriaPolicyEngine.#checkRedundantRole,
902
1243
  _AriaPolicyEngine.#checkStandaloneRegion,
1244
+ _AriaPolicyEngine.#checkAriaAttributeValues,
903
1245
  _AriaPolicyEngine.#checkInvalidAriaAttributes,
1246
+ _AriaPolicyEngine.#checkRequiredAriaProperties,
1247
+ _AriaPolicyEngine.#checkNameRequiredRoles,
1248
+ _AriaPolicyEngine.#checkRedundantAriaLevel,
904
1249
  _AriaPolicyEngine.#checkMissingLiveRegion,
905
1250
  _AriaPolicyEngine.#checkMissingAtomic,
906
- _AriaPolicyEngine.#checkInvalidAriaRelevant
1251
+ _AriaPolicyEngine.#checkInvalidAriaRelevant,
1252
+ _AriaPolicyEngine.#checkAriaHiddenOnFocusable,
1253
+ _AriaPolicyEngine.#checkPresentationalAriaAttributes
1254
+ ];
1255
+ // Rules for elements with an implicit role but no explicit role (not a live region).
1256
+ static #implicitOnlyRules = [
1257
+ _AriaPolicyEngine.#checkAriaAttributeValues,
1258
+ _AriaPolicyEngine.#checkInvalidAriaAttributes,
1259
+ _AriaPolicyEngine.#checkRequiredAriaProperties,
1260
+ _AriaPolicyEngine.#checkNameRequiredRoles,
1261
+ _AriaPolicyEngine.#checkRedundantAriaLevel,
1262
+ _AriaPolicyEngine.#checkAriaHiddenOnFocusable,
1263
+ _AriaPolicyEngine.#checkPresentationalAriaAttributes
907
1264
  ];
908
1265
  static #checkInvalidRoleOverride({
909
1266
  tag,
@@ -919,7 +1276,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends StrictBase {
919
1276
  fixable: true,
920
1277
  severity: "error",
921
1278
  fix: _AriaPolicyEngine.#removeRole,
922
- message: `<${tag}> should not override its implicit role="${implicitRole}" with role="${role}".`
1279
+ diagnostic: HtmlDiagnostics.implicitRoleOverride(tag, implicitRole, role)
923
1280
  }
924
1281
  ];
925
1282
  }
@@ -933,45 +1290,302 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends StrictBase {
933
1290
  valid: false,
934
1291
  fixable: true,
935
1292
  severity: "warning",
936
- fix: _AriaPolicyEngine.#removeRole,
937
- message: `<${tag}> already has implicit role="${implicitRole}". Avoid redundant role assignment.`
1293
+ fix: _AriaPolicyEngine.#removeRole,
1294
+ diagnostic: HtmlDiagnostics.implicitRoleRedundant(tag, implicitRole)
1295
+ }
1296
+ ];
1297
+ }
1298
+ static #checkStandaloneRegion({ tag, props, implicitRole }) {
1299
+ const role = props.role;
1300
+ if (role !== "region") return VALID;
1301
+ if (!isStandaloneTag(tag)) return VALID;
1302
+ return [
1303
+ {
1304
+ valid: false,
1305
+ fixable: true,
1306
+ severity: "error",
1307
+ fix: _AriaPolicyEngine.#removeRole,
1308
+ diagnostic: HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag)
1309
+ }
1310
+ ];
1311
+ }
1312
+ static #checkInvalidAriaAttributes({
1313
+ tag,
1314
+ props,
1315
+ effectiveRole
1316
+ }) {
1317
+ if (effectiveRole === "none" || effectiveRole === "presentation") return VALID;
1318
+ const results = [];
1319
+ iterate.forEachEntry(props, (key) => {
1320
+ if (!key.startsWith("aria-")) return;
1321
+ if (isGlobalAriaAttribute(key)) return;
1322
+ if (isAriaAttributeValidForRole(key, effectiveRole)) return;
1323
+ results.push({
1324
+ valid: false,
1325
+ severity: "warning",
1326
+ fixable: true,
1327
+ attribute: key,
1328
+ diagnostic: AriaDiagnostics.attributeInvalid(key, effectiveRole ?? tag),
1329
+ fix: _AriaPolicyEngine.#makeRemoveAttributeFix(key)
1330
+ });
1331
+ });
1332
+ return results;
1333
+ }
1334
+ // ─── ARIA attribute value validation ──────────────────────────────────────
1335
+ // Accepted value shapes for typed ARIA attributes.
1336
+ // Attributes not in this map are unconstrained (arbitrary string values permitted).
1337
+ static #ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
1338
+ // Boolean (true | false)
1339
+ ["aria-atomic", { kind: "boolean" }],
1340
+ ["aria-busy", { kind: "boolean" }],
1341
+ ["aria-disabled", { kind: "boolean" }],
1342
+ ["aria-expanded", { kind: "boolean" }],
1343
+ ["aria-hidden", { kind: "boolean" }],
1344
+ ["aria-modal", { kind: "boolean" }],
1345
+ ["aria-multiline", { kind: "boolean" }],
1346
+ ["aria-multiselectable", { kind: "boolean" }],
1347
+ ["aria-readonly", { kind: "boolean" }],
1348
+ ["aria-required", { kind: "boolean" }],
1349
+ ["aria-selected", { kind: "boolean" }],
1350
+ // Tristate (true | false | mixed)
1351
+ ["aria-checked", { kind: "tristate" }],
1352
+ ["aria-pressed", { kind: "tristate" }],
1353
+ // Numeric (any finite number)
1354
+ ["aria-valuenow", { kind: "number" }],
1355
+ ["aria-valuemin", { kind: "number" }],
1356
+ ["aria-valuemax", { kind: "number" }],
1357
+ // Integer with optional range
1358
+ ["aria-level", { kind: "integer", min: 1, max: 6 }],
1359
+ ["aria-posinset", { kind: "integer", min: 1 }],
1360
+ ["aria-setsize", { kind: "integer", min: -1 }],
1361
+ ["aria-rowcount", { kind: "integer", min: -1 }],
1362
+ ["aria-colcount", { kind: "integer", min: -1 }],
1363
+ ["aria-rowindex", { kind: "integer", min: 1 }],
1364
+ ["aria-colindex", { kind: "integer", min: 1 }],
1365
+ ["aria-rowspan", { kind: "integer", min: 0 }],
1366
+ ["aria-colspan", { kind: "integer", min: 0 }],
1367
+ // Enum (specific allowed tokens)
1368
+ ["aria-autocomplete", { kind: "enum", values: /* @__PURE__ */ new Set(["inline", "list", "both", "none"]) }],
1369
+ [
1370
+ "aria-current",
1371
+ {
1372
+ kind: "enum",
1373
+ values: /* @__PURE__ */ new Set(["page", "step", "location", "date", "time", "true", "false"])
1374
+ }
1375
+ ],
1376
+ [
1377
+ "aria-haspopup",
1378
+ {
1379
+ kind: "enum",
1380
+ values: /* @__PURE__ */ new Set(["false", "true", "menu", "listbox", "tree", "grid", "dialog"])
1381
+ }
1382
+ ],
1383
+ ["aria-invalid", { kind: "enum", values: /* @__PURE__ */ new Set(["grammar", "false", "spelling", "true"]) }],
1384
+ ["aria-live", { kind: "enum", values: /* @__PURE__ */ new Set(["assertive", "off", "polite"]) }],
1385
+ [
1386
+ "aria-orientation",
1387
+ { kind: "enum", values: /* @__PURE__ */ new Set(["horizontal", "vertical", "undefined"]) }
1388
+ ],
1389
+ ["aria-sort", { kind: "enum", values: /* @__PURE__ */ new Set(["ascending", "descending", "none", "other"]) }]
1390
+ ]);
1391
+ static #isValidAriaValue(value, type) {
1392
+ switch (type.kind) {
1393
+ case "boolean":
1394
+ return value === "true" || value === "false" || value === true || value === false;
1395
+ case "tristate":
1396
+ return value === "true" || value === "false" || value === "mixed" || value === true || value === false;
1397
+ case "number": {
1398
+ if (typeof value === "number") return Number.isFinite(value);
1399
+ if (typeof value !== "string") return false;
1400
+ const n = parseFloat(value);
1401
+ return Number.isFinite(n);
1402
+ }
1403
+ case "integer": {
1404
+ const n = typeof value === "number" ? value : typeof value === "string" ? parseInt(value, 10) : NaN;
1405
+ if (!Number.isFinite(n) || !Number.isInteger(n)) return false;
1406
+ if (type.min !== void 0 && n < type.min) return false;
1407
+ if (type.max !== void 0 && n > type.max) return false;
1408
+ return true;
1409
+ }
1410
+ case "enum":
1411
+ return typeof value === "string" && type.values.has(value);
1412
+ }
1413
+ }
1414
+ static #describeExpected(type) {
1415
+ switch (type.kind) {
1416
+ case "boolean":
1417
+ return '"true" or "false"';
1418
+ case "tristate":
1419
+ return '"true", "false", or "mixed"';
1420
+ case "number":
1421
+ return "a finite number";
1422
+ case "integer": {
1423
+ const parts = ["an integer"];
1424
+ if (type.min !== void 0 && type.max !== void 0)
1425
+ parts.push(`between ${type.min} and ${type.max}`);
1426
+ else if (type.min !== void 0) parts.push(`\u2265 ${type.min}`);
1427
+ else if (type.max !== void 0) parts.push(`\u2264 ${type.max}`);
1428
+ return parts.join(" ");
1429
+ }
1430
+ case "enum":
1431
+ return [...type.values].map((v) => `"${v}"`).join(", ");
1432
+ }
1433
+ }
1434
+ static #checkAriaAttributeValues({ props, effectiveRole }) {
1435
+ if (effectiveRole === "none" || effectiveRole === "presentation") return VALID;
1436
+ const results = [];
1437
+ iterate.forEachEntry(props, (key, value) => {
1438
+ if (!key.startsWith("aria-")) return;
1439
+ const type = _AriaPolicyEngine.#ARIA_VALUE_TYPES.get(key);
1440
+ if (type == null) return;
1441
+ if (_AriaPolicyEngine.#isValidAriaValue(value, type)) return;
1442
+ results.push({
1443
+ valid: false,
1444
+ fixable: true,
1445
+ severity: "warning",
1446
+ attribute: key,
1447
+ diagnostic: AriaDiagnostics.invalidAttributeValue(
1448
+ key,
1449
+ value,
1450
+ _AriaPolicyEngine.#describeExpected(type)
1451
+ ),
1452
+ fix: _AriaPolicyEngine.#makeRemoveAttributeFix(key)
1453
+ });
1454
+ });
1455
+ return results;
1456
+ }
1457
+ // ─── Heading implicit level ────────────────────────────────────────────────
1458
+ static #HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
1459
+ ["h1", 1],
1460
+ ["h2", 2],
1461
+ ["h3", 3],
1462
+ ["h4", 4],
1463
+ ["h5", 5],
1464
+ ["h6", 6]
1465
+ ]);
1466
+ static #checkRedundantAriaLevel({
1467
+ tag,
1468
+ props,
1469
+ effectiveRole
1470
+ }) {
1471
+ if (effectiveRole === "none" || effectiveRole === "presentation") return VALID;
1472
+ const implicitLevel = _AriaPolicyEngine.#HEADING_IMPLICIT_LEVELS.get(tag);
1473
+ if (implicitLevel == null) return VALID;
1474
+ const raw = props["aria-level"];
1475
+ if (raw == null) return VALID;
1476
+ const n = typeof raw === "number" ? raw : typeof raw === "string" ? parseInt(raw, 10) : NaN;
1477
+ if (!Number.isFinite(n) || n !== implicitLevel) return VALID;
1478
+ return [
1479
+ {
1480
+ valid: false,
1481
+ fixable: true,
1482
+ severity: "warning",
1483
+ attribute: "aria-level",
1484
+ diagnostic: AriaDiagnostics.redundantAriaLevel(tag, implicitLevel),
1485
+ fix: _AriaPolicyEngine.#makeRemoveAttributeFix("aria-level")
938
1486
  }
939
1487
  ];
940
1488
  }
941
- static #checkStandaloneRegion({ tag, props, implicitRole }) {
942
- const role = props.role;
943
- if (role !== "region") return VALID;
944
- if (!isStandaloneTag(tag)) return VALID;
1489
+ // ─── Name-required roles ───────────────────────────────────────────────────
1490
+ // Roles that always require an accessible name per WAI-ARIA APG.
1491
+ // Dialog and landmark names are enforced via contracts (ariaContract) rather than
1492
+ // the built-in pipeline so consumers can opt in; img is built in because role=img
1493
+ // on any element (including bare <img>) is definitionally useless without a name.
1494
+ static #NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
1495
+ static #checkNameRequiredRoles({
1496
+ tag,
1497
+ props,
1498
+ effectiveRole
1499
+ }) {
1500
+ if (!effectiveRole || !_AriaPolicyEngine.#NAME_REQUIRED_ROLES.has(effectiveRole)) return VALID;
1501
+ if ("aria-label" in props || "aria-labelledby" in props) return VALID;
1502
+ if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return VALID;
945
1503
  return [
946
1504
  {
947
1505
  valid: false,
948
- fixable: true,
1506
+ fixable: false,
1507
+ severity: "warning",
1508
+ diagnostic: AriaDiagnostics.missingAccessibleName(tag)
1509
+ }
1510
+ ];
1511
+ }
1512
+ // WAI-ARIA 1.2 required states and properties, keyed by role.
1513
+ // Source: https://www.w3.org/TR/wai-aria-1.2/#requiredState
1514
+ static #REQUIRED_PROPERTIES = /* @__PURE__ */ new Map([
1515
+ ["combobox", ["aria-expanded"]],
1516
+ ["option", ["aria-selected"]],
1517
+ ["slider", ["aria-valuenow"]],
1518
+ ["scrollbar", ["aria-controls", "aria-valuenow"]],
1519
+ ["spinbutton", ["aria-valuenow"]]
1520
+ ]);
1521
+ static #checkRequiredAriaProperties({
1522
+ props,
1523
+ effectiveRole
1524
+ }) {
1525
+ if (!effectiveRole) return VALID;
1526
+ const required = _AriaPolicyEngine.#REQUIRED_PROPERTIES.get(effectiveRole);
1527
+ if (required == null) return VALID;
1528
+ const results = [];
1529
+ iterate.forEach(required, (attr) => {
1530
+ if (attr in props) return;
1531
+ results.push({
1532
+ valid: false,
1533
+ fixable: false,
1534
+ severity: "warning",
1535
+ attribute: attr,
1536
+ diagnostic: AriaDiagnostics.requiredProperty(attr, effectiveRole)
1537
+ });
1538
+ });
1539
+ return results;
1540
+ }
1541
+ // Natively interactive HTML elements — always keyboard-reachable unless explicitly disabled.
1542
+ static #INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
1543
+ "a",
1544
+ "button",
1545
+ "input",
1546
+ "select",
1547
+ "textarea"
1548
+ ]);
1549
+ // WAI-ARIA 1.2 §6.6: aria-hidden="true" must not be placed on focusable elements.
1550
+ static #checkAriaHiddenOnFocusable({ tag, props }) {
1551
+ if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return VALID;
1552
+ const isInteractive = _AriaPolicyEngine.#INTERACTIVE_TAGS.has(tag);
1553
+ if (!isInteractive) {
1554
+ const tabindex = props.tabindex;
1555
+ const n = typeof tabindex === "number" ? tabindex : typeof tabindex === "string" ? parseInt(tabindex, 10) : NaN;
1556
+ if (!Number.isFinite(n) || n < 0) return VALID;
1557
+ }
1558
+ return [
1559
+ {
1560
+ valid: false,
1561
+ fixable: false,
949
1562
  severity: "error",
950
- fix: _AriaPolicyEngine.#removeRole,
951
- message: `<${tag}> is a self-contained element with implicit role="${implicitRole}". Assigning role="region" has been removed.`
1563
+ attribute: "aria-hidden",
1564
+ diagnostic: AriaDiagnostics.ariaHiddenOnFocusable(tag)
952
1565
  }
953
1566
  ];
954
1567
  }
955
- static #checkInvalidAriaAttributes({
1568
+ // Presentational elements (role=none/presentation, including <img alt="">) are removed
1569
+ // from the accessibility tree — ARIA attributes on them are meaningless and misleading.
1570
+ static #checkPresentationalAriaAttributes({
956
1571
  tag,
957
1572
  props,
958
1573
  effectiveRole
959
1574
  }) {
1575
+ if (effectiveRole !== "none" && effectiveRole !== "presentation") return VALID;
960
1576
  const results = [];
961
- for (const key in props) {
962
- if (!Object.hasOwn(props, key)) continue;
963
- if (!key.startsWith("aria-")) continue;
964
- if (isGlobalAriaAttribute(key)) continue;
965
- if (isAriaAttributeValidForRole(key, effectiveRole)) continue;
1577
+ iterate.forEachEntry(props, (key) => {
1578
+ if (!key.startsWith("aria-")) return;
1579
+ if (key === "aria-hidden") return;
966
1580
  results.push({
967
1581
  valid: false,
968
- severity: "warning",
969
1582
  fixable: true,
1583
+ severity: "warning",
970
1584
  attribute: key,
971
- message: `"${key}" is not valid on role="${effectiveRole ?? tag}". It will be removed.`,
1585
+ diagnostic: AriaDiagnostics.attributeOnPresentational(key, tag),
972
1586
  fix: _AriaPolicyEngine.#makeRemoveAttributeFix(key)
973
1587
  });
974
- }
1588
+ });
975
1589
  return results;
976
1590
  }
977
1591
  // WAI-ARIA live region roles and their implied aria-live politeness values.
@@ -1000,7 +1614,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends StrictBase {
1000
1614
  fixable: true,
1001
1615
  severity: "warning",
1002
1616
  fix: injectLive,
1003
- message: `role="${effectiveRole}" implies aria-live="${impliedLive}" but it is missing. It has been injected.`
1617
+ diagnostic: AriaDiagnostics.missingLiveRegion(effectiveRole, impliedLive)
1004
1618
  }
1005
1619
  ];
1006
1620
  }
@@ -1012,7 +1626,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends StrictBase {
1012
1626
  valid: false,
1013
1627
  fixable: false,
1014
1628
  severity: "warning",
1015
- message: `role="${effectiveRole}" 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.`
1629
+ diagnostic: AriaDiagnostics.missingAtomic(effectiveRole)
1016
1630
  }
1017
1631
  ];
1018
1632
  }
@@ -1041,7 +1655,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends StrictBase {
1041
1655
  fixable: true,
1042
1656
  severity: "warning",
1043
1657
  attribute: "aria-relevant",
1044
- message: `aria-relevant contains invalid token(s): ${invalid.map((t) => `"${t}"`).join(", ")}. Valid tokens are: additions, removals, text, all.`,
1658
+ diagnostic: AriaDiagnostics.relevantInvalidTokens(invalid),
1045
1659
  fix: _AriaPolicyEngine.#makeRemoveAttributeFix("aria-relevant")
1046
1660
  }
1047
1661
  ];
@@ -1053,7 +1667,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends StrictBase {
1053
1667
  fixable: true,
1054
1668
  severity: "warning",
1055
1669
  attribute: "aria-relevant",
1056
- message: `aria-relevant includes "all" alongside other tokens. "all" supersedes additions, removals, and text \u2014 use aria-relevant="all" alone.`,
1670
+ diagnostic: AriaDiagnostics.relevantSuperseded(),
1057
1671
  fix: _AriaPolicyEngine.#normalizeRelevantAllFix
1058
1672
  }
1059
1673
  ];
@@ -1061,6 +1675,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends StrictBase {
1061
1675
  return VALID;
1062
1676
  }
1063
1677
  };
1678
+
1679
+ // ../../lib/contract/src/children/get-type-name.ts
1064
1680
  function getTypeName(value) {
1065
1681
  if (value === null) return "null";
1066
1682
  if (value === void 0) return "undefined";
@@ -1071,39 +1687,8 @@ function getTypeName(value) {
1071
1687
  const name = value.constructor?.name;
1072
1688
  return typeof name === "string" && name !== "Object" ? name : "object";
1073
1689
  }
1074
- var MatchValidationErrorBuilder = class {
1075
- #prefix;
1076
- constructor(ctx = "") {
1077
- this.#prefix = ctx ? `${ctx}:
1078
- ` : "";
1079
- }
1080
- #template(typeName, index, prefix = "", suffix = "") {
1081
- const leadingStr = prefix ? `${prefix} ` : "";
1082
- const followingStr = suffix ? ` ${suffix}` : "";
1083
- return `${leadingStr}child "${typeName}" at index ${index}${followingStr}.`;
1084
- }
1085
- unexpectedChild(typeName, index) {
1086
- return this.#template(typeName, index, "unexpected");
1087
- }
1088
- multipleMatches(typeName, index, ruleNames) {
1089
- const quoted = ruleNames.map((n) => `"${n}"`);
1090
- return this.#template(
1091
- typeName,
1092
- index,
1093
- "",
1094
- `matches multiple child rules: ${quoted.join(" and ")}`
1095
- );
1096
- }
1097
- #format(errors) {
1098
- return this.#prefix + errors.join("\n");
1099
- }
1100
- toError(errors) {
1101
- if (errors.length === 0) {
1102
- return new Error(this.#prefix + "Unknown validation error.");
1103
- }
1104
- return new Error(this.#format(errors));
1105
- }
1106
- };
1690
+
1691
+ // ../../lib/contract/src/children/normalize-child-rule.ts
1107
1692
  function normalizeCardinality(input, impliesSingleton) {
1108
1693
  const min = input?.min ?? 0;
1109
1694
  const max = input?.max ?? (impliesSingleton ? 1 : Infinity);
@@ -1128,6 +1713,8 @@ function normalizeChildRule(rule) {
1128
1713
  cardinality: normalizeCardinality(rule.cardinality, impliesSingleton)
1129
1714
  };
1130
1715
  }
1716
+
1717
+ // ../../lib/contract/src/children/rules-matcher.ts
1131
1718
  function getChildType(child) {
1132
1719
  if (!isObject(child) || !("type" in child)) return void 0;
1133
1720
  return child.type;
@@ -1136,8 +1723,8 @@ function buildPartialIndex(rules) {
1136
1723
  const typeIndex = /* @__PURE__ */ new Map();
1137
1724
  const duplicateTypes = /* @__PURE__ */ new Set();
1138
1725
  const untypedIndices = [];
1139
- for (let ri = 0; ri < rules.length; ri++) {
1140
- const t = rules[ri].type;
1726
+ iterate.forEach(rules, (rule, ri) => {
1727
+ const t = rule.type;
1141
1728
  if (t === void 0) {
1142
1729
  untypedIndices.push(ri);
1143
1730
  } else if (typeIndex.has(t)) {
@@ -1145,12 +1732,14 @@ function buildPartialIndex(rules) {
1145
1732
  } else {
1146
1733
  typeIndex.set(t, ri);
1147
1734
  }
1148
- }
1735
+ });
1149
1736
  if (duplicateTypes.size > 0) {
1150
- for (const t of duplicateTypes) typeIndex.delete(t);
1151
- for (let ri = 0; ri < rules.length; ri++) {
1152
- if (duplicateTypes.has(rules[ri].type)) untypedIndices.push(ri);
1153
- }
1737
+ iterate.forEachSet(duplicateTypes, (t) => {
1738
+ typeIndex.delete(t);
1739
+ });
1740
+ iterate.forEach(rules, (rule, ri) => {
1741
+ if (duplicateTypes.has(rule.type)) untypedIndices.push(ri);
1742
+ });
1154
1743
  }
1155
1744
  return { typeIndex, untypedIndices };
1156
1745
  }
@@ -1169,10 +1758,10 @@ var RuleMatcher = class {
1169
1758
  const reverse = /* @__PURE__ */ new Map();
1170
1759
  const unexpectedIndices = /* @__PURE__ */ new Set();
1171
1760
  const ambiguousIndices = /* @__PURE__ */ new Set();
1172
- for (let ri = 0; ri < this.#rules.length; ri++) {
1761
+ iterate.forEach(this.#rules, (_, ri) => {
1173
1762
  reverse.set(ri, /* @__PURE__ */ new Set());
1174
- }
1175
- for (const [ci, child] of children.entries()) {
1763
+ });
1764
+ iterate.forEach(children, (child, ci) => {
1176
1765
  const t = getChildType(child);
1177
1766
  if (t !== void 0) {
1178
1767
  const ri = this.#typeIndex.get(t);
@@ -1186,8 +1775,8 @@ var RuleMatcher = class {
1186
1775
  reverse.get(ri).add(ci);
1187
1776
  }
1188
1777
  }
1189
- for (const ri of this.#untypedIndices) {
1190
- if (!this.#rules[ri].match(child)) continue;
1778
+ iterate.forEach(this.#untypedIndices, (ri) => {
1779
+ if (!this.#rules[ri].match(child)) return;
1191
1780
  let childEntry = forward.get(ci);
1192
1781
  if (!childEntry) {
1193
1782
  childEntry = /* @__PURE__ */ new Set();
@@ -1195,33 +1784,35 @@ var RuleMatcher = class {
1195
1784
  }
1196
1785
  childEntry.add(ri);
1197
1786
  reverse.get(ri).add(ci);
1198
- }
1787
+ });
1199
1788
  const entry = forward.get(ci);
1200
1789
  if (!entry) {
1201
1790
  unexpectedIndices.add(ci);
1202
1791
  } else if (entry.size > 1) {
1203
1792
  ambiguousIndices.add(ci);
1204
1793
  }
1205
- }
1794
+ });
1206
1795
  return { matrix: { childToRules: { forward, reverse } }, unexpectedIndices, ambiguousIndices };
1207
1796
  }
1208
1797
  };
1209
- var RuleValidator = class _RuleValidator extends StrictBase {
1798
+
1799
+ // ../../lib/contract/src/children/rule-validator.ts
1800
+ var RuleValidator = class _RuleValidator extends InvariantBase {
1210
1801
  #context;
1211
- constructor(context, strict) {
1212
- super(strict);
1802
+ constructor(context, diagnostics) {
1803
+ super(diagnostics);
1213
1804
  this.#context = context;
1214
1805
  }
1215
1806
  validate(rules, matrix, childCount) {
1216
1807
  const firstIndex = 0;
1217
1808
  const lastIndex = childCount - 1;
1218
- for (const [ri, rule] of rules.entries()) {
1809
+ iterate.forEach(rules, (rule, ri) => {
1219
1810
  const matches = matrix.childToRules.reverse.get(ri);
1220
1811
  const matchCount = matches.size;
1221
1812
  this.#validateCardinality(rule, matchCount);
1222
- if (matchCount === 0) continue;
1813
+ if (matchCount === 0) return;
1223
1814
  this.#validatePositions(rule, matches, firstIndex, lastIndex);
1224
- }
1815
+ });
1225
1816
  }
1226
1817
  #validateCardinality(rule, matchCount) {
1227
1818
  const { cardinality, name } = rule;
@@ -1230,21 +1821,21 @@ var RuleValidator = class _RuleValidator extends StrictBase {
1230
1821
  }
1231
1822
  const { min, max } = cardinality;
1232
1823
  if (matchCount < min) {
1233
- this.violate(`${this.#context}: "${name}" requires at least ${min}.`);
1824
+ this.violate(ContractDiagnostics.cardinalityMin(name, min, this.#context));
1234
1825
  return;
1235
1826
  }
1236
1827
  if (matchCount > max) {
1237
- this.violate(`${this.#context}: "${name}" allows at most ${max}.`);
1828
+ this.violate(ContractDiagnostics.cardinalityMax(name, max, this.#context));
1238
1829
  }
1239
1830
  }
1240
1831
  #validatePositions(rule, matches, firstIndex, lastIndex) {
1241
1832
  const { name, position } = rule;
1242
- for (const index of matches) {
1833
+ iterate.forEachSet(matches, (index) => {
1243
1834
  if (_RuleValidator.#isValidPosition(index, position, firstIndex, lastIndex)) {
1244
- continue;
1835
+ return;
1245
1836
  }
1246
- this.violate(`${this.#context}: "${name}" must be ${position}, got index ${index}`);
1247
- }
1837
+ this.violate(ContractDiagnostics.positionViolation(name, position, index, this.#context));
1838
+ });
1248
1839
  }
1249
1840
  static #isValidPosition(matchIndex, position, firstIndex, lastIndex) {
1250
1841
  switch (position) {
@@ -1259,48 +1850,50 @@ var RuleValidator = class _RuleValidator extends StrictBase {
1259
1850
  }
1260
1851
  }
1261
1852
  };
1262
- var ChildrenEvaluator = class extends StrictBase {
1853
+
1854
+ // ../../lib/contract/src/children/children-evaluator.ts
1855
+ var ChildrenEvaluator = class extends InvariantBase {
1856
+ #context;
1263
1857
  #rules;
1264
1858
  #ruleNames;
1265
1859
  #matcher;
1266
1860
  #ruleValidator;
1267
- #matchBuilder;
1268
- constructor(rules, strict = "warn", context = "Component") {
1269
- super(strict);
1270
- this.#rules = rules.map((r2) => normalizeChildRule(r2));
1271
- this.#ruleNames = this.#rules.map((r2) => r2.name);
1272
- for (const rule of this.#rules) {
1861
+ constructor(rules, diagnostics, context = "Component") {
1862
+ super(diagnostics);
1863
+ this.#context = context;
1864
+ this.#rules = rules.map((r) => normalizeChildRule(r));
1865
+ this.#ruleNames = this.#rules.map((r) => r.name);
1866
+ iterate.forEach(this.#rules, (rule) => {
1273
1867
  const { name, position, cardinality } = rule;
1274
1868
  if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
1275
1869
  throw new RangeError(
1276
1870
  `ChildrenEvaluator [${context}]: rule "${name}" sets position="${position}" with an unbound or >1 max. position="first|last" implies max=1.`
1277
1871
  );
1278
1872
  }
1279
- }
1873
+ });
1280
1874
  this.#matcher = new RuleMatcher(this.#rules);
1281
- this.#ruleValidator = new RuleValidator(context, strict);
1282
- this.#matchBuilder = new MatchValidationErrorBuilder(context);
1875
+ this.#ruleValidator = new RuleValidator(context, diagnostics);
1283
1876
  }
1284
1877
  evaluate(children) {
1285
- if (!this.strict) return;
1878
+ if (!this.active) return;
1286
1879
  const { matrix, unexpectedIndices, ambiguousIndices } = this.#matcher.match(children);
1287
1880
  this.#ruleValidator.validate(this.#rules, matrix, children.length);
1288
1881
  if (unexpectedIndices.size === 0 && ambiguousIndices.size === 0) return;
1289
- const errors = [];
1290
1882
  const violating = [...unexpectedIndices, ...ambiguousIndices].sort((a, b) => a - b);
1291
- for (const ci of violating) {
1883
+ iterate.forEach(violating, (ci) => {
1292
1884
  const typeName = getTypeName(children[ci]);
1293
1885
  if (unexpectedIndices.has(ci)) {
1294
- errors.push(this.#matchBuilder.unexpectedChild(typeName, ci));
1886
+ this.violate(ContractDiagnostics.unexpectedChild(typeName, ci, this.#context));
1295
1887
  } else {
1296
1888
  const matches = matrix.childToRules.forward.get(ci);
1297
1889
  const names = [...matches].map((ri) => this.#ruleNames[ri] ?? `#${ri}`);
1298
- errors.push(this.#matchBuilder.multipleMatches(typeName, ci, names));
1890
+ this.violate(ContractDiagnostics.ambiguousChild(typeName, ci, names, this.#context));
1299
1891
  }
1300
- }
1301
- this.invariant(errors.length === 0, this.#matchBuilder.toError(errors).message);
1892
+ });
1302
1893
  }
1303
1894
  };
1895
+
1896
+ // ../../lib/contract/src/props/get-disabled-props.ts
1304
1897
  var disabledProps = ({
1305
1898
  disabled,
1306
1899
  "aria-disabled": ariaDisabled,
@@ -1312,6 +1905,8 @@ var disabledProps = ({
1312
1905
  ...dataDisabled === void 0 && { "data-disabled": "" }
1313
1906
  };
1314
1907
  };
1908
+
1909
+ // ../../lib/contract/src/props/get-invalid-props.ts
1315
1910
  var invalidProps = ({
1316
1911
  invalid,
1317
1912
  "aria-invalid": ariaInvalid,
@@ -1323,6 +1918,8 @@ var invalidProps = ({
1323
1918
  ...dataInvalid === void 0 && { "data-invalid": "" }
1324
1919
  };
1325
1920
  };
1921
+
1922
+ // ../../lib/contract/src/props/get-readonly-props.ts
1326
1923
  var readonlyProps = ({
1327
1924
  readOnly,
1328
1925
  "aria-readonly": ariaReadonly,
@@ -1338,6 +1935,8 @@ var readonlyProps = ({
1338
1935
  }
1339
1936
  };
1340
1937
  };
1938
+
1939
+ // ../core/src/html/aria-rules.ts
1341
1940
  var LANDMARK_TAG_SET = /* @__PURE__ */ new Set(["article", "aside", "footer", "header", "main", "nav"]);
1342
1941
  var removeLandmarkRoleOverride = {
1343
1942
  kind: "removeRole",
@@ -1357,154 +1956,29 @@ function landmarkRoleRule({ tag, props, implicitRole }) {
1357
1956
  fixable: true,
1358
1957
  severity: "error",
1359
1958
  fix: removeLandmarkRoleOverride,
1360
- message: `<${tag}> has a fixed landmark role="${implicitRole}". role="${role}" overrides it and confuses assistive technology. The override has been removed.`
1959
+ diagnostic: HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role)
1361
1960
  }
1362
1961
  ];
1363
1962
  }
1364
- var HTML_ARIA_RULES = [landmarkRoleRule];
1365
- function isOpenContent(...blockedTags) {
1366
- const set = new Set(blockedTags);
1367
- return (child) => isObject(child) && "type" in child && (!isString(child.type) || !set.has(child.type));
1368
- }
1369
- var METADATA_TAGS = ["script", "template"];
1370
- var metadataMatch = isTag(...METADATA_TAGS);
1371
- function metadata(name = "metadata") {
1372
- return { name, match: metadataMatch };
1373
- }
1374
- function firstOptional(name, tag) {
1375
- return { name, match: isTag(tag), cardinality: { max: 1 }, position: "first" };
1376
- }
1377
- function contract(children) {
1378
- return { strict: "warn", children };
1379
- }
1380
- function ariaContract(aria) {
1381
- return { strict: "warn", aria };
1382
- }
1383
- function firstChildContract(name, tag) {
1384
- return contract([firstOptional(name, tag), { name: "content", match: isOpenContent(tag) }]);
1385
- }
1386
- var VOID_TAGS = [
1387
- "area",
1388
- "base",
1389
- "br",
1390
- "col",
1391
- "embed",
1392
- "hr",
1393
- "img",
1394
- "input",
1395
- "link",
1396
- "meta",
1397
- "param",
1398
- "source",
1399
- "track",
1400
- "wbr"
1401
- ];
1402
- var TEXT_ONLY_TAGS = ["option", "script", "style", "textarea", "title"];
1403
- var LANDMARK_TAGS = ["article", "aside", "footer", "header", "main", "nav"];
1404
- var listContract = contract([{ name: "list-item", match: isTag("li", ...METADATA_TAGS) }]);
1405
- var tableContract = contract([
1406
- firstOptional("caption", "caption"),
1407
- { name: "colgroup", match: isTag("colgroup") },
1408
- { name: "thead", match: isTag("thead"), cardinality: { max: 1 } },
1409
- { name: "tbody", match: isTag("tbody") },
1410
- { name: "tfoot", match: isTag("tfoot"), cardinality: { max: 1 } },
1411
- { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
1412
- ]);
1413
- var tableBodyContract = contract([
1414
- { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
1415
- ]);
1416
- var tableRowContract = contract([
1417
- { name: "table-cell", match: isTag("td", "th", ...METADATA_TAGS) }
1418
- ]);
1419
- var colgroupContract = contract([{ name: "column", match: isTag("col", "template") }]);
1420
- var dlContract = contract([
1421
- { name: "term", match: isTag("dt") },
1422
- { name: "description", match: isTag("dd") },
1423
- { name: "group", match: isTag("div") },
1424
- metadata()
1425
- ]);
1426
- var selectContract = contract([
1427
- { name: "option", match: isTag("option", "optgroup", "hr", ...METADATA_TAGS) }
1428
- ]);
1429
- var optgroupContract = contract([
1430
- { name: "option", match: isTag("option", ...METADATA_TAGS) }
1431
- ]);
1432
- var datalistContract = contract([
1433
- { name: "option", match: isTag("option", ...METADATA_TAGS) }
1434
- ]);
1435
- var pictureContract = contract([
1436
- { name: "source", match: isTag("source", ...METADATA_TAGS) },
1437
- { name: "image", match: isTag("img"), cardinality: { min: 1, max: 1 }, position: "last" }
1438
- ]);
1439
- var figureContract = contract([
1440
- { name: "caption", match: isTag("figcaption"), cardinality: { max: 1 } },
1441
- { name: "content", match: isOpenContent("figcaption") }
1442
- ]);
1443
- var detailsContract = firstChildContract("summary", "summary");
1444
- var fieldsetContract = firstChildContract("legend", "legend");
1445
- var mediaContract = contract([
1446
- { name: "source", match: isTag("source") },
1447
- { name: "track", match: isTag("track") },
1448
- metadata(),
1449
- { name: "content", match: isOpenContent("source", "track", ...METADATA_TAGS) }
1450
- ]);
1451
- var headContract = contract([
1452
- {
1453
- name: "metadata",
1454
- match: isTag("base", "link", "meta", "noscript", "script", "style", "template", "title")
1455
- }
1456
- ]);
1457
- var htmlContract = contract([
1458
- { name: "head", match: isTag("head"), cardinality: { min: 1, max: 1 }, position: "first" },
1459
- { name: "body", match: isTag("body"), cardinality: { min: 1, max: 1 } }
1460
- ]);
1461
- var voidContract = contract([]);
1462
- var textOnlyContract = contract([
1463
- {
1464
- name: "text",
1465
- match: (child) => isString(child) || isNumber(child)
1466
- }
1467
- ]);
1468
- var landmarkContract = ariaContract([landmarkRoleRule]);
1469
- var CONTRACT_GROUPS = [
1470
- [VOID_TAGS, voidContract],
1471
- [TEXT_ONLY_TAGS, textOnlyContract],
1472
- [LANDMARK_TAGS, landmarkContract],
1473
- [["ul", "ol", "menu"], listContract],
1474
- [["audio", "video"], mediaContract],
1475
- [["thead", "tbody", "tfoot"], tableBodyContract]
1476
- ];
1477
- function contractMap(groups) {
1478
- return Object.fromEntries(
1479
- groups.flatMap(([tags, enforcement]) => tags.map((tag) => [tag, enforcement]))
1480
- );
1481
- }
1482
- var htmlContracts = {
1483
- ...contractMap(CONTRACT_GROUPS),
1484
- table: tableContract,
1485
- tr: tableRowContract,
1486
- colgroup: colgroupContract,
1487
- dl: dlContract,
1488
- select: selectContract,
1489
- optgroup: optgroupContract,
1490
- datalist: datalistContract,
1491
- picture: pictureContract,
1492
- figure: figureContract,
1493
- details: detailsContract,
1494
- fieldset: fieldsetContract,
1495
- head: headContract,
1496
- html: htmlContract
1497
- };
1498
- function buildEvaluatorMap() {
1499
- const map = /* @__PURE__ */ new Map();
1500
- for (const [tag, { children }] of Object.entries(htmlContracts)) {
1501
- if (children?.length) {
1502
- map.set(tag, new ChildrenEvaluator(children, "warn", `<${tag}>`));
1963
+ function requireAccessibleName({ tag, props }) {
1964
+ if ("aria-label" in props || "aria-labelledby" in props) return [];
1965
+ return [
1966
+ {
1967
+ valid: false,
1968
+ fixable: false,
1969
+ severity: "warning",
1970
+ diagnostic: AriaDiagnostics.missingAccessibleName(tag)
1503
1971
  }
1504
- }
1505
- return map;
1972
+ ];
1506
1973
  }
1507
- var HTML_EVALUATORS = buildEvaluatorMap();
1974
+ var NAMED_LANDMARK_TAGS = /* @__PURE__ */ new Set(["nav", "aside"]);
1975
+ function landmarkNameAdvisory(ctx) {
1976
+ if (!ctx.implicitRole || !NAMED_LANDMARK_TAGS.has(ctx.tag)) return [];
1977
+ return requireAccessibleName(ctx);
1978
+ }
1979
+ var HTML_ARIA_RULES = [landmarkRoleRule, landmarkNameAdvisory];
1980
+
1981
+ // ../core/src/html/prop-normalizers.ts
1508
1982
  var HTML_FORM_NORMALIZERS = /* @__PURE__ */ new Map([
1509
1983
  ["button", [disabledProps]],
1510
1984
  ["input", [disabledProps, readonlyProps, invalidProps]],
@@ -1517,9 +1991,10 @@ function getHtmlPropNormalizers(tag) {
1517
1991
  return typeof tag === "string" ? HTML_FORM_NORMALIZERS.get(tag) : void 0;
1518
1992
  }
1519
1993
 
1520
- // ../core/dist/chunk-3T4EM5FG.js
1994
+ // ../../node_modules/.pnpm/class-variance-authority@0.7.1/node_modules/class-variance-authority/dist/index.mjs
1995
+ import { clsx as clsx2 } from "clsx";
1521
1996
  var falsyToString = (value) => typeof value === "boolean" ? `${value}` : value === 0 ? "0" : value;
1522
- var cx = clsx;
1997
+ var cx = clsx2;
1523
1998
  var cva = (base, config) => (props) => {
1524
1999
  var _config_compoundVariants;
1525
2000
  if ((config === null || config === void 0 ? void 0 : config.variants) == null) return cx(base, props === null || props === void 0 ? void 0 : props.class, props === null || props === void 0 ? void 0 : props.className);
@@ -1558,10 +2033,14 @@ var cva = (base, config) => (props) => {
1558
2033
  }, []);
1559
2034
  return cx(base, getVariantClassNames, getCompoundVariantClassNames, props === null || props === void 0 ? void 0 : props.class, props === null || props === void 0 ? void 0 : props.className);
1560
2035
  };
2036
+
2037
+ // ../../lib/styling/src/cva.ts
1561
2038
  function cva2(base, config) {
1562
2039
  const fn = cva(base, config);
1563
2040
  return (props) => cn(fn(props));
1564
2041
  }
2042
+
2043
+ // ../../lib/styling/src/static-class-resolver.ts
1565
2044
  var StaticClassResolver = class {
1566
2045
  #baseClass;
1567
2046
  #cache = /* @__PURE__ */ new Map();
@@ -1592,6 +2071,8 @@ var StaticClassResolver = class {
1592
2071
  return result;
1593
2072
  }
1594
2073
  };
2074
+
2075
+ // ../../lib/styling/src/variant-class-resolver.ts
1595
2076
  var VariantClassResolver = class _VariantClassResolver {
1596
2077
  #cvaFn;
1597
2078
  #recipeMap;
@@ -1639,15 +2120,15 @@ var VariantClassResolver = class _VariantClassResolver {
1639
2120
  #createCacheKey(props, recipe) {
1640
2121
  if (this.#variantKeys !== null) {
1641
2122
  let key2 = recipe;
1642
- for (const k of this.#variantKeys) {
2123
+ iterate.forEachSet(this.#variantKeys, (k) => {
1643
2124
  if (k in props) key2 += `|${k}:${_VariantClassResolver.#serializeValue(props[k])}`;
1644
- }
2125
+ });
1645
2126
  return key2;
1646
2127
  }
1647
2128
  let key = recipe;
1648
- for (const k of Object.keys(props).sort()) {
2129
+ iterate.forEach(Object.keys(props).sort(), (k) => {
1649
2130
  key += `|${k}:${_VariantClassResolver.#serializeValue(props[k])}`;
1650
- }
2131
+ });
1651
2132
  return key;
1652
2133
  }
1653
2134
  static #serializeValue(value) {
@@ -1658,6 +2139,8 @@ var VariantClassResolver = class _VariantClassResolver {
1658
2139
  return `x:${String(value)}`;
1659
2140
  }
1660
2141
  };
2142
+
2143
+ // ../../lib/styling/src/create-class-pipeline.ts
1661
2144
  function createClassPipeline(resolved) {
1662
2145
  const baseClass = resolved.baseClassName ?? "";
1663
2146
  const cvaFn = resolved.variants ? cva2("", {
@@ -1682,7 +2165,203 @@ function createClassPipeline(resolved) {
1682
2165
  };
1683
2166
  }
1684
2167
 
1685
- // ../core/dist/index.js
2168
+ // ../core/src/options/resolve-factory-options.ts
2169
+ var EMPTY_VARIANT_KEYS = /* @__PURE__ */ new Set();
2170
+ function composeNormalizers(normalizers, fn) {
2171
+ if (!normalizers?.length) return fn;
2172
+ return ((props) => {
2173
+ const patched = normalizers.reduce(
2174
+ (acc, normalizer) => ({ ...acc, ...normalizer(acc) }),
2175
+ props
2176
+ );
2177
+ return fn ? fn(patched) : patched;
2178
+ });
2179
+ }
2180
+ function whenDefined(key, value) {
2181
+ return value === void 0 ? {} : { [key]: value };
2182
+ }
2183
+ function resolveFactoryOptions(options = {}) {
2184
+ const { styling, enforcement } = options;
2185
+ const composedNormalizeFn = composeNormalizers(enforcement?.props, options.normalize);
2186
+ const variantKeys = styling?.variants === void 0 ? EMPTY_VARIANT_KEYS : new Set(Object.keys(styling.variants));
2187
+ return Object.freeze({
2188
+ defaultTag: options.tag ?? "div",
2189
+ diagnostics: enforcement?.diagnostics ?? silentDiagnostics,
2190
+ variantKeys,
2191
+ ...whenDefined("displayName", options.name),
2192
+ ...whenDefined("defaultProps", options.defaults),
2193
+ ...whenDefined("baseClassName", styling?.base),
2194
+ ...whenDefined("tagMap", styling?.tags),
2195
+ ...whenDefined("recipeMap", styling?.presets),
2196
+ ...whenDefined("variants", styling?.variants),
2197
+ ...whenDefined("defaultVariants", styling?.defaults),
2198
+ ...whenDefined("compoundVariants", styling?.compounds),
2199
+ ...whenDefined("normalizeFn", composedNormalizeFn),
2200
+ ...whenDefined("ariaRules", enforcement?.aria),
2201
+ ...whenDefined("childRules", enforcement?.children),
2202
+ ...whenDefined("allowedAs", enforcement?.allowedAs),
2203
+ ...whenDefined("precomputedClasses", styling?.precomputedClasses)
2204
+ });
2205
+ }
2206
+
2207
+ // ../core/src/options/validate-factory-options.ts
2208
+ function validateFactoryOptions(resolved, diagnostics) {
2209
+ if (!diagnostics.active) return;
2210
+ const name = resolved.displayName ?? "Component";
2211
+ const { variants } = resolved;
2212
+ const checkSelection = (label2, selection) => {
2213
+ iterate.forEachEntry(selection, (dim, value) => {
2214
+ if (value === void 0 || value === null) return;
2215
+ if (!variants || !Object.hasOwn(variants, dim)) {
2216
+ diagnostics.error(ContractDiagnostics.unknownVariantDim(name, label2, dim));
2217
+ return;
2218
+ }
2219
+ const states = variants[dim];
2220
+ const stateKey = String(value);
2221
+ if (!Object.hasOwn(states, stateKey)) {
2222
+ diagnostics.error(
2223
+ ContractDiagnostics.unknownVariantValue(name, label2, dim, stateKey, Object.keys(states))
2224
+ );
2225
+ }
2226
+ });
2227
+ };
2228
+ const { recipeMap } = resolved;
2229
+ if (recipeMap) {
2230
+ iterate.forEachEntry(recipeMap, (recipeKey, selection) => {
2231
+ checkSelection(`preset "${recipeKey}"`, selection);
2232
+ });
2233
+ }
2234
+ if (resolved.defaultVariants) checkSelection("defaults", resolved.defaultVariants);
2235
+ }
2236
+
2237
+ // ../core/src/options/validate-render-props.ts
2238
+ function label(name) {
2239
+ return name ? `[${name}]` : "[createContractComponent]";
2240
+ }
2241
+ function validateRenderProps(diagnostics, options, props, recipeKey) {
2242
+ const { recipeMap, variants, displayName } = options;
2243
+ const tag = label(displayName);
2244
+ if (recipeKey !== void 0 && (!recipeMap || !Object.hasOwn(recipeMap, recipeKey))) {
2245
+ diagnostics.error(ContractDiagnostics.unknownRecipeKey(tag, recipeKey));
2246
+ }
2247
+ if (variants) {
2248
+ iterate.forEachKey(variants, (key) => {
2249
+ if (!Object.hasOwn(props, key)) return;
2250
+ const value = props[key];
2251
+ if (value === void 0 || value === null) return;
2252
+ const dim = variants[key];
2253
+ if (dim && !Object.hasOwn(dim, String(value))) {
2254
+ diagnostics.error(ContractDiagnostics.invalidVariantValue(tag, key, String(value)));
2255
+ }
2256
+ });
2257
+ }
2258
+ }
2259
+
2260
+ // ../core/src/factory/plugin-diagnostics.ts
2261
+ var PluginDiagnostics = {
2262
+ invalidShape(received) {
2263
+ const got = received === null ? "null" : typeof received;
2264
+ return {
2265
+ code: "PLUGIN7001" /* PluginInvalidShape */,
2266
+ category: 7 /* Internal */,
2267
+ message: `[praxis-kit] Plugin factory must return an object with a 'pipeline' function. Got: ${got}.`
2268
+ };
2269
+ },
2270
+ pipelineReturnType(received) {
2271
+ const got = received === null ? "null" : Array.isArray(received) ? "array" : typeof received;
2272
+ return {
2273
+ code: "PLUGIN7002" /* PluginPipelineReturnType */,
2274
+ category: 7 /* Internal */,
2275
+ message: `[praxis-kit] Plugin pipeline must return a string. Got: ${got}.`
2276
+ };
2277
+ }
2278
+ };
2279
+
2280
+ // ../core/src/factory/plugin-invariants.ts
2281
+ function assertPluginShape(result) {
2282
+ if (result === null || typeof result !== "object")
2283
+ throwDiagnostics.error(PluginDiagnostics.invalidShape(result));
2284
+ const plugin = result;
2285
+ if (typeof plugin.pipeline !== "function")
2286
+ throwDiagnostics.error(PluginDiagnostics.invalidShape(result));
2287
+ }
2288
+ function guardPipeline(pipeline) {
2289
+ if (process.env.NODE_ENV === "production") return pipeline;
2290
+ return function guardedPipeline(tag, props, className, recipe) {
2291
+ const result = pipeline(tag, props, className, recipe);
2292
+ if (typeof result !== "string")
2293
+ throwDiagnostics.error(PluginDiagnostics.pipelineReturnType(result));
2294
+ return result;
2295
+ };
2296
+ }
2297
+
2298
+ // ../core/src/factory/create-polymorphic.ts
2299
+ var NOOP_CLASS_PIPELINE = (_tag, _props, className) => Array.isArray(className) ? className.join(" ") : className ?? "";
2300
+ function resolveClassPipeline(options, resolved, diagnostics, capabilities) {
2301
+ const factory = options.styling?.plugin;
2302
+ if (!factory) {
2303
+ const createClassPipeline2 = capabilities?.createClassPipeline;
2304
+ const classPipeline2 = createClassPipeline2 ? createClassPipeline2(resolved) : NOOP_CLASS_PIPELINE;
2305
+ return { pluginResult: void 0, classPipeline: classPipeline2 };
2306
+ }
2307
+ const pluginResult = factory(resolved, diagnostics);
2308
+ assertPluginShape(pluginResult);
2309
+ const classPipeline = guardPipeline(pluginResult.pipeline);
2310
+ return { pluginResult, classPipeline };
2311
+ }
2312
+ function createRuntimeMethods(resolved, classPipeline, engine, renderDiagnostics) {
2313
+ return {
2314
+ resolveTag: makeResolveTag(resolved.defaultTag),
2315
+ resolveProps(props) {
2316
+ return mergeProps(resolved.defaultProps, props);
2317
+ },
2318
+ resolveClasses(tag, props, className, recipe) {
2319
+ if (process.env.NODE_ENV !== "production") {
2320
+ validateRenderProps(renderDiagnostics, resolved, props, recipe);
2321
+ }
2322
+ return classPipeline(tag, props, className, recipe);
2323
+ },
2324
+ resolveAria(tag, props) {
2325
+ if (!engine) return { props };
2326
+ const result = engine.validate(tag, props);
2327
+ return { props: result.props };
2328
+ }
2329
+ };
2330
+ }
2331
+ function createRuntimeObject(methods, resolved, pluginResult) {
2332
+ return pluginResult ? { ...methods, options: resolved, hasStyling: true, classPlugin: pluginResult } : { ...methods, options: resolved };
2333
+ }
2334
+ function createPolymorphic(options = {}, capabilities) {
2335
+ const baseResolved = resolveFactoryOptions(options);
2336
+ const resolved = capabilities?.htmlPropNormalizersFn !== void 0 ? Object.freeze({
2337
+ ...baseResolved,
2338
+ htmlPropNormalizersFn: capabilities.htmlPropNormalizersFn
2339
+ }) : baseResolved;
2340
+ if (process.env.NODE_ENV !== "production") {
2341
+ validateFactoryOptions(resolved, resolved.diagnostics);
2342
+ }
2343
+ const { pluginResult, classPipeline } = resolveClassPipeline(
2344
+ options,
2345
+ resolved,
2346
+ resolved.diagnostics,
2347
+ capabilities
2348
+ );
2349
+ const allAriaRules = [
2350
+ .../* @__PURE__ */ new Set([...capabilities?.htmlAriaRules ?? [], ...resolved.ariaRules ?? []])
2351
+ ];
2352
+ const engine = options.enforcement !== void 0 && capabilities?.AriaEngine ? new capabilities.AriaEngine(
2353
+ resolved.diagnostics,
2354
+ allAriaRules.length ? { rules: allAriaRules } : void 0
2355
+ ) : null;
2356
+ const methods = createRuntimeMethods(resolved, classPipeline, engine, resolved.diagnostics);
2357
+ return createRuntimeObject(
2358
+ methods,
2359
+ resolved,
2360
+ pluginResult
2361
+ );
2362
+ }
2363
+
2364
+ // ../core/src/factory/create-polymorphic-full.ts
1686
2365
  var FULL_CAPABILITIES = {
1687
2366
  createClassPipeline,
1688
2367
  AriaEngine: AriaPolicyEngine,
@@ -1693,6 +2372,11 @@ function createPolymorphic2(options = {}) {
1693
2372
  return createPolymorphic(options, FULL_CAPABILITIES);
1694
2373
  }
1695
2374
 
2375
+ // ../../lib/adapter-utils/src/define-component.ts
2376
+ function defineContractComponent(options) {
2377
+ return (factory) => factory(options);
2378
+ }
2379
+
1696
2380
  // ../../lib/adapter-utils/src/build-core-runtime.ts
1697
2381
  var EMPTY_SET = /* @__PURE__ */ new Set();
1698
2382
  function buildCoreRuntime(normalized) {
@@ -1702,8 +2386,8 @@ function buildCoreRuntime(normalized) {
1702
2386
  }
1703
2387
 
1704
2388
  // ../../lib/adapter-utils/src/build-engines.ts
1705
- function buildEngines(strict, childRules, context) {
1706
- return childRules?.length ? { childrenEvaluator: new ChildrenEvaluator(childRules, strict, context) } : {};
2389
+ function buildEngines(diagnostics, childRules, context) {
2390
+ return childRules?.length ? { childrenEvaluator: new ChildrenEvaluator(childRules, diagnostics, context) } : {};
1707
2391
  }
1708
2392
 
1709
2393
  // ../../lib/adapter-utils/src/compose-filter.ts
@@ -1716,34 +2400,32 @@ function composeFilter(ownedKeys, filterProps) {
1716
2400
  }
1717
2401
 
1718
2402
  // ../../lib/adapter-utils/src/resolve-adapter-common-options.ts
1719
- function resolveAdapterCommonOptions(options, defaultName = "PolymorphicComponent", defaultStrict = "throw") {
2403
+ function resolveAdapterCommonOptions(options, defaultName = "PolymorphicComponent", defaultDiagnostics = throwDiagnostics) {
1720
2404
  return {
1721
2405
  name: options.name ?? defaultName,
1722
- strict: options.enforcement?.strict ?? defaultStrict
2406
+ diagnostics: options.enforcement?.diagnostics ?? defaultDiagnostics
1723
2407
  };
1724
2408
  }
1725
2409
 
1726
2410
  // ../../lib/adapter-utils/src/slot-validator.ts
1727
- var SlotValidator = class extends StrictBase {
2411
+ var SlotValidator = class extends InvariantBase {
1728
2412
  #name;
1729
2413
  #elementTerm;
1730
- constructor(name, strict, elementTerm) {
1731
- super(strict);
2414
+ constructor(name, diagnostics, elementTerm) {
2415
+ super(diagnostics);
1732
2416
  this.#name = name;
1733
2417
  this.#elementTerm = elementTerm;
1734
2418
  }
1735
2419
  assertExclusive() {
1736
- this.violate(`${this.#name}: "as" and "asChild" are mutually exclusive`);
2420
+ this.violate(SlotDiagnostics.exclusive(this.#name));
1737
2421
  }
1738
2422
  warnDiscardedChildren(count) {
1739
- const suffix = count === 1 ? "" : "ren";
1740
- this.warn(
1741
- `${this.#name}: asChild discarded ${count} non-element child${suffix} \u2014 only ${this.#elementTerm}s are valid asChild children.`
1742
- );
2423
+ this.warn(SlotDiagnostics.discardedChildren(this.#name, this.#elementTerm, count));
1743
2424
  }
1744
2425
  assertSingleChild(count) {
1745
- const msg = count === 0 ? `${this.#name}: asChild requires a ${this.#elementTerm} child` : `${this.#name}: asChild requires exactly one ${this.#elementTerm} child, got ${count}`;
1746
- this.violate(msg);
2426
+ this.violate(
2427
+ count === 0 ? SlotDiagnostics.singleChildRequired(this.#name, this.#elementTerm) : SlotDiagnostics.singleChildExceeded(this.#name, this.#elementTerm, count)
2428
+ );
1747
2429
  }
1748
2430
  };
1749
2431
 
@@ -1755,12 +2437,12 @@ function buildRuntime(options) {
1755
2437
  const normalized = normalizeOptions(options);
1756
2438
  const { runtime, ownedKeys } = buildCoreRuntime(normalized);
1757
2439
  const { childrenEvaluator } = buildEngines(
1758
- normalized.strict,
2440
+ normalized.diagnostics,
1759
2441
  normalized.enforcement?.children,
1760
2442
  normalized.name
1761
2443
  );
1762
2444
  const filterProps = composeFilter(ownedKeys, normalized.filterProps);
1763
- const slotValidator = new SlotValidator(normalized.name, normalized.strict, "Snippet");
2445
+ const slotValidator = new SlotValidator(normalized.name, normalized.diagnostics, "Snippet");
1764
2446
  return {
1765
2447
  runtime,
1766
2448
  filterProps,