praxis-kit 6.1.0 → 6.2.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
@@ -3,6 +3,16 @@ function defineContractComponent(options) {
3
3
  return (factory) => factory(options);
4
4
  }
5
5
 
6
+ // ../../lib/primitive/src/tag/resolve-tag.ts
7
+ function makeResolveTag(defaultTag) {
8
+ return function tag(as) {
9
+ return as ?? defaultTag;
10
+ };
11
+ }
12
+
13
+ // ../../lib/primitive/src/rule/rule-brand.ts
14
+ var RULE_BRAND = /* @__PURE__ */ Symbol("praxis-kit/dynamic-rule");
15
+
6
16
  // ../../lib/primitive/src/guards/foundational/is-defined.ts
7
17
  function isUndefined(value) {
8
18
  return value === void 0;
@@ -12,8 +22,11 @@ function isUndefined(value) {
12
22
  function isNull(value) {
13
23
  return value === null;
14
24
  }
25
+ function isNonNull(value) {
26
+ return value != null;
27
+ }
15
28
 
16
- // ../../lib/primitive/src/utils/is-object.ts
29
+ // ../../lib/primitive/src/utils/type-guards.ts
17
30
  function isObject(value, excludeArrays = false) {
18
31
  if (value === null || typeof value !== "object") return false;
19
32
  return excludeArrays ? !Array.isArray(value) : true;
@@ -25,6 +38,244 @@ function isNumber(value) {
25
38
  return typeof value === "number";
26
39
  }
27
40
 
41
+ // ../../lib/primitive/src/rule/is-dynamic-rule.ts
42
+ function isDynamicRule(rule) {
43
+ return isObject(rule, true) && rule[RULE_BRAND] === true;
44
+ }
45
+
46
+ // ../../lib/primitive/src/rule/resolve-rule.ts
47
+ function resolveRule(rule, context) {
48
+ return isDynamicRule(rule) ? rule.resolve(context) : rule;
49
+ }
50
+
51
+ // ../../lib/primitive/src/utils/assert-never.ts
52
+ function assertNever(value) {
53
+ throw new Error(`Unexpected value: ${String(value)}`);
54
+ }
55
+
56
+ // ../../lib/primitive/src/utils/cn.ts
57
+ import { clsx } from "clsx";
58
+ function cn(...inputs) {
59
+ return clsx(...inputs);
60
+ }
61
+
62
+ // ../../lib/primitive/src/utils/iterate.ts
63
+ function find(iterable, callback) {
64
+ for (const value of iterable) {
65
+ const result = callback(value);
66
+ if (result != null) {
67
+ return result;
68
+ }
69
+ }
70
+ return null;
71
+ }
72
+ function some(iterable, predicate) {
73
+ for (const value of iterable) {
74
+ if (predicate(value)) return true;
75
+ }
76
+ return false;
77
+ }
78
+ function every(iterable, predicate) {
79
+ let index = 0;
80
+ for (const value of iterable) {
81
+ if (!predicate(value, index++)) {
82
+ return false;
83
+ }
84
+ }
85
+ return true;
86
+ }
87
+ function* filter(iterable, predicate) {
88
+ let index = 0;
89
+ for (const value of iterable) {
90
+ if (predicate(value, index++)) {
91
+ yield value;
92
+ }
93
+ }
94
+ }
95
+ function* map(iterable, callback) {
96
+ let index = 0;
97
+ for (const value of iterable) {
98
+ yield callback(value, index++);
99
+ }
100
+ }
101
+ function forEach(iterable, callback) {
102
+ let index = 0;
103
+ for (const value of iterable) {
104
+ callback(value, index++);
105
+ }
106
+ }
107
+ function reduce(iterable, initial, callback) {
108
+ let accumulator = initial;
109
+ let index = 0;
110
+ for (const value of iterable) {
111
+ accumulator = callback(accumulator, value, index++);
112
+ }
113
+ return accumulator;
114
+ }
115
+ function collect(iterable, callback) {
116
+ const result = {};
117
+ let index = 0;
118
+ for (const value of iterable) {
119
+ const entry = callback(value, index++);
120
+ if (entry === null) {
121
+ return null;
122
+ }
123
+ result[entry[0]] = entry[1];
124
+ }
125
+ return result;
126
+ }
127
+ function findLast(value, callback) {
128
+ for (let index = value.length - 1; index >= 0; index--) {
129
+ const result = callback(value[index], index);
130
+ if (result != null) {
131
+ return result;
132
+ }
133
+ }
134
+ return null;
135
+ }
136
+ function* items(collection) {
137
+ for (let i = 0; i < collection.length; i++) {
138
+ const item = collection.item(i);
139
+ if (item !== null) {
140
+ yield item;
141
+ }
142
+ }
143
+ }
144
+ function nodeList(list) {
145
+ return {
146
+ *[Symbol.iterator]() {
147
+ for (let i = 0; i < list.length; i++) {
148
+ const node = list.item(i);
149
+ if (node !== null) {
150
+ yield node;
151
+ }
152
+ }
153
+ }
154
+ };
155
+ }
156
+ function mapEntries(m) {
157
+ return m.entries();
158
+ }
159
+ function set(s) {
160
+ return s.values();
161
+ }
162
+ function hasOwn(object, key) {
163
+ return Object.hasOwn(object, key);
164
+ }
165
+ function* entries(object) {
166
+ for (const key in object) {
167
+ if (!hasOwn(object, key)) continue;
168
+ yield [key, object[key]];
169
+ }
170
+ }
171
+ function* keys(object) {
172
+ for (const [key] of entries(object)) {
173
+ yield key;
174
+ }
175
+ }
176
+ function* values(object) {
177
+ for (const [, value] of entries(object)) {
178
+ yield value;
179
+ }
180
+ }
181
+ function mapValues(object, callback) {
182
+ const result = {};
183
+ for (const [key, value] of entries(object)) {
184
+ result[key] = callback(value, key);
185
+ }
186
+ return result;
187
+ }
188
+ function forEachEntry(object, callback) {
189
+ for (const [key, value] of entries(object)) {
190
+ callback(key, value);
191
+ }
192
+ }
193
+ function forEachKey(object, callback) {
194
+ for (const key of keys(object)) {
195
+ callback(key);
196
+ }
197
+ }
198
+ function forEachValue(object, callback) {
199
+ for (const value of values(object)) {
200
+ callback(value);
201
+ }
202
+ }
203
+ function forEachSet(s, callback) {
204
+ for (const value of s) {
205
+ callback(value);
206
+ }
207
+ }
208
+ var iterate = Object.freeze({
209
+ entries,
210
+ filter,
211
+ find,
212
+ findLast,
213
+ forEach,
214
+ forEachEntry,
215
+ forEachKey,
216
+ forEachSet,
217
+ forEachValue,
218
+ items,
219
+ keys,
220
+ map,
221
+ mapEntries,
222
+ mapValues,
223
+ nodeList,
224
+ reduce,
225
+ collect,
226
+ set,
227
+ some,
228
+ every,
229
+ values
230
+ });
231
+
232
+ // ../../lib/primitive/src/utils/lru-cache.ts
233
+ var LRUCache = class {
234
+ #maxSize;
235
+ #store = /* @__PURE__ */ new Map();
236
+ constructor(maxSize) {
237
+ if (!Number.isInteger(maxSize) || maxSize < 1) {
238
+ throw new RangeError("LRUCache maxSize must be a positive integer.");
239
+ }
240
+ this.#maxSize = maxSize;
241
+ }
242
+ get(key) {
243
+ if (!this.#store.has(key)) return void 0;
244
+ const value = this.#store.get(key);
245
+ this.#store.delete(key);
246
+ this.#store.set(key, value);
247
+ return value;
248
+ }
249
+ set(key, value) {
250
+ this.#store.delete(key);
251
+ this.#store.set(key, value);
252
+ if (this.#store.size > this.#maxSize) {
253
+ const lru = this.#store.keys().next().value;
254
+ if (lru !== void 0) this.#store.delete(lru);
255
+ }
256
+ }
257
+ has(key) {
258
+ return this.#store.has(key);
259
+ }
260
+ delete(key) {
261
+ return this.#store.delete(key);
262
+ }
263
+ get size() {
264
+ return this.#store.size;
265
+ }
266
+ clear() {
267
+ this.#store.clear();
268
+ }
269
+ };
270
+
271
+ // ../../lib/primitive/src/utils/merge-props.ts
272
+ function mergeProps(defaultProps, props) {
273
+ return {
274
+ ...defaultProps ?? {},
275
+ ...props
276
+ };
277
+ }
278
+
28
279
  // ../../lib/primitive/src/constants/aria/global-aria-attributes.ts
29
280
  var GLOBAL_ARIA_ATTRIBUTES = /* @__PURE__ */ new Set([
30
281
  "aria-atomic",
@@ -261,297 +512,38 @@ var ROLE_RESTRICTED_ATTRIBUTES = /* @__PURE__ */ new Map([
261
512
  "aria-valuetext",
262
513
  /* @__PURE__ */ new Set(["meter", "progressbar", "scrollbar", "separator", "slider", "spinbutton"])
263
514
  ]
264
- ]);
265
-
266
- // ../../lib/primitive/src/guards/aria/is-aria-attribute.ts
267
- function isGlobalAriaAttribute(attr) {
268
- return GLOBAL_ARIA_ATTRIBUTES.has(attr);
269
- }
270
- function isAriaAttributeValidForRole(attr, role) {
271
- const allowedRoles = ROLE_RESTRICTED_ATTRIBUTES.get(attr);
272
- if (isUndefined(allowedRoles)) return true;
273
- if (isUndefined(role)) return false;
274
- return allowedRoles.has(role);
275
- }
276
-
277
- // ../../lib/primitive/src/guards/aria/is-aria-role.ts
278
- function isStrongImplicitRole(tag) {
279
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
280
- return STRONG_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
281
- }
282
- function isStandaloneTag(tag) {
283
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
284
- return STANDALONE_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
285
- }
286
- function getInputImplicitRole(type) {
287
- if (!isString(type) || !(type in INPUT_TYPE_ROLE_MAP)) return void 0;
288
- return INPUT_TYPE_ROLE_MAP[type];
289
- }
290
- function getConditionalImplicitRole(tag, ariaLabel, ariaLabelledBy) {
291
- const isNamed = isString(ariaLabel) || isString(ariaLabelledBy);
292
- if (!isNamed) return void 0;
293
- if (tag === "section") return "region";
294
- if (tag === "form") return "form";
295
- return void 0;
296
- }
297
-
298
- // ../../lib/contract/src/aria/aria-role-policy.ts
299
- function getImplicitRole(tag, props) {
300
- if (tag in IMPLICIT_ROLE_RECORD) return IMPLICIT_ROLE_RECORD[tag];
301
- if (tag === "input") return getInputImplicitRole(props?.type);
302
- if (tag === "img") return props?.alt === "" ? "none" : "img";
303
- if (tag === "section" || tag === "form") {
304
- return getConditionalImplicitRole(tag, props?.["aria-label"], props?.["aria-labelledby"]);
305
- }
306
- return void 0;
307
- }
308
-
309
- // ../../lib/primitive/src/tag/resolve-tag.ts
310
- function makeResolveTag(defaultTag) {
311
- return function tag(as) {
312
- return as ?? defaultTag;
313
- };
314
- }
315
-
316
- // ../../lib/primitive/src/rule/rule-brand.ts
317
- var RULE_BRAND = /* @__PURE__ */ Symbol("praxis-kit/dynamic-rule");
318
-
319
- // ../../lib/primitive/src/rule/is-dynamic-rule.ts
320
- function isDynamicRule(rule) {
321
- return isObject(rule, true) && rule[RULE_BRAND] === true;
322
- }
323
-
324
- // ../../lib/primitive/src/rule/resolve-rule.ts
325
- function resolveRule(rule, context) {
326
- return isDynamicRule(rule) ? rule.resolve(context) : rule;
327
- }
328
-
329
- // ../../lib/primitive/src/utils/assert-never.ts
330
- function assertNever(value) {
331
- throw new Error(`Unexpected value: ${String(value)}`);
332
- }
333
-
334
- // ../../lib/primitive/src/utils/cn.ts
335
- import { clsx } from "clsx";
336
- function cn(...inputs) {
337
- return clsx(...inputs);
338
- }
515
+ ]);
339
516
 
340
- // ../../lib/primitive/src/utils/iterate.ts
341
- function find(iterable, callback) {
342
- for (const value of iterable) {
343
- const result = callback(value);
344
- if (result != null) {
345
- return result;
346
- }
347
- }
348
- return null;
349
- }
350
- function some(iterable, predicate) {
351
- for (const value of iterable) {
352
- if (predicate(value)) return true;
353
- }
354
- return false;
355
- }
356
- function every(iterable, predicate) {
357
- let index = 0;
358
- for (const value of iterable) {
359
- if (!predicate(value, index++)) {
360
- return false;
361
- }
362
- }
363
- return true;
364
- }
365
- function* filter(iterable, predicate) {
366
- let index = 0;
367
- for (const value of iterable) {
368
- if (predicate(value, index++)) {
369
- yield value;
370
- }
371
- }
372
- }
373
- function* map(iterable, callback) {
374
- let index = 0;
375
- for (const value of iterable) {
376
- yield callback(value, index++);
377
- }
378
- }
379
- function forEach(iterable, callback) {
380
- let index = 0;
381
- for (const value of iterable) {
382
- callback(value, index++);
383
- }
384
- }
385
- function reduce(iterable, initial, callback) {
386
- let accumulator = initial;
387
- let index = 0;
388
- for (const value of iterable) {
389
- accumulator = callback(accumulator, value, index++);
390
- }
391
- return accumulator;
392
- }
393
- function collect(iterable, callback) {
394
- const result = {};
395
- let index = 0;
396
- for (const value of iterable) {
397
- const entry = callback(value, index++);
398
- if (entry === null) {
399
- return null;
400
- }
401
- result[entry[0]] = entry[1];
402
- }
403
- return result;
404
- }
405
- function findLast(value, callback) {
406
- for (let index = value.length - 1; index >= 0; index--) {
407
- const result = callback(value[index], index);
408
- if (result != null) {
409
- return result;
410
- }
411
- }
412
- return null;
413
- }
414
- function* items(collection) {
415
- for (let i = 0; i < collection.length; i++) {
416
- const item = collection.item(i);
417
- if (item !== null) {
418
- yield item;
419
- }
420
- }
421
- }
422
- function nodeList(list) {
423
- return {
424
- *[Symbol.iterator]() {
425
- for (let i = 0; i < list.length; i++) {
426
- const node = list.item(i);
427
- if (node !== null) {
428
- yield node;
429
- }
430
- }
431
- }
432
- };
433
- }
434
- function mapEntries(m) {
435
- return m.entries();
436
- }
437
- function set(s) {
438
- return s.values();
439
- }
440
- function hasOwn(object, key) {
441
- return Object.hasOwn(object, key);
442
- }
443
- function* entries(object) {
444
- for (const key in object) {
445
- if (!hasOwn(object, key)) continue;
446
- yield [key, object[key]];
447
- }
448
- }
449
- function* keys(object) {
450
- for (const [key] of entries(object)) {
451
- yield key;
452
- }
453
- }
454
- function* values(object) {
455
- for (const [, value] of entries(object)) {
456
- yield value;
457
- }
458
- }
459
- function mapValues(object, callback) {
460
- const result = {};
461
- for (const [key, value] of entries(object)) {
462
- result[key] = callback(value, key);
463
- }
464
- return result;
517
+ // ../../lib/primitive/src/guards/aria/is-aria-attribute.ts
518
+ function isGlobalAriaAttribute(attr) {
519
+ return GLOBAL_ARIA_ATTRIBUTES.has(attr);
465
520
  }
466
- function forEachEntry(object, callback) {
467
- for (const [key, value] of entries(object)) {
468
- callback(key, value);
469
- }
521
+ function isAriaAttributeValidForRole(attr, role) {
522
+ const allowedRoles = ROLE_RESTRICTED_ATTRIBUTES.get(attr);
523
+ if (isUndefined(allowedRoles)) return true;
524
+ if (isUndefined(role)) return false;
525
+ return allowedRoles.has(role);
470
526
  }
471
- function forEachKey(object, callback) {
472
- for (const key of keys(object)) {
473
- callback(key);
474
- }
527
+
528
+ // ../../lib/primitive/src/guards/aria/is-aria-role.ts
529
+ function isStrongImplicitRole(tag) {
530
+ if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
531
+ return STRONG_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
475
532
  }
476
- function forEachValue(object, callback) {
477
- for (const value of values(object)) {
478
- callback(value);
479
- }
533
+ function isStandaloneTag(tag) {
534
+ if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
535
+ return STANDALONE_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
480
536
  }
481
- function forEachSet(s, callback) {
482
- for (const value of s) {
483
- callback(value);
484
- }
537
+ function getInputImplicitRole(type) {
538
+ if (!isString(type) || !(type in INPUT_TYPE_ROLE_MAP)) return void 0;
539
+ return INPUT_TYPE_ROLE_MAP[type];
485
540
  }
486
- var iterate = Object.freeze({
487
- entries,
488
- filter,
489
- find,
490
- findLast,
491
- forEach,
492
- forEachEntry,
493
- forEachKey,
494
- forEachSet,
495
- forEachValue,
496
- items,
497
- keys,
498
- map,
499
- mapEntries,
500
- mapValues,
501
- nodeList,
502
- reduce,
503
- collect,
504
- set,
505
- some,
506
- every,
507
- values
508
- });
509
-
510
- // ../../lib/primitive/src/utils/lru-cache.ts
511
- var LRUCache = class {
512
- #maxSize;
513
- #store = /* @__PURE__ */ new Map();
514
- constructor(maxSize) {
515
- if (!Number.isInteger(maxSize) || maxSize < 1) {
516
- throw new RangeError("LRUCache maxSize must be a positive integer.");
517
- }
518
- this.#maxSize = maxSize;
519
- }
520
- get(key) {
521
- if (!this.#store.has(key)) return void 0;
522
- const value = this.#store.get(key);
523
- this.#store.delete(key);
524
- this.#store.set(key, value);
525
- return value;
526
- }
527
- set(key, value) {
528
- this.#store.delete(key);
529
- this.#store.set(key, value);
530
- if (this.#store.size > this.#maxSize) {
531
- const lru = this.#store.keys().next().value;
532
- if (lru !== void 0) this.#store.delete(lru);
533
- }
534
- }
535
- has(key) {
536
- return this.#store.has(key);
537
- }
538
- delete(key) {
539
- return this.#store.delete(key);
540
- }
541
- get size() {
542
- return this.#store.size;
543
- }
544
- clear() {
545
- this.#store.clear();
546
- }
547
- };
548
-
549
- // ../../lib/primitive/src/utils/merge-props.ts
550
- function mergeProps(defaultProps, props) {
551
- return {
552
- ...defaultProps ?? {},
553
- ...props
554
- };
541
+ function getConditionalImplicitRole(tag, ariaLabel, ariaLabelledBy) {
542
+ const isNamed = isString(ariaLabel) || isString(ariaLabelledBy);
543
+ if (!isNamed) return void 0;
544
+ if (tag === "section") return "region";
545
+ if (tag === "form") return "form";
546
+ return void 0;
555
547
  }
556
548
 
557
549
  // ../../lib/primitive/src/guards/children/component-id.ts
@@ -590,6 +582,17 @@ function isTag(...args) {
590
582
  return tag !== void 0 && set2.has(tag);
591
583
  }
592
584
 
585
+ // ../../lib/contract/src/aria/aria-role-policy.ts
586
+ function getImplicitRole(tag, props) {
587
+ if (tag in IMPLICIT_ROLE_RECORD) return IMPLICIT_ROLE_RECORD[tag];
588
+ if (tag === "input") return getInputImplicitRole(props?.type);
589
+ if (tag === "img") return props?.alt === "" ? "none" : "img";
590
+ if (tag === "section" || tag === "form") {
591
+ return getConditionalImplicitRole(tag, props?.["aria-label"], props?.["aria-labelledby"]);
592
+ }
593
+ return void 0;
594
+ }
595
+
593
596
  // ../../lib/contract/src/diagnostics/aria.ts
594
597
  import { DiagnosticCategory, DiagnosticCode } from "../_shared/diagnostics.js";
595
598
  var AriaDiagnostics = {
@@ -923,7 +926,7 @@ var InvariantBase = class {
923
926
  };
924
927
 
925
928
  // ../../lib/contract/src/aria/polymorphic-validator.ts
926
- var VALID = [{ valid: true }];
929
+ var NO_VIOLATIONS = [{ valid: true }];
927
930
  function isIntrinsicTag(tag) {
928
931
  return isString(tag);
929
932
  }
@@ -965,17 +968,20 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
965
968
  static #deriveContext(tag, props) {
966
969
  if (!isIntrinsicTag(tag)) return { proceed: false, result: { props, violations: [] } };
967
970
  const implicitRole = getImplicitRole(tag, props);
968
- const hasRole = implicitRole != null || isString(props.role) && props.role.length > 0;
971
+ const hasRole = isNonNull(implicitRole) || isString(props.role) && props.role.length > 0;
969
972
  if (!hasRole) return { proceed: false, result: { props, violations: [] } };
970
973
  const normalized = _AriaPolicyEngine.#normalizeEmptyRole(tag, props);
971
- if (normalized.normalized) return { proceed: false, result: normalized.result };
972
- const effectiveRole = props.role ?? implicitRole;
974
+ const workingProps = normalized.normalized ? normalized.result.props : props;
975
+ const preExistingViolations = normalized.normalized ? normalized.result.violations : [];
976
+ const effectiveRole = workingProps.role ?? implicitRole;
973
977
  return {
974
978
  proceed: true,
975
979
  tag,
976
980
  implicitRole,
977
981
  effectiveRole,
978
- context: { tag, props, implicitRole, effectiveRole }
982
+ props: workingProps,
983
+ preExistingViolations,
984
+ context: { tag, props: workingProps, implicitRole, effectiveRole }
979
985
  };
980
986
  }
981
987
  static #runRules(rules, context) {
@@ -990,7 +996,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
990
996
  } = context;
991
997
  const { message, attribute, severity } = result;
992
998
  const resolvedMessage = message ?? result.diagnostic?.message;
993
- const fallbackDiag = resolvedMessage == null ? AriaDiagnostics.invalidRole(role, tag) : void 0;
999
+ const fallbackDiag = isNonNull(resolvedMessage) ? void 0 : AriaDiagnostics.invalidRole(role, tag);
994
1000
  violations.push({
995
1001
  message: resolvedMessage ?? fallbackDiag.message,
996
1002
  tag,
@@ -998,8 +1004,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
998
1004
  attribute,
999
1005
  severity,
1000
1006
  phase: "evaluate",
1001
- ...result.diagnostic != null && { diagnostic: result.diagnostic },
1002
- ...fallbackDiag != null && { diagnostic: fallbackDiag }
1007
+ ...isNonNull(result.diagnostic) && { diagnostic: result.diagnostic },
1008
+ ...isNonNull(fallbackDiag) && { diagnostic: fallbackDiag }
1003
1009
  });
1004
1010
  if (result.fixable) fixes.push(result.fix);
1005
1011
  });
@@ -1007,7 +1013,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1007
1013
  return { violations, fixes };
1008
1014
  }
1009
1015
  static #getRules(context) {
1010
- if (_AriaPolicyEngine.#hasRole(context.props) || context.effectiveRole != null && _AriaPolicyEngine.#LIVE_REGION_ROLES.has(context.effectiveRole)) {
1016
+ if (_AriaPolicyEngine.#hasRole(context.props) || isNonNull(context.effectiveRole) && _AriaPolicyEngine.#LIVE_REGION_ROLES.has(context.effectiveRole)) {
1011
1017
  return _AriaPolicyEngine.#pipeline;
1012
1018
  }
1013
1019
  return _AriaPolicyEngine.#implicitOnlyRules;
@@ -1015,24 +1021,36 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1015
1021
  static evaluate(tag, props) {
1016
1022
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1017
1023
  if (!derived.proceed) return derived.result;
1018
- const { tag: narrowedTag, implicitRole, context } = derived;
1024
+ const {
1025
+ tag: narrowedTag,
1026
+ implicitRole,
1027
+ context,
1028
+ props: workingProps,
1029
+ preExistingViolations
1030
+ } = derived;
1019
1031
  const { violations, fixes } = _AriaPolicyEngine.#runRules(
1020
1032
  _AriaPolicyEngine.#getRules(context),
1021
1033
  context
1022
1034
  );
1023
- const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, props, fixes);
1024
- return { props: next, violations };
1035
+ const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1036
+ return { props: next, violations: [...preExistingViolations, ...violations] };
1025
1037
  }
1026
1038
  static #evaluateWithRules(tag, props, extraRules) {
1027
1039
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1028
1040
  if (!derived.proceed) return derived.result;
1029
- const { tag: narrowedTag, implicitRole, context } = derived;
1041
+ const {
1042
+ tag: narrowedTag,
1043
+ implicitRole,
1044
+ context,
1045
+ props: workingProps,
1046
+ preExistingViolations
1047
+ } = derived;
1030
1048
  const { violations, fixes } = _AriaPolicyEngine.#runRules(
1031
1049
  [..._AriaPolicyEngine.#getRules(context), ...extraRules],
1032
1050
  context
1033
1051
  );
1034
- const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, props, fixes);
1035
- return { props: next, violations };
1052
+ const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1053
+ return { props: next, violations: [...preExistingViolations, ...violations] };
1036
1054
  }
1037
1055
  report(violations) {
1038
1056
  iterate.forEach(violations, (v) => {
@@ -1041,12 +1059,12 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1041
1059
  else this.warn(d);
1042
1060
  });
1043
1061
  }
1044
- // Cache key covers only the aria-relevant subset of props (tag + role + aria-* attrs).
1045
- // Non-aria props (className, onClick, etc.) do not affect ARIA decisions and are excluded
1046
- // so cache hits survive re-renders that only change non-aria props.
1047
- // Note: #extraRules are NOT included in the key each engine instance has its own Map,
1048
- // so two engines with different rules never share cache entries. If caching ever becomes
1049
- // static/shared, rule identity would need to be folded into the key.
1062
+ // Cache key covers only the aria-relevant subset of props (tag + role + aria-* attrs)
1063
+ // exactly what the built-in pipeline reads. Non-aria props (className, onClick, etc.) do
1064
+ // not affect built-in ARIA decisions and are excluded so cache hits survive re-renders
1065
+ // that only change non-aria props. This key alone is unsound for #extraRules, which may
1066
+ // read arbitrary props outside this set validate() extends it with #extraRulesKeySuffix,
1067
+ // or bypasses the cache, to account for that.
1050
1068
  static #createPlanKey(tag, props) {
1051
1069
  if (!isIntrinsicTag(tag)) return null;
1052
1070
  const parts = [tag];
@@ -1062,6 +1080,23 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1062
1080
  if (ariaEntries.length > 0) parts.push(...ariaEntries.sort());
1063
1081
  return parts.join("|");
1064
1082
  }
1083
+ // Extends the base cache key with the props each extra rule declares it reads (`readsProps`).
1084
+ // Returns null — meaning "don't cache" — if any extra rule omits `readsProps` (it may read
1085
+ // arbitrary props the key can't account for) or if a declared prop's value isn't a primitive
1086
+ // (object/array identity isn't stably representable in a string key).
1087
+ static #extraRulesKeySuffix(extraRules, props) {
1088
+ const parts = [];
1089
+ for (const rule of extraRules) {
1090
+ const readsProps = rule.readsProps;
1091
+ if (!isNonNull(readsProps)) return null;
1092
+ for (const propKey of readsProps) {
1093
+ const v = props[propKey];
1094
+ if (v !== void 0 && !isString(v) && !isNumber(v) && typeof v !== "boolean") return null;
1095
+ parts.push(`x:${propKey}:${String(v)}`);
1096
+ }
1097
+ }
1098
+ return parts.sort().join("|");
1099
+ }
1065
1100
  static #computePlan(inputProps, resultProps) {
1066
1101
  const removals = /* @__PURE__ */ new Set();
1067
1102
  const updates = {};
@@ -1085,7 +1120,12 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1085
1120
  return next;
1086
1121
  }
1087
1122
  validate(tag, props) {
1088
- const key = _AriaPolicyEngine.#createPlanKey(tag, props);
1123
+ const baseKey = _AriaPolicyEngine.#createPlanKey(tag, props);
1124
+ let key = baseKey;
1125
+ if (this.#extraRules.length > 0) {
1126
+ const suffix = _AriaPolicyEngine.#extraRulesKeySuffix(this.#extraRules, props);
1127
+ key = isNonNull(baseKey) && isNonNull(suffix) ? `${baseKey}|${suffix}` : null;
1128
+ }
1089
1129
  if (!isNull(key)) {
1090
1130
  const cached = this.#planCache.get(key);
1091
1131
  if (cached !== void 0) {
@@ -1175,7 +1215,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1175
1215
  implicitRole
1176
1216
  }) {
1177
1217
  const role = props.role;
1178
- if (!implicitRole || !role || role === implicitRole) return VALID;
1218
+ if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS;
1179
1219
  if (isStrongImplicitRole(tag) && role === "region") {
1180
1220
  return [
1181
1221
  {
@@ -1187,11 +1227,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1187
1227
  }
1188
1228
  ];
1189
1229
  }
1190
- return VALID;
1230
+ return NO_VIOLATIONS;
1191
1231
  }
1192
1232
  static #checkRedundantRole({ tag, props, implicitRole }) {
1193
1233
  const role = props.role;
1194
- if (!implicitRole || !role || role !== implicitRole) return VALID;
1234
+ if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS;
1195
1235
  return [
1196
1236
  {
1197
1237
  valid: false,
@@ -1204,8 +1244,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1204
1244
  }
1205
1245
  static #checkStandaloneRegion({ tag, props, implicitRole }) {
1206
1246
  const role = props.role;
1207
- if (role !== "region") return VALID;
1208
- if (!isStandaloneTag(tag)) return VALID;
1247
+ if (role !== "region") return NO_VIOLATIONS;
1248
+ if (!isStandaloneTag(tag)) return NO_VIOLATIONS;
1209
1249
  return [
1210
1250
  {
1211
1251
  valid: false,
@@ -1221,7 +1261,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1221
1261
  props,
1222
1262
  effectiveRole
1223
1263
  }) {
1224
- if (effectiveRole === "none" || effectiveRole === "presentation") return VALID;
1264
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1225
1265
  const results = [];
1226
1266
  iterate.forEachEntry(props, (key) => {
1227
1267
  if (!key.startsWith("aria-")) return;
@@ -1339,12 +1379,12 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1339
1379
  }
1340
1380
  }
1341
1381
  static #checkAriaAttributeValues({ props, effectiveRole }) {
1342
- if (effectiveRole === "none" || effectiveRole === "presentation") return VALID;
1382
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1343
1383
  const results = [];
1344
1384
  iterate.forEachEntry(props, (key, value) => {
1345
1385
  if (!key.startsWith("aria-")) return;
1346
1386
  const type = _AriaPolicyEngine.#ARIA_VALUE_TYPES.get(key);
1347
- if (type == null) return;
1387
+ if (!isNonNull(type)) return;
1348
1388
  if (_AriaPolicyEngine.#isValidAriaValue(value, type)) return;
1349
1389
  results.push({
1350
1390
  valid: false,
@@ -1375,13 +1415,13 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1375
1415
  props,
1376
1416
  effectiveRole
1377
1417
  }) {
1378
- if (effectiveRole === "none" || effectiveRole === "presentation") return VALID;
1418
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1379
1419
  const implicitLevel = _AriaPolicyEngine.#HEADING_IMPLICIT_LEVELS.get(tag);
1380
- if (implicitLevel == null) return VALID;
1420
+ if (!isNonNull(implicitLevel)) return NO_VIOLATIONS;
1381
1421
  const raw = props["aria-level"];
1382
- if (raw == null) return VALID;
1422
+ if (!isNonNull(raw)) return NO_VIOLATIONS;
1383
1423
  const n = typeof raw === "number" ? raw : typeof raw === "string" ? parseInt(raw, 10) : NaN;
1384
- if (!Number.isFinite(n) || n !== implicitLevel) return VALID;
1424
+ if (!Number.isFinite(n) || n !== implicitLevel) return NO_VIOLATIONS;
1385
1425
  return [
1386
1426
  {
1387
1427
  valid: false,
@@ -1404,9 +1444,10 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1404
1444
  props,
1405
1445
  effectiveRole
1406
1446
  }) {
1407
- if (!effectiveRole || !_AriaPolicyEngine.#NAME_REQUIRED_ROLES.has(effectiveRole)) return VALID;
1408
- if ("aria-label" in props || "aria-labelledby" in props) return VALID;
1409
- if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return VALID;
1447
+ if (!effectiveRole || !_AriaPolicyEngine.#NAME_REQUIRED_ROLES.has(effectiveRole))
1448
+ return NO_VIOLATIONS;
1449
+ if ("aria-label" in props || "aria-labelledby" in props) return NO_VIOLATIONS;
1450
+ if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return NO_VIOLATIONS;
1410
1451
  return [
1411
1452
  {
1412
1453
  valid: false,
@@ -1429,9 +1470,9 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1429
1470
  props,
1430
1471
  effectiveRole
1431
1472
  }) {
1432
- if (!effectiveRole) return VALID;
1473
+ if (!effectiveRole) return NO_VIOLATIONS;
1433
1474
  const required = _AriaPolicyEngine.#REQUIRED_PROPERTIES.get(effectiveRole);
1434
- if (required == null) return VALID;
1475
+ if (!isNonNull(required)) return NO_VIOLATIONS;
1435
1476
  const results = [];
1436
1477
  iterate.forEach(required, (attr) => {
1437
1478
  if (attr in props) return;
@@ -1455,12 +1496,12 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1455
1496
  ]);
1456
1497
  // WAI-ARIA 1.2 §6.6: aria-hidden="true" must not be placed on focusable elements.
1457
1498
  static #checkAriaHiddenOnFocusable({ tag, props }) {
1458
- if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return VALID;
1499
+ if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return NO_VIOLATIONS;
1459
1500
  const isInteractive = _AriaPolicyEngine.#INTERACTIVE_TAGS.has(tag);
1460
1501
  if (!isInteractive) {
1461
1502
  const tabindex = props.tabindex;
1462
1503
  const n = typeof tabindex === "number" ? tabindex : typeof tabindex === "string" ? parseInt(tabindex, 10) : NaN;
1463
- if (!Number.isFinite(n) || n < 0) return VALID;
1504
+ if (!Number.isFinite(n) || n < 0) return NO_VIOLATIONS;
1464
1505
  }
1465
1506
  return [
1466
1507
  {
@@ -1479,7 +1520,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1479
1520
  props,
1480
1521
  effectiveRole
1481
1522
  }) {
1482
- if (effectiveRole !== "none" && effectiveRole !== "presentation") return VALID;
1523
+ if (effectiveRole !== "none" && effectiveRole !== "presentation") return NO_VIOLATIONS;
1483
1524
  const results = [];
1484
1525
  iterate.forEachEntry(props, (key) => {
1485
1526
  if (!key.startsWith("aria-")) return;
@@ -1503,10 +1544,10 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1503
1544
  ["timer", "off"]
1504
1545
  ]);
1505
1546
  static #checkMissingLiveRegion({ effectiveRole, props }) {
1506
- if (!effectiveRole) return VALID;
1547
+ if (!effectiveRole) return NO_VIOLATIONS;
1507
1548
  const impliedLive = _AriaPolicyEngine.#LIVE_REGION_ROLES.get(effectiveRole);
1508
- if (!impliedLive) return VALID;
1509
- if ("aria-live" in props) return VALID;
1549
+ if (!impliedLive) return NO_VIOLATIONS;
1550
+ if ("aria-live" in props) return NO_VIOLATIONS;
1510
1551
  const injectLive = {
1511
1552
  kind: `injectLive:${effectiveRole}`,
1512
1553
  apply: (ctx) => ({
@@ -1526,8 +1567,9 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1526
1567
  ];
1527
1568
  }
1528
1569
  static #checkMissingAtomic({ effectiveRole, props }) {
1529
- if (!effectiveRole || !_AriaPolicyEngine.#LIVE_REGION_ROLES.has(effectiveRole)) return VALID;
1530
- if ("aria-atomic" in props) return VALID;
1570
+ if (!effectiveRole || !_AriaPolicyEngine.#LIVE_REGION_ROLES.has(effectiveRole))
1571
+ return NO_VIOLATIONS;
1572
+ if ("aria-atomic" in props) return NO_VIOLATIONS;
1531
1573
  return [
1532
1574
  {
1533
1575
  valid: false,
@@ -1551,8 +1593,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1551
1593
  };
1552
1594
  static #checkInvalidAriaRelevant({ props }) {
1553
1595
  const relevant = props["aria-relevant"];
1554
- if (relevant === void 0) return VALID;
1555
- if (typeof relevant !== "string") return VALID;
1596
+ if (relevant === void 0) return NO_VIOLATIONS;
1597
+ if (typeof relevant !== "string") return NO_VIOLATIONS;
1556
1598
  const tokens = relevant.trim().split(/\s+/);
1557
1599
  const invalid = tokens.filter((t) => !_AriaPolicyEngine.#VALID_RELEVANT_TOKENS.has(t));
1558
1600
  if (invalid.length > 0) {
@@ -1579,7 +1621,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1579
1621
  }
1580
1622
  ];
1581
1623
  }
1582
- return VALID;
1624
+ return NO_VIOLATIONS;
1583
1625
  }
1584
1626
  };
1585
1627
 
@@ -2280,7 +2322,7 @@ function createClassPipeline(resolved) {
2280
2322
  const staticClasses = staticResolver.resolve(tag, recipe !== void 0);
2281
2323
  const variantClasses = variantResolver.resolve({ props, recipe });
2282
2324
  if (!className)
2283
- return staticClasses && variantClasses ? `${staticClasses} ${variantClasses}` : staticClasses || variantClasses;
2325
+ return (staticClasses && variantClasses ? `${staticClasses} ${variantClasses}` : staticClasses || variantClasses) || void 0;
2284
2326
  return cn(staticClasses, variantClasses, className);
2285
2327
  };
2286
2328
  }
@@ -2320,7 +2362,10 @@ function resolveFactoryOptions(options = {}) {
2320
2362
  const variantKeys = styling?.variants === void 0 ? EMPTY_VARIANT_KEYS : new Set(Object.keys(styling.variants));
2321
2363
  return Object.freeze({
2322
2364
  defaultTag: options.tag ?? "div",
2323
- diagnostics: resolveDiagnostics(enforcement?.diagnostics, silentDiagnostics),
2365
+ diagnostics: resolveDiagnostics(
2366
+ enforcement?.diagnostics,
2367
+ options.diagnostics ?? silentDiagnostics
2368
+ ),
2324
2369
  variantKeys,
2325
2370
  ...whenDefined("displayName", options.name),
2326
2371
  ...whenDefined("defaultProps", options.defaults),
@@ -2491,7 +2536,7 @@ function createPolymorphic2(options = {}) {
2491
2536
  if (process.env.NODE_ENV !== "production") {
2492
2537
  validateRenderProps(resolved.diagnostics, resolved, props, recipe);
2493
2538
  }
2494
- return classPipeline(tag, props, className, recipe);
2539
+ return classPipeline(tag, props, className, recipe) || void 0;
2495
2540
  },
2496
2541
  resolveAria(tag, props) {
2497
2542
  return resolveAriaFn(tag, props);
@@ -2661,7 +2706,11 @@ function resolveHostState(bundle, props) {
2661
2706
  function diffAndApplyAttributes(host, state, prevPipelineAttrs, incomingProps) {
2662
2707
  const hasOwn2 = Object.hasOwn;
2663
2708
  const attrs = state.attributes;
2664
- host.className = state.className;
2709
+ if (state.className) {
2710
+ host.className = state.className;
2711
+ } else {
2712
+ host.removeAttribute("class");
2713
+ }
2665
2714
  for (const key of prevPipelineAttrs) {
2666
2715
  if (!hasOwn2(attrs, key)) host.removeAttribute(key);
2667
2716
  }