praxis-kit 6.0.0 → 6.1.1

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.
@@ -156,7 +156,7 @@ interface BaseClassOptions {
156
156
  baseClassName?: ClassName;
157
157
  }
158
158
 
159
- type ClassPipelineFn = (tag: unknown, props: AnyRecord, className?: ClassName, recipe?: string) => string;
159
+ type ClassPipelineFn = (tag: unknown, props: AnyRecord, className?: ClassName, recipe?: string) => string | undefined;
160
160
 
161
161
  interface RecipeOptions<TVariants extends VariantMap = VariantMap> {
162
162
  recipeMap?: Record<string, RecipeTarget<TVariants>>;
@@ -234,7 +234,9 @@ type AriaInvalidWithoutFix<M extends string = string> = AriaInvalidBase<M> & {
234
234
  type AriaInvalidResult<M extends string = string> = AriaInvalidWithFix<M> | AriaInvalidWithoutFix<M>;
235
235
  type AriaResult = ValidResult | AriaInvalidResult;
236
236
 
237
- type AriaRule<C extends AriaContext = AriaContext> = (context: C) => readonly AriaResult[];
237
+ type AriaRule<C extends AriaContext = AriaContext> = ((context: C) => readonly AriaResult[]) & {
238
+ readonly readsProps?: readonly string[];
239
+ };
238
240
 
239
241
  type PropNormalizer = (props: Readonly<AnyRecord & IntrinsicProps>) => Partial<AnyRecord & IntrinsicProps>;
240
242
 
@@ -1,7 +1,20 @@
1
1
  // ../../lib/primitive/src/guards/children/component-id.ts
2
2
  var COMPONENT_DEFAULT_TAG = /* @__PURE__ */ Symbol.for("praxis.component-default-tag");
3
3
 
4
- // ../../lib/primitive/src/utils/is-object.ts
4
+ // ../../lib/primitive/src/guards/foundational/is-defined.ts
5
+ function isUndefined(value) {
6
+ return value === void 0;
7
+ }
8
+
9
+ // ../../lib/primitive/src/guards/foundational/is-null.ts
10
+ function isNull(value) {
11
+ return value === null;
12
+ }
13
+ function isNonNull(value) {
14
+ return value != null;
15
+ }
16
+
17
+ // ../../lib/primitive/src/utils/type-guards.ts
5
18
  function isObject(value, excludeArrays = false) {
6
19
  if (value === null || typeof value !== "object") return false;
7
20
  return excludeArrays ? !Array.isArray(value) : true;
@@ -13,16 +26,6 @@ function isNumber(value) {
13
26
  return typeof value === "number";
14
27
  }
15
28
 
16
- // ../../lib/primitive/src/guards/foundational/is-defined.ts
17
- function isUndefined(value) {
18
- return value === void 0;
19
- }
20
-
21
- // ../../lib/primitive/src/guards/foundational/is-null.ts
22
- function isNull(value) {
23
- return value === null;
24
- }
25
-
26
29
  // ../../lib/primitive/src/guards/children/is-tag.ts
27
30
  function getAsProp(child) {
28
31
  if (!isObject(child) || !("props" in child)) return void 0;
@@ -639,6 +642,45 @@ var iterate = Object.freeze({
639
642
  values
640
643
  });
641
644
 
645
+ // ../../lib/primitive/src/utils/lru-cache.ts
646
+ var LRUCache = class {
647
+ #maxSize;
648
+ #store = /* @__PURE__ */ new Map();
649
+ constructor(maxSize) {
650
+ if (!Number.isInteger(maxSize) || maxSize < 1) {
651
+ throw new RangeError("LRUCache maxSize must be a positive integer.");
652
+ }
653
+ this.#maxSize = maxSize;
654
+ }
655
+ get(key) {
656
+ if (!this.#store.has(key)) return void 0;
657
+ const value = this.#store.get(key);
658
+ this.#store.delete(key);
659
+ this.#store.set(key, value);
660
+ return value;
661
+ }
662
+ set(key, value) {
663
+ this.#store.delete(key);
664
+ this.#store.set(key, value);
665
+ if (this.#store.size > this.#maxSize) {
666
+ const lru = this.#store.keys().next().value;
667
+ if (lru !== void 0) this.#store.delete(lru);
668
+ }
669
+ }
670
+ has(key) {
671
+ return this.#store.has(key);
672
+ }
673
+ delete(key) {
674
+ return this.#store.delete(key);
675
+ }
676
+ get size() {
677
+ return this.#store.size;
678
+ }
679
+ clear() {
680
+ this.#store.clear();
681
+ }
682
+ };
683
+
642
684
  // ../../lib/primitive/src/utils/merge-props.ts
643
685
  function mergeProps(defaultProps, props) {
644
686
  return {
@@ -647,28 +689,6 @@ function mergeProps(defaultProps, props) {
647
689
  };
648
690
  }
649
691
 
650
- // ../../lib/contract/src/strict/invariant-base.ts
651
- var InvariantBase = class {
652
- diagnostics;
653
- constructor(diagnostics) {
654
- this.diagnostics = diagnostics;
655
- }
656
- get active() {
657
- return this.diagnostics.active;
658
- }
659
- violate(input) {
660
- this.diagnostics.error(input);
661
- }
662
- // Always caps at warn — never throws. ARIA 'warning' violations route here
663
- // so they surface even in strict='throw' mode without aborting a render.
664
- warn(input) {
665
- this.diagnostics.warn(input);
666
- }
667
- invariant(condition, input) {
668
- if (!condition) this.violate(input);
669
- }
670
- };
671
-
672
692
  // ../../lib/contract/src/diagnostics/aria.ts
673
693
  import { DiagnosticCategory, DiagnosticCode } from "../_shared/diagnostics.js";
674
694
  var AriaDiagnostics = {
@@ -1025,8 +1045,30 @@ var SlotDiagnostics = {
1025
1045
  }
1026
1046
  };
1027
1047
 
1048
+ // ../../lib/contract/src/strict/invariant-base.ts
1049
+ var InvariantBase = class {
1050
+ diagnostics;
1051
+ constructor(diagnostics) {
1052
+ this.diagnostics = diagnostics;
1053
+ }
1054
+ get active() {
1055
+ return this.diagnostics.active;
1056
+ }
1057
+ violate(input) {
1058
+ this.diagnostics.error(input);
1059
+ }
1060
+ // Always caps at warn — never throws. ARIA 'warning' violations route here
1061
+ // so they surface even in strict='throw' mode without aborting a render.
1062
+ warn(input) {
1063
+ this.diagnostics.warn(input);
1064
+ }
1065
+ invariant(condition, input) {
1066
+ if (!condition) this.violate(input);
1067
+ }
1068
+ };
1069
+
1028
1070
  // ../../lib/contract/src/aria/polymorphic-validator.ts
1029
- var VALID = [{ valid: true }];
1071
+ var NO_VIOLATIONS = [{ valid: true }];
1030
1072
  function isIntrinsicTag(tag) {
1031
1073
  return isString(tag);
1032
1074
  }
@@ -1036,8 +1078,7 @@ function omitProp(obj, key) {
1036
1078
  }
1037
1079
  var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1038
1080
  #extraRules;
1039
- #planCache = /* @__PURE__ */ new Map();
1040
- static #MAX_CACHE = 100;
1081
+ #planCache = new LRUCache(100);
1041
1082
  // Memoized AriaFix objects keyed by attribute name — the ARIA attribute set is
1042
1083
  // finite so this Map is bounded and avoids recreating closures on every cache miss.
1043
1084
  static #removeAttributeFixCache = /* @__PURE__ */ new Map();
@@ -1069,17 +1110,20 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1069
1110
  static #deriveContext(tag, props) {
1070
1111
  if (!isIntrinsicTag(tag)) return { proceed: false, result: { props, violations: [] } };
1071
1112
  const implicitRole = getImplicitRole(tag, props);
1072
- const hasRole2 = implicitRole != null || isString(props.role) && props.role.length > 0;
1113
+ const hasRole2 = isNonNull(implicitRole) || isString(props.role) && props.role.length > 0;
1073
1114
  if (!hasRole2) return { proceed: false, result: { props, violations: [] } };
1074
1115
  const normalized = _AriaPolicyEngine.#normalizeEmptyRole(tag, props);
1075
- if (normalized.normalized) return { proceed: false, result: normalized.result };
1076
- const effectiveRole = props.role ?? implicitRole;
1116
+ const workingProps = normalized.normalized ? normalized.result.props : props;
1117
+ const preExistingViolations = normalized.normalized ? normalized.result.violations : [];
1118
+ const effectiveRole = workingProps.role ?? implicitRole;
1077
1119
  return {
1078
1120
  proceed: true,
1079
1121
  tag,
1080
1122
  implicitRole,
1081
1123
  effectiveRole,
1082
- context: { tag, props, implicitRole, effectiveRole }
1124
+ props: workingProps,
1125
+ preExistingViolations,
1126
+ context: { tag, props: workingProps, implicitRole, effectiveRole }
1083
1127
  };
1084
1128
  }
1085
1129
  static #runRules(rules, context) {
@@ -1094,7 +1138,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1094
1138
  } = context;
1095
1139
  const { message, attribute, severity } = result;
1096
1140
  const resolvedMessage = message ?? result.diagnostic?.message;
1097
- const fallbackDiag = resolvedMessage == null ? AriaDiagnostics.invalidRole(role, tag) : void 0;
1141
+ const fallbackDiag = isNonNull(resolvedMessage) ? void 0 : AriaDiagnostics.invalidRole(role, tag);
1098
1142
  violations.push({
1099
1143
  message: resolvedMessage ?? fallbackDiag.message,
1100
1144
  tag,
@@ -1102,8 +1146,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1102
1146
  attribute,
1103
1147
  severity,
1104
1148
  phase: "evaluate",
1105
- ...result.diagnostic != null && { diagnostic: result.diagnostic },
1106
- ...fallbackDiag != null && { diagnostic: fallbackDiag }
1149
+ ...isNonNull(result.diagnostic) && { diagnostic: result.diagnostic },
1150
+ ...isNonNull(fallbackDiag) && { diagnostic: fallbackDiag }
1107
1151
  });
1108
1152
  if (result.fixable) fixes.push(result.fix);
1109
1153
  });
@@ -1111,7 +1155,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1111
1155
  return { violations, fixes };
1112
1156
  }
1113
1157
  static #getRules(context) {
1114
- if (_AriaPolicyEngine.#hasRole(context.props) || context.effectiveRole != null && _AriaPolicyEngine.#LIVE_REGION_ROLES.has(context.effectiveRole)) {
1158
+ if (_AriaPolicyEngine.#hasRole(context.props) || isNonNull(context.effectiveRole) && _AriaPolicyEngine.#LIVE_REGION_ROLES.has(context.effectiveRole)) {
1115
1159
  return _AriaPolicyEngine.#pipeline;
1116
1160
  }
1117
1161
  return _AriaPolicyEngine.#implicitOnlyRules;
@@ -1119,24 +1163,36 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1119
1163
  static evaluate(tag, props) {
1120
1164
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1121
1165
  if (!derived.proceed) return derived.result;
1122
- const { tag: narrowedTag, implicitRole, context } = derived;
1166
+ const {
1167
+ tag: narrowedTag,
1168
+ implicitRole,
1169
+ context,
1170
+ props: workingProps,
1171
+ preExistingViolations
1172
+ } = derived;
1123
1173
  const { violations, fixes } = _AriaPolicyEngine.#runRules(
1124
1174
  _AriaPolicyEngine.#getRules(context),
1125
1175
  context
1126
1176
  );
1127
- const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, props, fixes);
1128
- return { props: next, violations };
1177
+ const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1178
+ return { props: next, violations: [...preExistingViolations, ...violations] };
1129
1179
  }
1130
1180
  static #evaluateWithRules(tag, props, extraRules) {
1131
1181
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1132
1182
  if (!derived.proceed) return derived.result;
1133
- const { tag: narrowedTag, implicitRole, context } = derived;
1183
+ const {
1184
+ tag: narrowedTag,
1185
+ implicitRole,
1186
+ context,
1187
+ props: workingProps,
1188
+ preExistingViolations
1189
+ } = derived;
1134
1190
  const { violations, fixes } = _AriaPolicyEngine.#runRules(
1135
1191
  [..._AriaPolicyEngine.#getRules(context), ...extraRules],
1136
1192
  context
1137
1193
  );
1138
- const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, props, fixes);
1139
- return { props: next, violations };
1194
+ const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1195
+ return { props: next, violations: [...preExistingViolations, ...violations] };
1140
1196
  }
1141
1197
  report(violations) {
1142
1198
  iterate.forEach(violations, (v) => {
@@ -1145,12 +1201,12 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1145
1201
  else this.warn(d);
1146
1202
  });
1147
1203
  }
1148
- // Cache key covers only the aria-relevant subset of props (tag + role + aria-* attrs).
1149
- // Non-aria props (className, onClick, etc.) do not affect ARIA decisions and are excluded
1150
- // so cache hits survive re-renders that only change non-aria props.
1151
- // Note: #extraRules are NOT included in the key each engine instance has its own Map,
1152
- // so two engines with different rules never share cache entries. If caching ever becomes
1153
- // static/shared, rule identity would need to be folded into the key.
1204
+ // Cache key covers only the aria-relevant subset of props (tag + role + aria-* attrs)
1205
+ // exactly what the built-in pipeline reads. Non-aria props (className, onClick, etc.) do
1206
+ // not affect built-in ARIA decisions and are excluded so cache hits survive re-renders
1207
+ // that only change non-aria props. This key alone is unsound for #extraRules, which may
1208
+ // read arbitrary props outside this set validate() extends it with #extraRulesKeySuffix,
1209
+ // or bypasses the cache, to account for that.
1154
1210
  static #createPlanKey(tag, props) {
1155
1211
  if (!isIntrinsicTag(tag)) return null;
1156
1212
  const parts = [tag];
@@ -1166,6 +1222,23 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1166
1222
  if (ariaEntries.length > 0) parts.push(...ariaEntries.sort());
1167
1223
  return parts.join("|");
1168
1224
  }
1225
+ // Extends the base cache key with the props each extra rule declares it reads (`readsProps`).
1226
+ // Returns null — meaning "don't cache" — if any extra rule omits `readsProps` (it may read
1227
+ // arbitrary props the key can't account for) or if a declared prop's value isn't a primitive
1228
+ // (object/array identity isn't stably representable in a string key).
1229
+ static #extraRulesKeySuffix(extraRules, props) {
1230
+ const parts = [];
1231
+ for (const rule of extraRules) {
1232
+ const readsProps = rule.readsProps;
1233
+ if (!isNonNull(readsProps)) return null;
1234
+ for (const propKey of readsProps) {
1235
+ const v = props[propKey];
1236
+ if (v !== void 0 && !isString(v) && !isNumber(v) && typeof v !== "boolean") return null;
1237
+ parts.push(`x:${propKey}:${String(v)}`);
1238
+ }
1239
+ }
1240
+ return parts.sort().join("|");
1241
+ }
1169
1242
  static #computePlan(inputProps, resultProps) {
1170
1243
  const removals = /* @__PURE__ */ new Set();
1171
1244
  const updates = {};
@@ -1189,12 +1262,15 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1189
1262
  return next;
1190
1263
  }
1191
1264
  validate(tag, props) {
1192
- const key = _AriaPolicyEngine.#createPlanKey(tag, props);
1265
+ const baseKey = _AriaPolicyEngine.#createPlanKey(tag, props);
1266
+ let key = baseKey;
1267
+ if (this.#extraRules.length > 0) {
1268
+ const suffix = _AriaPolicyEngine.#extraRulesKeySuffix(this.#extraRules, props);
1269
+ key = isNonNull(baseKey) && isNonNull(suffix) ? `${baseKey}|${suffix}` : null;
1270
+ }
1193
1271
  if (!isNull(key)) {
1194
1272
  const cached = this.#planCache.get(key);
1195
1273
  if (cached !== void 0) {
1196
- this.#planCache.delete(key);
1197
- this.#planCache.set(key, cached);
1198
1274
  if (cached.violations.length > 0) this.report(cached.violations);
1199
1275
  return {
1200
1276
  props: _AriaPolicyEngine.#applyPlan(props, cached.removals, cached.updates),
@@ -1211,10 +1287,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1211
1287
  );
1212
1288
  const plan = { removals, updates, violations: result.violations };
1213
1289
  this.#planCache.set(key, plan);
1214
- if (this.#planCache.size > _AriaPolicyEngine.#MAX_CACHE) {
1215
- const lru = this.#planCache.keys().next().value;
1216
- if (lru !== void 0) this.#planCache.delete(lru);
1217
- }
1218
1290
  }
1219
1291
  return result;
1220
1292
  }
@@ -1285,7 +1357,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1285
1357
  implicitRole
1286
1358
  }) {
1287
1359
  const role = props.role;
1288
- if (!implicitRole || !role || role === implicitRole) return VALID;
1360
+ if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS;
1289
1361
  if (isStrongImplicitRole(tag) && role === "region") {
1290
1362
  return [
1291
1363
  {
@@ -1297,11 +1369,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1297
1369
  }
1298
1370
  ];
1299
1371
  }
1300
- return VALID;
1372
+ return NO_VIOLATIONS;
1301
1373
  }
1302
1374
  static #checkRedundantRole({ tag, props, implicitRole }) {
1303
1375
  const role = props.role;
1304
- if (!implicitRole || !role || role !== implicitRole) return VALID;
1376
+ if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS;
1305
1377
  return [
1306
1378
  {
1307
1379
  valid: false,
@@ -1314,8 +1386,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1314
1386
  }
1315
1387
  static #checkStandaloneRegion({ tag, props, implicitRole }) {
1316
1388
  const role = props.role;
1317
- if (role !== "region") return VALID;
1318
- if (!isStandaloneTag(tag)) return VALID;
1389
+ if (role !== "region") return NO_VIOLATIONS;
1390
+ if (!isStandaloneTag(tag)) return NO_VIOLATIONS;
1319
1391
  return [
1320
1392
  {
1321
1393
  valid: false,
@@ -1331,7 +1403,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1331
1403
  props,
1332
1404
  effectiveRole
1333
1405
  }) {
1334
- if (effectiveRole === "none" || effectiveRole === "presentation") return VALID;
1406
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1335
1407
  const results = [];
1336
1408
  iterate.forEachEntry(props, (key) => {
1337
1409
  if (!key.startsWith("aria-")) return;
@@ -1449,12 +1521,12 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1449
1521
  }
1450
1522
  }
1451
1523
  static #checkAriaAttributeValues({ props, effectiveRole }) {
1452
- if (effectiveRole === "none" || effectiveRole === "presentation") return VALID;
1524
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1453
1525
  const results = [];
1454
1526
  iterate.forEachEntry(props, (key, value) => {
1455
1527
  if (!key.startsWith("aria-")) return;
1456
1528
  const type = _AriaPolicyEngine.#ARIA_VALUE_TYPES.get(key);
1457
- if (type == null) return;
1529
+ if (!isNonNull(type)) return;
1458
1530
  if (_AriaPolicyEngine.#isValidAriaValue(value, type)) return;
1459
1531
  results.push({
1460
1532
  valid: false,
@@ -1485,13 +1557,13 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1485
1557
  props,
1486
1558
  effectiveRole
1487
1559
  }) {
1488
- if (effectiveRole === "none" || effectiveRole === "presentation") return VALID;
1560
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1489
1561
  const implicitLevel = _AriaPolicyEngine.#HEADING_IMPLICIT_LEVELS.get(tag);
1490
- if (implicitLevel == null) return VALID;
1562
+ if (!isNonNull(implicitLevel)) return NO_VIOLATIONS;
1491
1563
  const raw = props["aria-level"];
1492
- if (raw == null) return VALID;
1564
+ if (!isNonNull(raw)) return NO_VIOLATIONS;
1493
1565
  const n = typeof raw === "number" ? raw : typeof raw === "string" ? parseInt(raw, 10) : NaN;
1494
- if (!Number.isFinite(n) || n !== implicitLevel) return VALID;
1566
+ if (!Number.isFinite(n) || n !== implicitLevel) return NO_VIOLATIONS;
1495
1567
  return [
1496
1568
  {
1497
1569
  valid: false,
@@ -1514,9 +1586,10 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1514
1586
  props,
1515
1587
  effectiveRole
1516
1588
  }) {
1517
- if (!effectiveRole || !_AriaPolicyEngine.#NAME_REQUIRED_ROLES.has(effectiveRole)) return VALID;
1518
- if ("aria-label" in props || "aria-labelledby" in props) return VALID;
1519
- if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return VALID;
1589
+ if (!effectiveRole || !_AriaPolicyEngine.#NAME_REQUIRED_ROLES.has(effectiveRole))
1590
+ return NO_VIOLATIONS;
1591
+ if ("aria-label" in props || "aria-labelledby" in props) return NO_VIOLATIONS;
1592
+ if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return NO_VIOLATIONS;
1520
1593
  return [
1521
1594
  {
1522
1595
  valid: false,
@@ -1539,9 +1612,9 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1539
1612
  props,
1540
1613
  effectiveRole
1541
1614
  }) {
1542
- if (!effectiveRole) return VALID;
1615
+ if (!effectiveRole) return NO_VIOLATIONS;
1543
1616
  const required = _AriaPolicyEngine.#REQUIRED_PROPERTIES.get(effectiveRole);
1544
- if (required == null) return VALID;
1617
+ if (!isNonNull(required)) return NO_VIOLATIONS;
1545
1618
  const results = [];
1546
1619
  iterate.forEach(required, (attr) => {
1547
1620
  if (attr in props) return;
@@ -1565,12 +1638,12 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1565
1638
  ]);
1566
1639
  // WAI-ARIA 1.2 §6.6: aria-hidden="true" must not be placed on focusable elements.
1567
1640
  static #checkAriaHiddenOnFocusable({ tag, props }) {
1568
- if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return VALID;
1641
+ if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return NO_VIOLATIONS;
1569
1642
  const isInteractive = _AriaPolicyEngine.#INTERACTIVE_TAGS.has(tag);
1570
1643
  if (!isInteractive) {
1571
1644
  const tabindex = props.tabindex;
1572
1645
  const n = typeof tabindex === "number" ? tabindex : typeof tabindex === "string" ? parseInt(tabindex, 10) : NaN;
1573
- if (!Number.isFinite(n) || n < 0) return VALID;
1646
+ if (!Number.isFinite(n) || n < 0) return NO_VIOLATIONS;
1574
1647
  }
1575
1648
  return [
1576
1649
  {
@@ -1589,7 +1662,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1589
1662
  props,
1590
1663
  effectiveRole
1591
1664
  }) {
1592
- if (effectiveRole !== "none" && effectiveRole !== "presentation") return VALID;
1665
+ if (effectiveRole !== "none" && effectiveRole !== "presentation") return NO_VIOLATIONS;
1593
1666
  const results = [];
1594
1667
  iterate.forEachEntry(props, (key) => {
1595
1668
  if (!key.startsWith("aria-")) return;
@@ -1613,10 +1686,10 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1613
1686
  ["timer", "off"]
1614
1687
  ]);
1615
1688
  static #checkMissingLiveRegion({ effectiveRole, props }) {
1616
- if (!effectiveRole) return VALID;
1689
+ if (!effectiveRole) return NO_VIOLATIONS;
1617
1690
  const impliedLive = _AriaPolicyEngine.#LIVE_REGION_ROLES.get(effectiveRole);
1618
- if (!impliedLive) return VALID;
1619
- if ("aria-live" in props) return VALID;
1691
+ if (!impliedLive) return NO_VIOLATIONS;
1692
+ if ("aria-live" in props) return NO_VIOLATIONS;
1620
1693
  const injectLive = {
1621
1694
  kind: `injectLive:${effectiveRole}`,
1622
1695
  apply: (ctx) => ({
@@ -1636,8 +1709,9 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1636
1709
  ];
1637
1710
  }
1638
1711
  static #checkMissingAtomic({ effectiveRole, props }) {
1639
- if (!effectiveRole || !_AriaPolicyEngine.#LIVE_REGION_ROLES.has(effectiveRole)) return VALID;
1640
- if ("aria-atomic" in props) return VALID;
1712
+ if (!effectiveRole || !_AriaPolicyEngine.#LIVE_REGION_ROLES.has(effectiveRole))
1713
+ return NO_VIOLATIONS;
1714
+ if ("aria-atomic" in props) return NO_VIOLATIONS;
1641
1715
  return [
1642
1716
  {
1643
1717
  valid: false,
@@ -1661,8 +1735,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1661
1735
  };
1662
1736
  static #checkInvalidAriaRelevant({ props }) {
1663
1737
  const relevant = props["aria-relevant"];
1664
- if (relevant === void 0) return VALID;
1665
- if (typeof relevant !== "string") return VALID;
1738
+ if (relevant === void 0) return NO_VIOLATIONS;
1739
+ if (typeof relevant !== "string") return NO_VIOLATIONS;
1666
1740
  const tokens = relevant.trim().split(/\s+/);
1667
1741
  const invalid = tokens.filter((t) => !_AriaPolicyEngine.#VALID_RELEVANT_TOKENS.has(t));
1668
1742
  if (invalid.length > 0) {
@@ -1689,7 +1763,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1689
1763
  }
1690
1764
  ];
1691
1765
  }
1692
- return VALID;
1766
+ return NO_VIOLATIONS;
1693
1767
  }
1694
1768
  };
1695
1769
 
@@ -2289,7 +2363,7 @@ function cva2(base, config) {
2289
2363
  // ../../lib/styling/src/static-class-resolver.ts
2290
2364
  var StaticClassResolver = class {
2291
2365
  #baseClass;
2292
- #cache = /* @__PURE__ */ new Map();
2366
+ #cache = new LRUCache(200);
2293
2367
  #resolveTag;
2294
2368
  constructor(baseClass, tagMap) {
2295
2369
  this.#baseClass = Array.isArray(baseClass) ? baseClass.join(" ") : baseClass;
@@ -2303,17 +2377,9 @@ var StaticClassResolver = class {
2303
2377
  resolve(tag, skipTagMap = false) {
2304
2378
  if (typeof tag !== "string" || skipTagMap) return this.#baseClass;
2305
2379
  const cached = this.#cache.get(tag);
2306
- if (cached !== void 0) {
2307
- this.#cache.delete(tag);
2308
- this.#cache.set(tag, cached);
2309
- return cached;
2310
- }
2380
+ if (cached !== void 0) return cached;
2311
2381
  const result = this.#resolveTag(tag);
2312
2382
  this.#cache.set(tag, result);
2313
- if (this.#cache.size > 200) {
2314
- const lru = this.#cache.keys().next().value;
2315
- if (lru !== void 0) this.#cache.delete(lru);
2316
- }
2317
2383
  return result;
2318
2384
  }
2319
2385
  };
@@ -2324,7 +2390,7 @@ var VariantClassResolver = class _VariantClassResolver {
2324
2390
  #recipeMap;
2325
2391
  #variantKeys;
2326
2392
  #precomputedClasses;
2327
- #cache = /* @__PURE__ */ new Map();
2393
+ #cache = new LRUCache(1e3);
2328
2394
  constructor(cvaFn, recipeMap, variantKeys, precomputedClasses) {
2329
2395
  this.#cvaFn = cvaFn ?? null;
2330
2396
  this.#recipeMap = Object.freeze(recipeMap ?? {});
@@ -2339,17 +2405,9 @@ var VariantClassResolver = class _VariantClassResolver {
2339
2405
  if (precomputed !== void 0) return precomputed;
2340
2406
  }
2341
2407
  const cached = this.#cache.get(cacheKey);
2342
- if (cached !== void 0) {
2343
- this.#cache.delete(cacheKey);
2344
- this.#cache.set(cacheKey, cached);
2345
- return cached;
2346
- }
2408
+ if (cached !== void 0) return cached;
2347
2409
  const result = this.#compute(props, recipe);
2348
2410
  this.#cache.set(cacheKey, result);
2349
- if (this.#cache.size > 1e3) {
2350
- const lru = this.#cache.keys().next().value;
2351
- if (lru !== void 0) this.#cache.delete(lru);
2352
- }
2353
2411
  return result;
2354
2412
  }
2355
2413
  #compute(props, recipe) {
@@ -2406,7 +2464,7 @@ function createClassPipeline(resolved) {
2406
2464
  const staticClasses = staticResolver.resolve(tag, recipe !== void 0);
2407
2465
  const variantClasses = variantResolver.resolve({ props, recipe });
2408
2466
  if (!className)
2409
- return staticClasses && variantClasses ? `${staticClasses} ${variantClasses}` : staticClasses || variantClasses;
2467
+ return (staticClasses && variantClasses ? `${staticClasses} ${variantClasses}` : staticClasses || variantClasses) || void 0;
2410
2468
  return cn(staticClasses, variantClasses, className);
2411
2469
  };
2412
2470
  }
@@ -2617,7 +2675,7 @@ function createPolymorphic2(options = {}) {
2617
2675
  if (process.env.NODE_ENV !== "production") {
2618
2676
  validateRenderProps(resolved.diagnostics, resolved, props, recipe);
2619
2677
  }
2620
- return classPipeline(tag, props, className, recipe);
2678
+ return classPipeline(tag, props, className, recipe) || void 0;
2621
2679
  },
2622
2680
  resolveAria(tag, props) {
2623
2681
  return resolveAriaFn(tag, props);
@@ -50,7 +50,7 @@
50
50
 
51
51
  function buildDomProps(
52
52
  props: UnknownProps,
53
- classStr: string,
53
+ classStr: string | undefined,
54
54
  tag: ElementType,
55
55
  ): ResolvedAttributes {
56
56
  const { role, style, ...r } = normalizeEventKeys(props)
@@ -67,7 +67,7 @@
67
67
  return bundle.runtime.resolveAria(tag, ep).props as ResolvedAttributes
68
68
  }
69
69
 
70
- function buildSlotProps(props: UnknownProps, classStr: string): UnknownProps {
70
+ function buildSlotProps(props: UnknownProps, classStr: string | undefined): UnknownProps {
71
71
  const { role, ...r } = props
72
72
  return {
73
73
  ...r,
@@ -159,7 +159,7 @@ interface BaseClassOptions {
159
159
  baseClassName?: ClassName;
160
160
  }
161
161
 
162
- type ClassPipelineFn = (tag: unknown, props: AnyRecord, className?: ClassName, recipe?: string) => string;
162
+ type ClassPipelineFn = (tag: unknown, props: AnyRecord, className?: ClassName, recipe?: string) => string | undefined;
163
163
 
164
164
  interface RecipeOptions<TVariants extends VariantMap = VariantMap> {
165
165
  recipeMap?: Record<string, RecipeTarget<TVariants>>;
@@ -238,7 +238,9 @@ type AriaInvalidWithoutFix<M extends string = string> = AriaInvalidBase<M> & {
238
238
  type AriaInvalidResult<M extends string = string> = AriaInvalidWithFix<M> | AriaInvalidWithoutFix<M>;
239
239
  type AriaResult = ValidResult | AriaInvalidResult;
240
240
 
241
- type AriaRule<C extends AriaContext = AriaContext> = (context: C) => readonly AriaResult[];
241
+ type AriaRule<C extends AriaContext = AriaContext> = ((context: C) => readonly AriaResult[]) & {
242
+ readonly readsProps?: readonly string[];
243
+ };
242
244
 
243
245
  type PropNormalizer = (props: Readonly<AnyRecord & IntrinsicProps>) => Partial<AnyRecord & IntrinsicProps>;
244
246
 
@@ -318,7 +320,7 @@ type ResolveAriaFn = <P extends IntrinsicProps>(tag: ElementType, props: P) => {
318
320
  props: P;
319
321
  };
320
322
 
321
- type ResolveClassNameFn<Props extends AnyRecord, TSlot extends string = never> = (tag: ElementType, props: Props, className?: ClassName, recipe?: TSlot) => string;
323
+ type ResolveClassNameFn<Props extends AnyRecord, TSlot extends string = never> = (tag: ElementType, props: Props, className?: ClassName, recipe?: TSlot) => string | undefined;
322
324
 
323
325
  type ResolvePropsFn<Props extends AnyRecord> = <P extends Partial<Props>>(props: P) => Simplify<Omit<Props, keyof P> & P>;
324
326