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.
@@ -12,8 +12,11 @@ function isUndefined(value) {
12
12
  function isNull(value) {
13
13
  return value === null;
14
14
  }
15
+ function isNonNull(value) {
16
+ return value != null;
17
+ }
15
18
 
16
- // ../../lib/primitive/src/utils/is-object.ts
19
+ // ../../lib/primitive/src/utils/type-guards.ts
17
20
  function isObject(value, excludeArrays = false) {
18
21
  if (value === null || typeof value !== "object") return false;
19
22
  return excludeArrays ? !Array.isArray(value) : true;
@@ -24,16 +27,11 @@ function isString(value) {
24
27
  function isNumber(value) {
25
28
  return typeof value === "number";
26
29
  }
27
-
28
- // ../../lib/primitive/src/merge/constants.ts
29
- var EVENT_HANDLER_RE = /^on[A-Z]/;
30
-
31
- // ../../lib/primitive/src/merge/predicates.ts
32
30
  function isFunction(value) {
33
31
  return typeof value === "function";
34
32
  }
35
33
  function isPlainObject(value) {
36
- if (!isObject(value)) return false;
34
+ if (!isObject(value, true)) return false;
37
35
  const proto = Object.getPrototypeOf(value);
38
36
  return proto === Object.prototype || proto === null;
39
37
  }
@@ -420,6 +418,9 @@ function makeResolveTag(defaultTag) {
420
418
  };
421
419
  }
422
420
 
421
+ // ../../lib/primitive/src/merge/constants.ts
422
+ var EVENT_HANDLER_RE = /^on[A-Z]/;
423
+
423
424
  // ../../lib/primitive/src/rule/rule-brand.ts
424
425
  var RULE_BRAND = /* @__PURE__ */ Symbol("praxis-kit/dynamic-rule");
425
426
 
@@ -614,14 +615,66 @@ var iterate = Object.freeze({
614
615
  values
615
616
  });
616
617
 
618
+ // ../../lib/primitive/src/utils/lazy.ts
619
+ function lazy(factory) {
620
+ let initialized = false;
621
+ let value;
622
+ return () => {
623
+ if (!initialized) {
624
+ value = factory();
625
+ initialized = true;
626
+ }
627
+ return value;
628
+ };
629
+ }
630
+
631
+ // ../../lib/primitive/src/utils/lru-cache.ts
632
+ var LRUCache = class {
633
+ #maxSize;
634
+ #store = /* @__PURE__ */ new Map();
635
+ constructor(maxSize) {
636
+ if (!Number.isInteger(maxSize) || maxSize < 1) {
637
+ throw new RangeError("LRUCache maxSize must be a positive integer.");
638
+ }
639
+ this.#maxSize = maxSize;
640
+ }
641
+ get(key) {
642
+ if (!this.#store.has(key)) return void 0;
643
+ const value = this.#store.get(key);
644
+ this.#store.delete(key);
645
+ this.#store.set(key, value);
646
+ return value;
647
+ }
648
+ set(key, value) {
649
+ this.#store.delete(key);
650
+ this.#store.set(key, value);
651
+ if (this.#store.size > this.#maxSize) {
652
+ const lru = this.#store.keys().next().value;
653
+ if (lru !== void 0) this.#store.delete(lru);
654
+ }
655
+ }
656
+ has(key) {
657
+ return this.#store.has(key);
658
+ }
659
+ delete(key) {
660
+ return this.#store.delete(key);
661
+ }
662
+ get size() {
663
+ return this.#store.size;
664
+ }
665
+ clear() {
666
+ this.#store.clear();
667
+ }
668
+ };
669
+
617
670
  // ../../lib/primitive/src/utils/merge-refs.ts
618
671
  function mergeRefsCore(...refs) {
619
- const active = refs.filter((r) => r != null);
620
- if (active.length === 0) return null;
621
- if (active.length === 1) return active[0];
672
+ const resolvedRefs = refs.filter((r) => r != null);
673
+ if (resolvedRefs.length === 0) return null;
674
+ if (resolvedRefs.length === 1) return resolvedRefs[0] ?? null;
622
675
  return (value) => {
623
- iterate.forEach(active, (ref) => {
624
- if (typeof ref === "function") {
676
+ iterate.forEach(resolvedRefs, (ref) => {
677
+ if (isFunction(ref)) {
625
678
  ref(value);
626
679
  } else {
627
680
  ref.current = value;
@@ -675,28 +728,6 @@ function isTag(...args) {
675
728
  return tag !== void 0 && set2.has(tag);
676
729
  }
677
730
 
678
- // ../../lib/contract/src/strict/invariant-base.ts
679
- var InvariantBase = class {
680
- diagnostics;
681
- constructor(diagnostics) {
682
- this.diagnostics = diagnostics;
683
- }
684
- get active() {
685
- return this.diagnostics.active;
686
- }
687
- violate(input) {
688
- this.diagnostics.error(input);
689
- }
690
- // Always caps at warn — never throws. ARIA 'warning' violations route here
691
- // so they surface even in strict='throw' mode without aborting a render.
692
- warn(input) {
693
- this.diagnostics.warn(input);
694
- }
695
- invariant(condition, input) {
696
- if (!condition) this.violate(input);
697
- }
698
- };
699
-
700
731
  // ../../lib/contract/src/diagnostics/aria.ts
701
732
  import { DiagnosticCategory, DiagnosticCode } from "./_shared/diagnostics.js";
702
733
  var AriaDiagnostics = {
@@ -1053,8 +1084,30 @@ var SlotDiagnostics = {
1053
1084
  }
1054
1085
  };
1055
1086
 
1087
+ // ../../lib/contract/src/strict/invariant-base.ts
1088
+ var InvariantBase = class {
1089
+ diagnostics;
1090
+ constructor(diagnostics) {
1091
+ this.diagnostics = diagnostics;
1092
+ }
1093
+ get active() {
1094
+ return this.diagnostics.active;
1095
+ }
1096
+ violate(input) {
1097
+ this.diagnostics.error(input);
1098
+ }
1099
+ // Always caps at warn — never throws. ARIA 'warning' violations route here
1100
+ // so they surface even in strict='throw' mode without aborting a render.
1101
+ warn(input) {
1102
+ this.diagnostics.warn(input);
1103
+ }
1104
+ invariant(condition, input) {
1105
+ if (!condition) this.violate(input);
1106
+ }
1107
+ };
1108
+
1056
1109
  // ../../lib/contract/src/aria/polymorphic-validator.ts
1057
- var VALID = [{ valid: true }];
1110
+ var NO_VIOLATIONS = [{ valid: true }];
1058
1111
  function isIntrinsicTag(tag) {
1059
1112
  return isString(tag);
1060
1113
  }
@@ -1064,8 +1117,7 @@ function omitProp(obj, key) {
1064
1117
  }
1065
1118
  var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1066
1119
  #extraRules;
1067
- #planCache = /* @__PURE__ */ new Map();
1068
- static #MAX_CACHE = 100;
1120
+ #planCache = new LRUCache(100);
1069
1121
  // Memoized AriaFix objects keyed by attribute name — the ARIA attribute set is
1070
1122
  // finite so this Map is bounded and avoids recreating closures on every cache miss.
1071
1123
  static #removeAttributeFixCache = /* @__PURE__ */ new Map();
@@ -1097,17 +1149,20 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1097
1149
  static #deriveContext(tag, props) {
1098
1150
  if (!isIntrinsicTag(tag)) return { proceed: false, result: { props, violations: [] } };
1099
1151
  const implicitRole = getImplicitRole(tag, props);
1100
- const hasRole2 = implicitRole != null || isString(props.role) && props.role.length > 0;
1152
+ const hasRole2 = isNonNull(implicitRole) || isString(props.role) && props.role.length > 0;
1101
1153
  if (!hasRole2) return { proceed: false, result: { props, violations: [] } };
1102
1154
  const normalized = _AriaPolicyEngine.#normalizeEmptyRole(tag, props);
1103
- if (normalized.normalized) return { proceed: false, result: normalized.result };
1104
- const effectiveRole = props.role ?? implicitRole;
1155
+ const workingProps = normalized.normalized ? normalized.result.props : props;
1156
+ const preExistingViolations = normalized.normalized ? normalized.result.violations : [];
1157
+ const effectiveRole = workingProps.role ?? implicitRole;
1105
1158
  return {
1106
1159
  proceed: true,
1107
1160
  tag,
1108
1161
  implicitRole,
1109
1162
  effectiveRole,
1110
- context: { tag, props, implicitRole, effectiveRole }
1163
+ props: workingProps,
1164
+ preExistingViolations,
1165
+ context: { tag, props: workingProps, implicitRole, effectiveRole }
1111
1166
  };
1112
1167
  }
1113
1168
  static #runRules(rules, context) {
@@ -1122,7 +1177,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1122
1177
  } = context;
1123
1178
  const { message, attribute, severity } = result;
1124
1179
  const resolvedMessage = message ?? result.diagnostic?.message;
1125
- const fallbackDiag = resolvedMessage == null ? AriaDiagnostics.invalidRole(role, tag) : void 0;
1180
+ const fallbackDiag = isNonNull(resolvedMessage) ? void 0 : AriaDiagnostics.invalidRole(role, tag);
1126
1181
  violations.push({
1127
1182
  message: resolvedMessage ?? fallbackDiag.message,
1128
1183
  tag,
@@ -1130,8 +1185,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1130
1185
  attribute,
1131
1186
  severity,
1132
1187
  phase: "evaluate",
1133
- ...result.diagnostic != null && { diagnostic: result.diagnostic },
1134
- ...fallbackDiag != null && { diagnostic: fallbackDiag }
1188
+ ...isNonNull(result.diagnostic) && { diagnostic: result.diagnostic },
1189
+ ...isNonNull(fallbackDiag) && { diagnostic: fallbackDiag }
1135
1190
  });
1136
1191
  if (result.fixable) fixes.push(result.fix);
1137
1192
  });
@@ -1139,7 +1194,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1139
1194
  return { violations, fixes };
1140
1195
  }
1141
1196
  static #getRules(context) {
1142
- if (_AriaPolicyEngine.#hasRole(context.props) || context.effectiveRole != null && _AriaPolicyEngine.#LIVE_REGION_ROLES.has(context.effectiveRole)) {
1197
+ if (_AriaPolicyEngine.#hasRole(context.props) || isNonNull(context.effectiveRole) && _AriaPolicyEngine.#LIVE_REGION_ROLES.has(context.effectiveRole)) {
1143
1198
  return _AriaPolicyEngine.#pipeline;
1144
1199
  }
1145
1200
  return _AriaPolicyEngine.#implicitOnlyRules;
@@ -1147,24 +1202,36 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1147
1202
  static evaluate(tag, props) {
1148
1203
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1149
1204
  if (!derived.proceed) return derived.result;
1150
- const { tag: narrowedTag, implicitRole, context } = derived;
1205
+ const {
1206
+ tag: narrowedTag,
1207
+ implicitRole,
1208
+ context,
1209
+ props: workingProps,
1210
+ preExistingViolations
1211
+ } = derived;
1151
1212
  const { violations, fixes } = _AriaPolicyEngine.#runRules(
1152
1213
  _AriaPolicyEngine.#getRules(context),
1153
1214
  context
1154
1215
  );
1155
- const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, props, fixes);
1156
- return { props: next, violations };
1216
+ const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1217
+ return { props: next, violations: [...preExistingViolations, ...violations] };
1157
1218
  }
1158
1219
  static #evaluateWithRules(tag, props, extraRules) {
1159
1220
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1160
1221
  if (!derived.proceed) return derived.result;
1161
- const { tag: narrowedTag, implicitRole, context } = derived;
1222
+ const {
1223
+ tag: narrowedTag,
1224
+ implicitRole,
1225
+ context,
1226
+ props: workingProps,
1227
+ preExistingViolations
1228
+ } = derived;
1162
1229
  const { violations, fixes } = _AriaPolicyEngine.#runRules(
1163
1230
  [..._AriaPolicyEngine.#getRules(context), ...extraRules],
1164
1231
  context
1165
1232
  );
1166
- const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, props, fixes);
1167
- return { props: next, violations };
1233
+ const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1234
+ return { props: next, violations: [...preExistingViolations, ...violations] };
1168
1235
  }
1169
1236
  report(violations) {
1170
1237
  iterate.forEach(violations, (v) => {
@@ -1173,12 +1240,12 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1173
1240
  else this.warn(d);
1174
1241
  });
1175
1242
  }
1176
- // Cache key covers only the aria-relevant subset of props (tag + role + aria-* attrs).
1177
- // Non-aria props (className, onClick, etc.) do not affect ARIA decisions and are excluded
1178
- // so cache hits survive re-renders that only change non-aria props.
1179
- // Note: #extraRules are NOT included in the key each engine instance has its own Map,
1180
- // so two engines with different rules never share cache entries. If caching ever becomes
1181
- // static/shared, rule identity would need to be folded into the key.
1243
+ // Cache key covers only the aria-relevant subset of props (tag + role + aria-* attrs)
1244
+ // exactly what the built-in pipeline reads. Non-aria props (className, onClick, etc.) do
1245
+ // not affect built-in ARIA decisions and are excluded so cache hits survive re-renders
1246
+ // that only change non-aria props. This key alone is unsound for #extraRules, which may
1247
+ // read arbitrary props outside this set validate() extends it with #extraRulesKeySuffix,
1248
+ // or bypasses the cache, to account for that.
1182
1249
  static #createPlanKey(tag, props) {
1183
1250
  if (!isIntrinsicTag(tag)) return null;
1184
1251
  const parts = [tag];
@@ -1194,6 +1261,23 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1194
1261
  if (ariaEntries.length > 0) parts.push(...ariaEntries.sort());
1195
1262
  return parts.join("|");
1196
1263
  }
1264
+ // Extends the base cache key with the props each extra rule declares it reads (`readsProps`).
1265
+ // Returns null — meaning "don't cache" — if any extra rule omits `readsProps` (it may read
1266
+ // arbitrary props the key can't account for) or if a declared prop's value isn't a primitive
1267
+ // (object/array identity isn't stably representable in a string key).
1268
+ static #extraRulesKeySuffix(extraRules, props) {
1269
+ const parts = [];
1270
+ for (const rule of extraRules) {
1271
+ const readsProps = rule.readsProps;
1272
+ if (!isNonNull(readsProps)) return null;
1273
+ for (const propKey of readsProps) {
1274
+ const v = props[propKey];
1275
+ if (v !== void 0 && !isString(v) && !isNumber(v) && typeof v !== "boolean") return null;
1276
+ parts.push(`x:${propKey}:${String(v)}`);
1277
+ }
1278
+ }
1279
+ return parts.sort().join("|");
1280
+ }
1197
1281
  static #computePlan(inputProps, resultProps) {
1198
1282
  const removals = /* @__PURE__ */ new Set();
1199
1283
  const updates = {};
@@ -1217,12 +1301,15 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1217
1301
  return next;
1218
1302
  }
1219
1303
  validate(tag, props) {
1220
- const key = _AriaPolicyEngine.#createPlanKey(tag, props);
1304
+ const baseKey = _AriaPolicyEngine.#createPlanKey(tag, props);
1305
+ let key = baseKey;
1306
+ if (this.#extraRules.length > 0) {
1307
+ const suffix = _AriaPolicyEngine.#extraRulesKeySuffix(this.#extraRules, props);
1308
+ key = isNonNull(baseKey) && isNonNull(suffix) ? `${baseKey}|${suffix}` : null;
1309
+ }
1221
1310
  if (!isNull(key)) {
1222
1311
  const cached = this.#planCache.get(key);
1223
1312
  if (cached !== void 0) {
1224
- this.#planCache.delete(key);
1225
- this.#planCache.set(key, cached);
1226
1313
  if (cached.violations.length > 0) this.report(cached.violations);
1227
1314
  return {
1228
1315
  props: _AriaPolicyEngine.#applyPlan(props, cached.removals, cached.updates),
@@ -1239,10 +1326,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1239
1326
  );
1240
1327
  const plan = { removals, updates, violations: result.violations };
1241
1328
  this.#planCache.set(key, plan);
1242
- if (this.#planCache.size > _AriaPolicyEngine.#MAX_CACHE) {
1243
- const lru = this.#planCache.keys().next().value;
1244
- if (lru !== void 0) this.#planCache.delete(lru);
1245
- }
1246
1329
  }
1247
1330
  return result;
1248
1331
  }
@@ -1313,7 +1396,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1313
1396
  implicitRole
1314
1397
  }) {
1315
1398
  const role = props.role;
1316
- if (!implicitRole || !role || role === implicitRole) return VALID;
1399
+ if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS;
1317
1400
  if (isStrongImplicitRole(tag) && role === "region") {
1318
1401
  return [
1319
1402
  {
@@ -1325,11 +1408,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1325
1408
  }
1326
1409
  ];
1327
1410
  }
1328
- return VALID;
1411
+ return NO_VIOLATIONS;
1329
1412
  }
1330
1413
  static #checkRedundantRole({ tag, props, implicitRole }) {
1331
1414
  const role = props.role;
1332
- if (!implicitRole || !role || role !== implicitRole) return VALID;
1415
+ if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS;
1333
1416
  return [
1334
1417
  {
1335
1418
  valid: false,
@@ -1342,8 +1425,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1342
1425
  }
1343
1426
  static #checkStandaloneRegion({ tag, props, implicitRole }) {
1344
1427
  const role = props.role;
1345
- if (role !== "region") return VALID;
1346
- if (!isStandaloneTag(tag)) return VALID;
1428
+ if (role !== "region") return NO_VIOLATIONS;
1429
+ if (!isStandaloneTag(tag)) return NO_VIOLATIONS;
1347
1430
  return [
1348
1431
  {
1349
1432
  valid: false,
@@ -1359,7 +1442,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1359
1442
  props,
1360
1443
  effectiveRole
1361
1444
  }) {
1362
- if (effectiveRole === "none" || effectiveRole === "presentation") return VALID;
1445
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1363
1446
  const results = [];
1364
1447
  iterate.forEachEntry(props, (key) => {
1365
1448
  if (!key.startsWith("aria-")) return;
@@ -1477,12 +1560,12 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1477
1560
  }
1478
1561
  }
1479
1562
  static #checkAriaAttributeValues({ props, effectiveRole }) {
1480
- if (effectiveRole === "none" || effectiveRole === "presentation") return VALID;
1563
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1481
1564
  const results = [];
1482
1565
  iterate.forEachEntry(props, (key, value) => {
1483
1566
  if (!key.startsWith("aria-")) return;
1484
1567
  const type = _AriaPolicyEngine.#ARIA_VALUE_TYPES.get(key);
1485
- if (type == null) return;
1568
+ if (!isNonNull(type)) return;
1486
1569
  if (_AriaPolicyEngine.#isValidAriaValue(value, type)) return;
1487
1570
  results.push({
1488
1571
  valid: false,
@@ -1513,13 +1596,13 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1513
1596
  props,
1514
1597
  effectiveRole
1515
1598
  }) {
1516
- if (effectiveRole === "none" || effectiveRole === "presentation") return VALID;
1599
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1517
1600
  const implicitLevel = _AriaPolicyEngine.#HEADING_IMPLICIT_LEVELS.get(tag);
1518
- if (implicitLevel == null) return VALID;
1601
+ if (!isNonNull(implicitLevel)) return NO_VIOLATIONS;
1519
1602
  const raw = props["aria-level"];
1520
- if (raw == null) return VALID;
1603
+ if (!isNonNull(raw)) return NO_VIOLATIONS;
1521
1604
  const n = typeof raw === "number" ? raw : typeof raw === "string" ? parseInt(raw, 10) : NaN;
1522
- if (!Number.isFinite(n) || n !== implicitLevel) return VALID;
1605
+ if (!Number.isFinite(n) || n !== implicitLevel) return NO_VIOLATIONS;
1523
1606
  return [
1524
1607
  {
1525
1608
  valid: false,
@@ -1542,9 +1625,10 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1542
1625
  props,
1543
1626
  effectiveRole
1544
1627
  }) {
1545
- if (!effectiveRole || !_AriaPolicyEngine.#NAME_REQUIRED_ROLES.has(effectiveRole)) return VALID;
1546
- if ("aria-label" in props || "aria-labelledby" in props) return VALID;
1547
- if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return VALID;
1628
+ if (!effectiveRole || !_AriaPolicyEngine.#NAME_REQUIRED_ROLES.has(effectiveRole))
1629
+ return NO_VIOLATIONS;
1630
+ if ("aria-label" in props || "aria-labelledby" in props) return NO_VIOLATIONS;
1631
+ if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return NO_VIOLATIONS;
1548
1632
  return [
1549
1633
  {
1550
1634
  valid: false,
@@ -1567,9 +1651,9 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1567
1651
  props,
1568
1652
  effectiveRole
1569
1653
  }) {
1570
- if (!effectiveRole) return VALID;
1654
+ if (!effectiveRole) return NO_VIOLATIONS;
1571
1655
  const required = _AriaPolicyEngine.#REQUIRED_PROPERTIES.get(effectiveRole);
1572
- if (required == null) return VALID;
1656
+ if (!isNonNull(required)) return NO_VIOLATIONS;
1573
1657
  const results = [];
1574
1658
  iterate.forEach(required, (attr) => {
1575
1659
  if (attr in props) return;
@@ -1593,12 +1677,12 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1593
1677
  ]);
1594
1678
  // WAI-ARIA 1.2 §6.6: aria-hidden="true" must not be placed on focusable elements.
1595
1679
  static #checkAriaHiddenOnFocusable({ tag, props }) {
1596
- if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return VALID;
1680
+ if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return NO_VIOLATIONS;
1597
1681
  const isInteractive = _AriaPolicyEngine.#INTERACTIVE_TAGS.has(tag);
1598
1682
  if (!isInteractive) {
1599
1683
  const tabindex = props.tabindex;
1600
1684
  const n = typeof tabindex === "number" ? tabindex : typeof tabindex === "string" ? parseInt(tabindex, 10) : NaN;
1601
- if (!Number.isFinite(n) || n < 0) return VALID;
1685
+ if (!Number.isFinite(n) || n < 0) return NO_VIOLATIONS;
1602
1686
  }
1603
1687
  return [
1604
1688
  {
@@ -1617,7 +1701,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1617
1701
  props,
1618
1702
  effectiveRole
1619
1703
  }) {
1620
- if (effectiveRole !== "none" && effectiveRole !== "presentation") return VALID;
1704
+ if (effectiveRole !== "none" && effectiveRole !== "presentation") return NO_VIOLATIONS;
1621
1705
  const results = [];
1622
1706
  iterate.forEachEntry(props, (key) => {
1623
1707
  if (!key.startsWith("aria-")) return;
@@ -1641,10 +1725,10 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1641
1725
  ["timer", "off"]
1642
1726
  ]);
1643
1727
  static #checkMissingLiveRegion({ effectiveRole, props }) {
1644
- if (!effectiveRole) return VALID;
1728
+ if (!effectiveRole) return NO_VIOLATIONS;
1645
1729
  const impliedLive = _AriaPolicyEngine.#LIVE_REGION_ROLES.get(effectiveRole);
1646
- if (!impliedLive) return VALID;
1647
- if ("aria-live" in props) return VALID;
1730
+ if (!impliedLive) return NO_VIOLATIONS;
1731
+ if ("aria-live" in props) return NO_VIOLATIONS;
1648
1732
  const injectLive = {
1649
1733
  kind: `injectLive:${effectiveRole}`,
1650
1734
  apply: (ctx) => ({
@@ -1664,8 +1748,9 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1664
1748
  ];
1665
1749
  }
1666
1750
  static #checkMissingAtomic({ effectiveRole, props }) {
1667
- if (!effectiveRole || !_AriaPolicyEngine.#LIVE_REGION_ROLES.has(effectiveRole)) return VALID;
1668
- if ("aria-atomic" in props) return VALID;
1751
+ if (!effectiveRole || !_AriaPolicyEngine.#LIVE_REGION_ROLES.has(effectiveRole))
1752
+ return NO_VIOLATIONS;
1753
+ if ("aria-atomic" in props) return NO_VIOLATIONS;
1669
1754
  return [
1670
1755
  {
1671
1756
  valid: false,
@@ -1689,8 +1774,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1689
1774
  };
1690
1775
  static #checkInvalidAriaRelevant({ props }) {
1691
1776
  const relevant = props["aria-relevant"];
1692
- if (relevant === void 0) return VALID;
1693
- if (typeof relevant !== "string") return VALID;
1777
+ if (relevant === void 0) return NO_VIOLATIONS;
1778
+ if (typeof relevant !== "string") return NO_VIOLATIONS;
1694
1779
  const tokens = relevant.trim().split(/\s+/);
1695
1780
  const invalid = tokens.filter((t) => !_AriaPolicyEngine.#VALID_RELEVANT_TOKENS.has(t));
1696
1781
  if (invalid.length > 0) {
@@ -1717,7 +1802,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1717
1802
  }
1718
1803
  ];
1719
1804
  }
1720
- return VALID;
1805
+ return NO_VIOLATIONS;
1721
1806
  }
1722
1807
  };
1723
1808
 
@@ -2317,7 +2402,7 @@ function cva2(base, config) {
2317
2402
  // ../../lib/styling/src/static-class-resolver.ts
2318
2403
  var StaticClassResolver = class {
2319
2404
  #baseClass;
2320
- #cache = /* @__PURE__ */ new Map();
2405
+ #cache = new LRUCache(200);
2321
2406
  #resolveTag;
2322
2407
  constructor(baseClass, tagMap) {
2323
2408
  this.#baseClass = Array.isArray(baseClass) ? baseClass.join(" ") : baseClass;
@@ -2331,17 +2416,9 @@ var StaticClassResolver = class {
2331
2416
  resolve(tag, skipTagMap = false) {
2332
2417
  if (typeof tag !== "string" || skipTagMap) return this.#baseClass;
2333
2418
  const cached = this.#cache.get(tag);
2334
- if (cached !== void 0) {
2335
- this.#cache.delete(tag);
2336
- this.#cache.set(tag, cached);
2337
- return cached;
2338
- }
2419
+ if (cached !== void 0) return cached;
2339
2420
  const result = this.#resolveTag(tag);
2340
2421
  this.#cache.set(tag, result);
2341
- if (this.#cache.size > 200) {
2342
- const lru = this.#cache.keys().next().value;
2343
- if (lru !== void 0) this.#cache.delete(lru);
2344
- }
2345
2422
  return result;
2346
2423
  }
2347
2424
  };
@@ -2352,7 +2429,7 @@ var VariantClassResolver = class _VariantClassResolver {
2352
2429
  #recipeMap;
2353
2430
  #variantKeys;
2354
2431
  #precomputedClasses;
2355
- #cache = /* @__PURE__ */ new Map();
2432
+ #cache = new LRUCache(1e3);
2356
2433
  constructor(cvaFn, recipeMap, variantKeys, precomputedClasses) {
2357
2434
  this.#cvaFn = cvaFn ?? null;
2358
2435
  this.#recipeMap = Object.freeze(recipeMap ?? {});
@@ -2367,17 +2444,9 @@ var VariantClassResolver = class _VariantClassResolver {
2367
2444
  if (precomputed !== void 0) return precomputed;
2368
2445
  }
2369
2446
  const cached = this.#cache.get(cacheKey);
2370
- if (cached !== void 0) {
2371
- this.#cache.delete(cacheKey);
2372
- this.#cache.set(cacheKey, cached);
2373
- return cached;
2374
- }
2447
+ if (cached !== void 0) return cached;
2375
2448
  const result = this.#compute(props, recipe);
2376
2449
  this.#cache.set(cacheKey, result);
2377
- if (this.#cache.size > 1e3) {
2378
- const lru = this.#cache.keys().next().value;
2379
- if (lru !== void 0) this.#cache.delete(lru);
2380
- }
2381
2450
  return result;
2382
2451
  }
2383
2452
  #compute(props, recipe) {
@@ -2434,7 +2503,7 @@ function createClassPipeline(resolved) {
2434
2503
  const staticClasses = staticResolver.resolve(tag, recipe !== void 0);
2435
2504
  const variantClasses = variantResolver.resolve({ props, recipe });
2436
2505
  if (!className)
2437
- return staticClasses && variantClasses ? `${staticClasses} ${variantClasses}` : staticClasses || variantClasses;
2506
+ return (staticClasses && variantClasses ? `${staticClasses} ${variantClasses}` : staticClasses || variantClasses) || void 0;
2438
2507
  return cn(staticClasses, variantClasses, className);
2439
2508
  };
2440
2509
  }
@@ -2645,7 +2714,7 @@ function createPolymorphic2(options = {}) {
2645
2714
  if (process.env.NODE_ENV !== "production") {
2646
2715
  validateRenderProps(resolved.diagnostics, resolved, props, recipe);
2647
2716
  }
2648
- return classPipeline(tag, props, className, recipe);
2717
+ return classPipeline(tag, props, className, recipe) || void 0;
2649
2718
  },
2650
2719
  resolveAria(tag, props) {
2651
2720
  return resolveAriaFn(tag, props);
@@ -3005,8 +3074,7 @@ function render({
3005
3074
  childrenEvaluator
3006
3075
  }) {
3007
3076
  const state = prepareRenderState(runtime, props, filterProps);
3008
- let cached;
3009
- const getNormalizedChildren = () => cached ??= normalizeChildren(state.children);
3077
+ const getNormalizedChildren = lazy(() => normalizeChildren(state.children));
3010
3078
  if (process.env.NODE_ENV !== "production") {
3011
3079
  childrenEvaluator?.evaluate(getNormalizedChildren(), {
3012
3080
  tag: state.tag,
@@ -138,7 +138,7 @@ interface BaseClassOptions {
138
138
  baseClassName?: ClassName;
139
139
  }
140
140
 
141
- type ClassPipelineFn = (tag: unknown, props: AnyRecord, className?: ClassName, recipe?: string) => string;
141
+ type ClassPipelineFn = (tag: unknown, props: AnyRecord, className?: ClassName, recipe?: string) => string | undefined;
142
142
 
143
143
  interface RecipeOptions<TVariants extends VariantMap = VariantMap> {
144
144
  recipeMap?: Record<string, RecipeTarget<TVariants>>;
@@ -215,7 +215,9 @@ type AriaInvalidWithoutFix<M extends string = string> = AriaInvalidBase<M> & {
215
215
  type AriaInvalidResult<M extends string = string> = AriaInvalidWithFix<M> | AriaInvalidWithoutFix<M>;
216
216
  type AriaResult = ValidResult | AriaInvalidResult;
217
217
 
218
- type AriaRule<C extends AriaContext = AriaContext> = (context: C) => readonly AriaResult[];
218
+ type AriaRule<C extends AriaContext = AriaContext> = ((context: C) => readonly AriaResult[]) & {
219
+ readonly readsProps?: readonly string[];
220
+ };
219
221
 
220
222
  type PropNormalizer = (props: Readonly<AnyRecord & IntrinsicProps>) => Partial<AnyRecord & IntrinsicProps>;
221
223
 
@@ -244,7 +244,7 @@ var require_eslint_utils = __commonJS({
244
244
  // ../../plugins/eslint/src/rules/no-dead-compound.ts
245
245
  var import_eslint_utils = __toESM(require_eslint_utils(), 1);
246
246
 
247
- // ../../lib/primitive/src/utils/is-object.ts
247
+ // ../../lib/primitive/src/utils/type-guards.ts
248
248
  function isString(value) {
249
249
  return typeof value === "string";
250
250
  }
@@ -0,0 +1,36 @@
1
+ type StringMap<T = unknown> = Record<string, T>;
2
+ type AnyRecord = StringMap<unknown>;
3
+
4
+ /**
5
+ * Resolves the effective HTML tag for a vnode:
6
+ * - native element: returns its type string directly
7
+ * - praxis-kit component: resolves `as ?? defaultTag`, mirroring render-time logic
8
+ * - anything else: returns undefined
9
+ */
10
+ declare function getTag(child: unknown): string | undefined;
11
+ /**
12
+ * Checks whether a vnode resolves to one of the given intrinsic tag names.
13
+ * Matches native elements directly, and praxis-kit components by resolving
14
+ * `as ?? defaultTag` — the same logic used at render time.
15
+ *
16
+ * Two call forms are supported:
17
+ * isTag('img')(child) — curried, composable with Array#filter
18
+ * isTag(child, 'img') — direct, reads naturally in if-blocks
19
+ */
20
+ type TagChild = {
21
+ type: unknown;
22
+ };
23
+ declare function isTag(tag: string, ...tags: readonly string[]): (child: unknown) => child is TagChild;
24
+ declare function isTag(child: unknown, tag: string, ...tags: readonly string[]): boolean;
25
+ /**
26
+ * Checks whether a vnode is flow content per the HTML content model: text nodes
27
+ * (string/number) always qualify, and elements/components qualify unless their
28
+ * resolved tag is in the blocked set.
29
+ */
30
+ declare function isFlowContent(...blockedTags: readonly string[]): (child: unknown) => boolean;
31
+
32
+ declare function isObject(value: unknown, excludeArrays: true): value is AnyRecord;
33
+ declare function isObject(value: unknown, excludeArrays?: false): value is object;
34
+ declare function isString(value: unknown): value is string;
35
+
36
+ export { getTag, isFlowContent, isObject, isString, isTag };