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.
@@ -171,6 +171,19 @@ var iterate = Object.freeze({
171
171
  values
172
172
  });
173
173
 
174
+ // ../../lib/primitive/src/utils/lazy.ts
175
+ function lazy(factory) {
176
+ let initialized = false;
177
+ let value;
178
+ return () => {
179
+ if (!initialized) {
180
+ value = factory();
181
+ initialized = true;
182
+ }
183
+ return value;
184
+ };
185
+ }
186
+
174
187
  // ../../plugins/vite/src/ast.ts
175
188
  function getProperty(obj, key) {
176
189
  return iterate.find(obj.properties, (prop) => {
@@ -744,8 +757,7 @@ function analyzeJsxSites(source, constraints, severity) {
744
757
  count = ZERO;
745
758
  }
746
759
  if (!tagName) return;
747
- let pos;
748
- const getPos = () => pos ??= source.getLineAndCharacterOfPosition(node.getStart(source));
760
+ const getPos = lazy(() => source.getLineAndCharacterOfPosition(node.getStart(source)));
749
761
  if (count !== void 0) {
750
762
  const c = byName.get(tagName);
751
763
  if (c && (count.max < c.totalMin || c.exclusiveChildren && count.min > c.totalMax)) {
@@ -157,7 +157,7 @@ interface BaseClassOptions {
157
157
  baseClassName?: ClassName;
158
158
  }
159
159
 
160
- type ClassPipelineFn = (tag: unknown, props: AnyRecord, className?: ClassName, recipe?: string) => string;
160
+ type ClassPipelineFn = (tag: unknown, props: AnyRecord, className?: ClassName, recipe?: string) => string | undefined;
161
161
 
162
162
  interface RecipeOptions<TVariants extends VariantMap = VariantMap> {
163
163
  recipeMap?: Record<string, RecipeTarget<TVariants>>;
@@ -235,7 +235,9 @@ type AriaInvalidWithoutFix<M extends string = string> = AriaInvalidBase<M> & {
235
235
  type AriaInvalidResult<M extends string = string> = AriaInvalidWithFix<M> | AriaInvalidWithoutFix<M>;
236
236
  type AriaResult = ValidResult | AriaInvalidResult;
237
237
 
238
- type AriaRule<C extends AriaContext = AriaContext> = (context: C) => readonly AriaResult[];
238
+ type AriaRule<C extends AriaContext = AriaContext> = ((context: C) => readonly AriaResult[]) & {
239
+ readonly readsProps?: readonly string[];
240
+ };
239
241
 
240
242
  type PropNormalizer = (props: Readonly<AnyRecord & IntrinsicProps>) => Partial<AnyRecord & IntrinsicProps>;
241
243
 
package/dist/vue/index.js CHANGED
@@ -4,7 +4,20 @@ import { computed, defineComponent as defineComponent2 } from "vue";
4
4
  // ../../lib/primitive/src/guards/children/component-id.ts
5
5
  var COMPONENT_DEFAULT_TAG = /* @__PURE__ */ Symbol.for("praxis.component-default-tag");
6
6
 
7
- // ../../lib/primitive/src/utils/is-object.ts
7
+ // ../../lib/primitive/src/guards/foundational/is-defined.ts
8
+ function isUndefined(value) {
9
+ return value === void 0;
10
+ }
11
+
12
+ // ../../lib/primitive/src/guards/foundational/is-null.ts
13
+ function isNull(value) {
14
+ return value === null;
15
+ }
16
+ function isNonNull(value) {
17
+ return value != null;
18
+ }
19
+
20
+ // ../../lib/primitive/src/utils/type-guards.ts
8
21
  function isObject(value, excludeArrays = false) {
9
22
  if (value === null || typeof value !== "object") return false;
10
23
  return excludeArrays ? !Array.isArray(value) : true;
@@ -16,16 +29,6 @@ function isNumber(value) {
16
29
  return typeof value === "number";
17
30
  }
18
31
 
19
- // ../../lib/primitive/src/guards/foundational/is-defined.ts
20
- function isUndefined(value) {
21
- return value === void 0;
22
- }
23
-
24
- // ../../lib/primitive/src/guards/foundational/is-null.ts
25
- function isNull(value) {
26
- return value === null;
27
- }
28
-
29
32
  // ../../lib/primitive/src/guards/children/is-tag.ts
30
33
  function getAsProp(child) {
31
34
  if (!isObject(child) || !("props" in child)) return void 0;
@@ -650,6 +653,45 @@ var iterate = Object.freeze({
650
653
  values
651
654
  });
652
655
 
656
+ // ../../lib/primitive/src/utils/lru-cache.ts
657
+ var LRUCache = class {
658
+ #maxSize;
659
+ #store = /* @__PURE__ */ new Map();
660
+ constructor(maxSize) {
661
+ if (!Number.isInteger(maxSize) || maxSize < 1) {
662
+ throw new RangeError("LRUCache maxSize must be a positive integer.");
663
+ }
664
+ this.#maxSize = maxSize;
665
+ }
666
+ get(key) {
667
+ if (!this.#store.has(key)) return void 0;
668
+ const value = this.#store.get(key);
669
+ this.#store.delete(key);
670
+ this.#store.set(key, value);
671
+ return value;
672
+ }
673
+ set(key, value) {
674
+ this.#store.delete(key);
675
+ this.#store.set(key, value);
676
+ if (this.#store.size > this.#maxSize) {
677
+ const lru = this.#store.keys().next().value;
678
+ if (lru !== void 0) this.#store.delete(lru);
679
+ }
680
+ }
681
+ has(key) {
682
+ return this.#store.has(key);
683
+ }
684
+ delete(key) {
685
+ return this.#store.delete(key);
686
+ }
687
+ get size() {
688
+ return this.#store.size;
689
+ }
690
+ clear() {
691
+ this.#store.clear();
692
+ }
693
+ };
694
+
653
695
  // ../../lib/primitive/src/utils/merge-props.ts
654
696
  function mergeProps(defaultProps, props) {
655
697
  return {
@@ -658,28 +700,6 @@ function mergeProps(defaultProps, props) {
658
700
  };
659
701
  }
660
702
 
661
- // ../../lib/contract/src/strict/invariant-base.ts
662
- var InvariantBase = class {
663
- diagnostics;
664
- constructor(diagnostics) {
665
- this.diagnostics = diagnostics;
666
- }
667
- get active() {
668
- return this.diagnostics.active;
669
- }
670
- violate(input) {
671
- this.diagnostics.error(input);
672
- }
673
- // Always caps at warn — never throws. ARIA 'warning' violations route here
674
- // so they surface even in strict='throw' mode without aborting a render.
675
- warn(input) {
676
- this.diagnostics.warn(input);
677
- }
678
- invariant(condition, input) {
679
- if (!condition) this.violate(input);
680
- }
681
- };
682
-
683
703
  // ../../lib/contract/src/diagnostics/aria.ts
684
704
  import { DiagnosticCategory, DiagnosticCode } from "../_shared/diagnostics.js";
685
705
  var AriaDiagnostics = {
@@ -1036,8 +1056,30 @@ var SlotDiagnostics = {
1036
1056
  }
1037
1057
  };
1038
1058
 
1059
+ // ../../lib/contract/src/strict/invariant-base.ts
1060
+ var InvariantBase = class {
1061
+ diagnostics;
1062
+ constructor(diagnostics) {
1063
+ this.diagnostics = diagnostics;
1064
+ }
1065
+ get active() {
1066
+ return this.diagnostics.active;
1067
+ }
1068
+ violate(input) {
1069
+ this.diagnostics.error(input);
1070
+ }
1071
+ // Always caps at warn — never throws. ARIA 'warning' violations route here
1072
+ // so they surface even in strict='throw' mode without aborting a render.
1073
+ warn(input) {
1074
+ this.diagnostics.warn(input);
1075
+ }
1076
+ invariant(condition, input) {
1077
+ if (!condition) this.violate(input);
1078
+ }
1079
+ };
1080
+
1039
1081
  // ../../lib/contract/src/aria/polymorphic-validator.ts
1040
- var VALID = [{ valid: true }];
1082
+ var NO_VIOLATIONS = [{ valid: true }];
1041
1083
  function isIntrinsicTag(tag) {
1042
1084
  return isString(tag);
1043
1085
  }
@@ -1047,8 +1089,7 @@ function omitProp(obj, key) {
1047
1089
  }
1048
1090
  var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1049
1091
  #extraRules;
1050
- #planCache = /* @__PURE__ */ new Map();
1051
- static #MAX_CACHE = 100;
1092
+ #planCache = new LRUCache(100);
1052
1093
  // Memoized AriaFix objects keyed by attribute name — the ARIA attribute set is
1053
1094
  // finite so this Map is bounded and avoids recreating closures on every cache miss.
1054
1095
  static #removeAttributeFixCache = /* @__PURE__ */ new Map();
@@ -1080,17 +1121,20 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1080
1121
  static #deriveContext(tag, props) {
1081
1122
  if (!isIntrinsicTag(tag)) return { proceed: false, result: { props, violations: [] } };
1082
1123
  const implicitRole = getImplicitRole(tag, props);
1083
- const hasRole2 = implicitRole != null || isString(props.role) && props.role.length > 0;
1124
+ const hasRole2 = isNonNull(implicitRole) || isString(props.role) && props.role.length > 0;
1084
1125
  if (!hasRole2) return { proceed: false, result: { props, violations: [] } };
1085
1126
  const normalized = _AriaPolicyEngine.#normalizeEmptyRole(tag, props);
1086
- if (normalized.normalized) return { proceed: false, result: normalized.result };
1087
- const effectiveRole = props.role ?? implicitRole;
1127
+ const workingProps = normalized.normalized ? normalized.result.props : props;
1128
+ const preExistingViolations = normalized.normalized ? normalized.result.violations : [];
1129
+ const effectiveRole = workingProps.role ?? implicitRole;
1088
1130
  return {
1089
1131
  proceed: true,
1090
1132
  tag,
1091
1133
  implicitRole,
1092
1134
  effectiveRole,
1093
- context: { tag, props, implicitRole, effectiveRole }
1135
+ props: workingProps,
1136
+ preExistingViolations,
1137
+ context: { tag, props: workingProps, implicitRole, effectiveRole }
1094
1138
  };
1095
1139
  }
1096
1140
  static #runRules(rules, context) {
@@ -1105,7 +1149,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1105
1149
  } = context;
1106
1150
  const { message, attribute, severity } = result;
1107
1151
  const resolvedMessage = message ?? result.diagnostic?.message;
1108
- const fallbackDiag = resolvedMessage == null ? AriaDiagnostics.invalidRole(role, tag) : void 0;
1152
+ const fallbackDiag = isNonNull(resolvedMessage) ? void 0 : AriaDiagnostics.invalidRole(role, tag);
1109
1153
  violations.push({
1110
1154
  message: resolvedMessage ?? fallbackDiag.message,
1111
1155
  tag,
@@ -1113,8 +1157,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1113
1157
  attribute,
1114
1158
  severity,
1115
1159
  phase: "evaluate",
1116
- ...result.diagnostic != null && { diagnostic: result.diagnostic },
1117
- ...fallbackDiag != null && { diagnostic: fallbackDiag }
1160
+ ...isNonNull(result.diagnostic) && { diagnostic: result.diagnostic },
1161
+ ...isNonNull(fallbackDiag) && { diagnostic: fallbackDiag }
1118
1162
  });
1119
1163
  if (result.fixable) fixes.push(result.fix);
1120
1164
  });
@@ -1122,7 +1166,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1122
1166
  return { violations, fixes };
1123
1167
  }
1124
1168
  static #getRules(context) {
1125
- if (_AriaPolicyEngine.#hasRole(context.props) || context.effectiveRole != null && _AriaPolicyEngine.#LIVE_REGION_ROLES.has(context.effectiveRole)) {
1169
+ if (_AriaPolicyEngine.#hasRole(context.props) || isNonNull(context.effectiveRole) && _AriaPolicyEngine.#LIVE_REGION_ROLES.has(context.effectiveRole)) {
1126
1170
  return _AriaPolicyEngine.#pipeline;
1127
1171
  }
1128
1172
  return _AriaPolicyEngine.#implicitOnlyRules;
@@ -1130,24 +1174,36 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1130
1174
  static evaluate(tag, props) {
1131
1175
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1132
1176
  if (!derived.proceed) return derived.result;
1133
- const { tag: narrowedTag, implicitRole, context } = derived;
1177
+ const {
1178
+ tag: narrowedTag,
1179
+ implicitRole,
1180
+ context,
1181
+ props: workingProps,
1182
+ preExistingViolations
1183
+ } = derived;
1134
1184
  const { violations, fixes } = _AriaPolicyEngine.#runRules(
1135
1185
  _AriaPolicyEngine.#getRules(context),
1136
1186
  context
1137
1187
  );
1138
- const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, props, fixes);
1139
- return { props: next, violations };
1188
+ const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1189
+ return { props: next, violations: [...preExistingViolations, ...violations] };
1140
1190
  }
1141
1191
  static #evaluateWithRules(tag, props, extraRules) {
1142
1192
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1143
1193
  if (!derived.proceed) return derived.result;
1144
- const { tag: narrowedTag, implicitRole, context } = derived;
1194
+ const {
1195
+ tag: narrowedTag,
1196
+ implicitRole,
1197
+ context,
1198
+ props: workingProps,
1199
+ preExistingViolations
1200
+ } = derived;
1145
1201
  const { violations, fixes } = _AriaPolicyEngine.#runRules(
1146
1202
  [..._AriaPolicyEngine.#getRules(context), ...extraRules],
1147
1203
  context
1148
1204
  );
1149
- const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, props, fixes);
1150
- return { props: next, violations };
1205
+ const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1206
+ return { props: next, violations: [...preExistingViolations, ...violations] };
1151
1207
  }
1152
1208
  report(violations) {
1153
1209
  iterate.forEach(violations, (v) => {
@@ -1156,12 +1212,12 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1156
1212
  else this.warn(d);
1157
1213
  });
1158
1214
  }
1159
- // Cache key covers only the aria-relevant subset of props (tag + role + aria-* attrs).
1160
- // Non-aria props (className, onClick, etc.) do not affect ARIA decisions and are excluded
1161
- // so cache hits survive re-renders that only change non-aria props.
1162
- // Note: #extraRules are NOT included in the key each engine instance has its own Map,
1163
- // so two engines with different rules never share cache entries. If caching ever becomes
1164
- // static/shared, rule identity would need to be folded into the key.
1215
+ // Cache key covers only the aria-relevant subset of props (tag + role + aria-* attrs)
1216
+ // exactly what the built-in pipeline reads. Non-aria props (className, onClick, etc.) do
1217
+ // not affect built-in ARIA decisions and are excluded so cache hits survive re-renders
1218
+ // that only change non-aria props. This key alone is unsound for #extraRules, which may
1219
+ // read arbitrary props outside this set validate() extends it with #extraRulesKeySuffix,
1220
+ // or bypasses the cache, to account for that.
1165
1221
  static #createPlanKey(tag, props) {
1166
1222
  if (!isIntrinsicTag(tag)) return null;
1167
1223
  const parts = [tag];
@@ -1177,6 +1233,23 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1177
1233
  if (ariaEntries.length > 0) parts.push(...ariaEntries.sort());
1178
1234
  return parts.join("|");
1179
1235
  }
1236
+ // Extends the base cache key with the props each extra rule declares it reads (`readsProps`).
1237
+ // Returns null — meaning "don't cache" — if any extra rule omits `readsProps` (it may read
1238
+ // arbitrary props the key can't account for) or if a declared prop's value isn't a primitive
1239
+ // (object/array identity isn't stably representable in a string key).
1240
+ static #extraRulesKeySuffix(extraRules, props) {
1241
+ const parts = [];
1242
+ for (const rule of extraRules) {
1243
+ const readsProps = rule.readsProps;
1244
+ if (!isNonNull(readsProps)) return null;
1245
+ for (const propKey of readsProps) {
1246
+ const v = props[propKey];
1247
+ if (v !== void 0 && !isString(v) && !isNumber(v) && typeof v !== "boolean") return null;
1248
+ parts.push(`x:${propKey}:${String(v)}`);
1249
+ }
1250
+ }
1251
+ return parts.sort().join("|");
1252
+ }
1180
1253
  static #computePlan(inputProps, resultProps) {
1181
1254
  const removals = /* @__PURE__ */ new Set();
1182
1255
  const updates = {};
@@ -1200,12 +1273,15 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1200
1273
  return next;
1201
1274
  }
1202
1275
  validate(tag, props) {
1203
- const key = _AriaPolicyEngine.#createPlanKey(tag, props);
1276
+ const baseKey = _AriaPolicyEngine.#createPlanKey(tag, props);
1277
+ let key = baseKey;
1278
+ if (this.#extraRules.length > 0) {
1279
+ const suffix = _AriaPolicyEngine.#extraRulesKeySuffix(this.#extraRules, props);
1280
+ key = isNonNull(baseKey) && isNonNull(suffix) ? `${baseKey}|${suffix}` : null;
1281
+ }
1204
1282
  if (!isNull(key)) {
1205
1283
  const cached = this.#planCache.get(key);
1206
1284
  if (cached !== void 0) {
1207
- this.#planCache.delete(key);
1208
- this.#planCache.set(key, cached);
1209
1285
  if (cached.violations.length > 0) this.report(cached.violations);
1210
1286
  return {
1211
1287
  props: _AriaPolicyEngine.#applyPlan(props, cached.removals, cached.updates),
@@ -1222,10 +1298,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1222
1298
  );
1223
1299
  const plan = { removals, updates, violations: result.violations };
1224
1300
  this.#planCache.set(key, plan);
1225
- if (this.#planCache.size > _AriaPolicyEngine.#MAX_CACHE) {
1226
- const lru = this.#planCache.keys().next().value;
1227
- if (lru !== void 0) this.#planCache.delete(lru);
1228
- }
1229
1301
  }
1230
1302
  return result;
1231
1303
  }
@@ -1296,7 +1368,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1296
1368
  implicitRole
1297
1369
  }) {
1298
1370
  const role = props.role;
1299
- if (!implicitRole || !role || role === implicitRole) return VALID;
1371
+ if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS;
1300
1372
  if (isStrongImplicitRole(tag) && role === "region") {
1301
1373
  return [
1302
1374
  {
@@ -1308,11 +1380,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1308
1380
  }
1309
1381
  ];
1310
1382
  }
1311
- return VALID;
1383
+ return NO_VIOLATIONS;
1312
1384
  }
1313
1385
  static #checkRedundantRole({ tag, props, implicitRole }) {
1314
1386
  const role = props.role;
1315
- if (!implicitRole || !role || role !== implicitRole) return VALID;
1387
+ if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS;
1316
1388
  return [
1317
1389
  {
1318
1390
  valid: false,
@@ -1325,8 +1397,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1325
1397
  }
1326
1398
  static #checkStandaloneRegion({ tag, props, implicitRole }) {
1327
1399
  const role = props.role;
1328
- if (role !== "region") return VALID;
1329
- if (!isStandaloneTag(tag)) return VALID;
1400
+ if (role !== "region") return NO_VIOLATIONS;
1401
+ if (!isStandaloneTag(tag)) return NO_VIOLATIONS;
1330
1402
  return [
1331
1403
  {
1332
1404
  valid: false,
@@ -1342,7 +1414,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1342
1414
  props,
1343
1415
  effectiveRole
1344
1416
  }) {
1345
- if (effectiveRole === "none" || effectiveRole === "presentation") return VALID;
1417
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1346
1418
  const results = [];
1347
1419
  iterate.forEachEntry(props, (key) => {
1348
1420
  if (!key.startsWith("aria-")) return;
@@ -1460,12 +1532,12 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1460
1532
  }
1461
1533
  }
1462
1534
  static #checkAriaAttributeValues({ props, effectiveRole }) {
1463
- if (effectiveRole === "none" || effectiveRole === "presentation") return VALID;
1535
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1464
1536
  const results = [];
1465
1537
  iterate.forEachEntry(props, (key, value) => {
1466
1538
  if (!key.startsWith("aria-")) return;
1467
1539
  const type = _AriaPolicyEngine.#ARIA_VALUE_TYPES.get(key);
1468
- if (type == null) return;
1540
+ if (!isNonNull(type)) return;
1469
1541
  if (_AriaPolicyEngine.#isValidAriaValue(value, type)) return;
1470
1542
  results.push({
1471
1543
  valid: false,
@@ -1496,13 +1568,13 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1496
1568
  props,
1497
1569
  effectiveRole
1498
1570
  }) {
1499
- if (effectiveRole === "none" || effectiveRole === "presentation") return VALID;
1571
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1500
1572
  const implicitLevel = _AriaPolicyEngine.#HEADING_IMPLICIT_LEVELS.get(tag);
1501
- if (implicitLevel == null) return VALID;
1573
+ if (!isNonNull(implicitLevel)) return NO_VIOLATIONS;
1502
1574
  const raw = props["aria-level"];
1503
- if (raw == null) return VALID;
1575
+ if (!isNonNull(raw)) return NO_VIOLATIONS;
1504
1576
  const n = typeof raw === "number" ? raw : typeof raw === "string" ? parseInt(raw, 10) : NaN;
1505
- if (!Number.isFinite(n) || n !== implicitLevel) return VALID;
1577
+ if (!Number.isFinite(n) || n !== implicitLevel) return NO_VIOLATIONS;
1506
1578
  return [
1507
1579
  {
1508
1580
  valid: false,
@@ -1525,9 +1597,10 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1525
1597
  props,
1526
1598
  effectiveRole
1527
1599
  }) {
1528
- if (!effectiveRole || !_AriaPolicyEngine.#NAME_REQUIRED_ROLES.has(effectiveRole)) return VALID;
1529
- if ("aria-label" in props || "aria-labelledby" in props) return VALID;
1530
- if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return VALID;
1600
+ if (!effectiveRole || !_AriaPolicyEngine.#NAME_REQUIRED_ROLES.has(effectiveRole))
1601
+ return NO_VIOLATIONS;
1602
+ if ("aria-label" in props || "aria-labelledby" in props) return NO_VIOLATIONS;
1603
+ if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return NO_VIOLATIONS;
1531
1604
  return [
1532
1605
  {
1533
1606
  valid: false,
@@ -1550,9 +1623,9 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1550
1623
  props,
1551
1624
  effectiveRole
1552
1625
  }) {
1553
- if (!effectiveRole) return VALID;
1626
+ if (!effectiveRole) return NO_VIOLATIONS;
1554
1627
  const required = _AriaPolicyEngine.#REQUIRED_PROPERTIES.get(effectiveRole);
1555
- if (required == null) return VALID;
1628
+ if (!isNonNull(required)) return NO_VIOLATIONS;
1556
1629
  const results = [];
1557
1630
  iterate.forEach(required, (attr) => {
1558
1631
  if (attr in props) return;
@@ -1576,12 +1649,12 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1576
1649
  ]);
1577
1650
  // WAI-ARIA 1.2 §6.6: aria-hidden="true" must not be placed on focusable elements.
1578
1651
  static #checkAriaHiddenOnFocusable({ tag, props }) {
1579
- if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return VALID;
1652
+ if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return NO_VIOLATIONS;
1580
1653
  const isInteractive = _AriaPolicyEngine.#INTERACTIVE_TAGS.has(tag);
1581
1654
  if (!isInteractive) {
1582
1655
  const tabindex = props.tabindex;
1583
1656
  const n = typeof tabindex === "number" ? tabindex : typeof tabindex === "string" ? parseInt(tabindex, 10) : NaN;
1584
- if (!Number.isFinite(n) || n < 0) return VALID;
1657
+ if (!Number.isFinite(n) || n < 0) return NO_VIOLATIONS;
1585
1658
  }
1586
1659
  return [
1587
1660
  {
@@ -1600,7 +1673,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1600
1673
  props,
1601
1674
  effectiveRole
1602
1675
  }) {
1603
- if (effectiveRole !== "none" && effectiveRole !== "presentation") return VALID;
1676
+ if (effectiveRole !== "none" && effectiveRole !== "presentation") return NO_VIOLATIONS;
1604
1677
  const results = [];
1605
1678
  iterate.forEachEntry(props, (key) => {
1606
1679
  if (!key.startsWith("aria-")) return;
@@ -1624,10 +1697,10 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1624
1697
  ["timer", "off"]
1625
1698
  ]);
1626
1699
  static #checkMissingLiveRegion({ effectiveRole, props }) {
1627
- if (!effectiveRole) return VALID;
1700
+ if (!effectiveRole) return NO_VIOLATIONS;
1628
1701
  const impliedLive = _AriaPolicyEngine.#LIVE_REGION_ROLES.get(effectiveRole);
1629
- if (!impliedLive) return VALID;
1630
- if ("aria-live" in props) return VALID;
1702
+ if (!impliedLive) return NO_VIOLATIONS;
1703
+ if ("aria-live" in props) return NO_VIOLATIONS;
1631
1704
  const injectLive = {
1632
1705
  kind: `injectLive:${effectiveRole}`,
1633
1706
  apply: (ctx) => ({
@@ -1647,8 +1720,9 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1647
1720
  ];
1648
1721
  }
1649
1722
  static #checkMissingAtomic({ effectiveRole, props }) {
1650
- if (!effectiveRole || !_AriaPolicyEngine.#LIVE_REGION_ROLES.has(effectiveRole)) return VALID;
1651
- if ("aria-atomic" in props) return VALID;
1723
+ if (!effectiveRole || !_AriaPolicyEngine.#LIVE_REGION_ROLES.has(effectiveRole))
1724
+ return NO_VIOLATIONS;
1725
+ if ("aria-atomic" in props) return NO_VIOLATIONS;
1652
1726
  return [
1653
1727
  {
1654
1728
  valid: false,
@@ -1672,8 +1746,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1672
1746
  };
1673
1747
  static #checkInvalidAriaRelevant({ props }) {
1674
1748
  const relevant = props["aria-relevant"];
1675
- if (relevant === void 0) return VALID;
1676
- if (typeof relevant !== "string") return VALID;
1749
+ if (relevant === void 0) return NO_VIOLATIONS;
1750
+ if (typeof relevant !== "string") return NO_VIOLATIONS;
1677
1751
  const tokens = relevant.trim().split(/\s+/);
1678
1752
  const invalid = tokens.filter((t) => !_AriaPolicyEngine.#VALID_RELEVANT_TOKENS.has(t));
1679
1753
  if (invalid.length > 0) {
@@ -1700,7 +1774,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1700
1774
  }
1701
1775
  ];
1702
1776
  }
1703
- return VALID;
1777
+ return NO_VIOLATIONS;
1704
1778
  }
1705
1779
  };
1706
1780
 
@@ -2300,7 +2374,7 @@ function cva2(base, config) {
2300
2374
  // ../../lib/styling/src/static-class-resolver.ts
2301
2375
  var StaticClassResolver = class {
2302
2376
  #baseClass;
2303
- #cache = /* @__PURE__ */ new Map();
2377
+ #cache = new LRUCache(200);
2304
2378
  #resolveTag;
2305
2379
  constructor(baseClass, tagMap) {
2306
2380
  this.#baseClass = Array.isArray(baseClass) ? baseClass.join(" ") : baseClass;
@@ -2314,17 +2388,9 @@ var StaticClassResolver = class {
2314
2388
  resolve(tag, skipTagMap = false) {
2315
2389
  if (typeof tag !== "string" || skipTagMap) return this.#baseClass;
2316
2390
  const cached = this.#cache.get(tag);
2317
- if (cached !== void 0) {
2318
- this.#cache.delete(tag);
2319
- this.#cache.set(tag, cached);
2320
- return cached;
2321
- }
2391
+ if (cached !== void 0) return cached;
2322
2392
  const result = this.#resolveTag(tag);
2323
2393
  this.#cache.set(tag, result);
2324
- if (this.#cache.size > 200) {
2325
- const lru = this.#cache.keys().next().value;
2326
- if (lru !== void 0) this.#cache.delete(lru);
2327
- }
2328
2394
  return result;
2329
2395
  }
2330
2396
  };
@@ -2335,7 +2401,7 @@ var VariantClassResolver = class _VariantClassResolver {
2335
2401
  #recipeMap;
2336
2402
  #variantKeys;
2337
2403
  #precomputedClasses;
2338
- #cache = /* @__PURE__ */ new Map();
2404
+ #cache = new LRUCache(1e3);
2339
2405
  constructor(cvaFn, recipeMap, variantKeys, precomputedClasses) {
2340
2406
  this.#cvaFn = cvaFn ?? null;
2341
2407
  this.#recipeMap = Object.freeze(recipeMap ?? {});
@@ -2350,17 +2416,9 @@ var VariantClassResolver = class _VariantClassResolver {
2350
2416
  if (precomputed !== void 0) return precomputed;
2351
2417
  }
2352
2418
  const cached = this.#cache.get(cacheKey);
2353
- if (cached !== void 0) {
2354
- this.#cache.delete(cacheKey);
2355
- this.#cache.set(cacheKey, cached);
2356
- return cached;
2357
- }
2419
+ if (cached !== void 0) return cached;
2358
2420
  const result = this.#compute(props, recipe);
2359
2421
  this.#cache.set(cacheKey, result);
2360
- if (this.#cache.size > 1e3) {
2361
- const lru = this.#cache.keys().next().value;
2362
- if (lru !== void 0) this.#cache.delete(lru);
2363
- }
2364
2422
  return result;
2365
2423
  }
2366
2424
  #compute(props, recipe) {
@@ -2417,7 +2475,7 @@ function createClassPipeline(resolved) {
2417
2475
  const staticClasses = staticResolver.resolve(tag, recipe !== void 0);
2418
2476
  const variantClasses = variantResolver.resolve({ props, recipe });
2419
2477
  if (!className)
2420
- return staticClasses && variantClasses ? `${staticClasses} ${variantClasses}` : staticClasses || variantClasses;
2478
+ return (staticClasses && variantClasses ? `${staticClasses} ${variantClasses}` : staticClasses || variantClasses) || void 0;
2421
2479
  return cn(staticClasses, variantClasses, className);
2422
2480
  };
2423
2481
  }
@@ -2628,7 +2686,7 @@ function createPolymorphic2(options = {}) {
2628
2686
  if (process.env.NODE_ENV !== "production") {
2629
2687
  validateRenderProps(resolved.diagnostics, resolved, props, recipe);
2630
2688
  }
2631
- return classPipeline(tag, props, className, recipe);
2689
+ return classPipeline(tag, props, className, recipe) || void 0;
2632
2690
  },
2633
2691
  resolveAria(tag, props) {
2634
2692
  return resolveAriaFn(tag, props);
@@ -2845,7 +2903,10 @@ function buildElementProps(props, className) {
2845
2903
  const { role, ...rest } = props;
2846
2904
  return {
2847
2905
  ...normalizeListenerKeys(rest),
2848
- class: className,
2906
+ // Vue's SSR renderer and its client-side patcher disagree on `class: undefined` — SSR's
2907
+ // normalizeClass turns it into '' and still emits class="", while the client patcher
2908
+ // removes the attribute outright. Omitting the key entirely keeps both paths consistent.
2909
+ ...className !== void 0 && { class: className },
2849
2910
  ...isKnownAriaRole(role) && { role }
2850
2911
  };
2851
2912
  }
@@ -2866,7 +2927,10 @@ function validateSlotDirectives(directives, validator) {
2866
2927
  function tryRenderAsChild(state, children, discarded, validator) {
2867
2928
  if (!validateSlotDirectives(state.directives, validator)) return null;
2868
2929
  if (discarded > 0) validator.warnDiscardedChildren(discarded);
2869
- const attrs = { ...pickAttributes(state.props), class: state.className };
2930
+ const attrs = {
2931
+ ...pickAttributes(state.props),
2932
+ ...state.className !== void 0 && { class: state.className }
2933
+ };
2870
2934
  const slottable = extractSlottable(children);
2871
2935
  if (slottable) return slottable.rebuild(cloneVNode(slottable.child, attrs));
2872
2936
  if (children.length === 1 && children[0] !== void 0) {
@@ -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>>;
@@ -216,7 +216,9 @@ type AriaInvalidWithoutFix<M extends string = string> = AriaInvalidBase<M> & {
216
216
  type AriaInvalidResult<M extends string = string> = AriaInvalidWithFix<M> | AriaInvalidWithoutFix<M>;
217
217
  type AriaResult = ValidResult | AriaInvalidResult;
218
218
 
219
- type AriaRule<C extends AriaContext = AriaContext> = (context: C) => readonly AriaResult[];
219
+ type AriaRule<C extends AriaContext = AriaContext> = ((context: C) => readonly AriaResult[]) & {
220
+ readonly readsProps?: readonly string[];
221
+ };
220
222
 
221
223
  type PropNormalizer = (props: Readonly<AnyRecord & IntrinsicProps>) => Partial<AnyRecord & IntrinsicProps>;
222
224