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