praxis-kit 6.1.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.
@@ -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;
@@ -1065,7 +1068,7 @@ var InvariantBase = class {
1065
1068
  };
1066
1069
 
1067
1070
  // ../../lib/contract/src/aria/polymorphic-validator.ts
1068
- var VALID = [{ valid: true }];
1071
+ var NO_VIOLATIONS = [{ valid: true }];
1069
1072
  function isIntrinsicTag(tag) {
1070
1073
  return isString(tag);
1071
1074
  }
@@ -1107,17 +1110,20 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1107
1110
  static #deriveContext(tag, props) {
1108
1111
  if (!isIntrinsicTag(tag)) return { proceed: false, result: { props, violations: [] } };
1109
1112
  const implicitRole = getImplicitRole(tag, props);
1110
- const hasRole2 = implicitRole != null || isString(props.role) && props.role.length > 0;
1113
+ const hasRole2 = isNonNull(implicitRole) || isString(props.role) && props.role.length > 0;
1111
1114
  if (!hasRole2) return { proceed: false, result: { props, violations: [] } };
1112
1115
  const normalized = _AriaPolicyEngine.#normalizeEmptyRole(tag, props);
1113
- if (normalized.normalized) return { proceed: false, result: normalized.result };
1114
- 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;
1115
1119
  return {
1116
1120
  proceed: true,
1117
1121
  tag,
1118
1122
  implicitRole,
1119
1123
  effectiveRole,
1120
- context: { tag, props, implicitRole, effectiveRole }
1124
+ props: workingProps,
1125
+ preExistingViolations,
1126
+ context: { tag, props: workingProps, implicitRole, effectiveRole }
1121
1127
  };
1122
1128
  }
1123
1129
  static #runRules(rules, context) {
@@ -1132,7 +1138,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1132
1138
  } = context;
1133
1139
  const { message, attribute, severity } = result;
1134
1140
  const resolvedMessage = message ?? result.diagnostic?.message;
1135
- const fallbackDiag = resolvedMessage == null ? AriaDiagnostics.invalidRole(role, tag) : void 0;
1141
+ const fallbackDiag = isNonNull(resolvedMessage) ? void 0 : AriaDiagnostics.invalidRole(role, tag);
1136
1142
  violations.push({
1137
1143
  message: resolvedMessage ?? fallbackDiag.message,
1138
1144
  tag,
@@ -1140,8 +1146,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1140
1146
  attribute,
1141
1147
  severity,
1142
1148
  phase: "evaluate",
1143
- ...result.diagnostic != null && { diagnostic: result.diagnostic },
1144
- ...fallbackDiag != null && { diagnostic: fallbackDiag }
1149
+ ...isNonNull(result.diagnostic) && { diagnostic: result.diagnostic },
1150
+ ...isNonNull(fallbackDiag) && { diagnostic: fallbackDiag }
1145
1151
  });
1146
1152
  if (result.fixable) fixes.push(result.fix);
1147
1153
  });
@@ -1149,7 +1155,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1149
1155
  return { violations, fixes };
1150
1156
  }
1151
1157
  static #getRules(context) {
1152
- 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)) {
1153
1159
  return _AriaPolicyEngine.#pipeline;
1154
1160
  }
1155
1161
  return _AriaPolicyEngine.#implicitOnlyRules;
@@ -1157,24 +1163,36 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1157
1163
  static evaluate(tag, props) {
1158
1164
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1159
1165
  if (!derived.proceed) return derived.result;
1160
- const { tag: narrowedTag, implicitRole, context } = derived;
1166
+ const {
1167
+ tag: narrowedTag,
1168
+ implicitRole,
1169
+ context,
1170
+ props: workingProps,
1171
+ preExistingViolations
1172
+ } = derived;
1161
1173
  const { violations, fixes } = _AriaPolicyEngine.#runRules(
1162
1174
  _AriaPolicyEngine.#getRules(context),
1163
1175
  context
1164
1176
  );
1165
- const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, props, fixes);
1166
- return { props: next, violations };
1177
+ const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1178
+ return { props: next, violations: [...preExistingViolations, ...violations] };
1167
1179
  }
1168
1180
  static #evaluateWithRules(tag, props, extraRules) {
1169
1181
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1170
1182
  if (!derived.proceed) return derived.result;
1171
- const { tag: narrowedTag, implicitRole, context } = derived;
1183
+ const {
1184
+ tag: narrowedTag,
1185
+ implicitRole,
1186
+ context,
1187
+ props: workingProps,
1188
+ preExistingViolations
1189
+ } = derived;
1172
1190
  const { violations, fixes } = _AriaPolicyEngine.#runRules(
1173
1191
  [..._AriaPolicyEngine.#getRules(context), ...extraRules],
1174
1192
  context
1175
1193
  );
1176
- const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, props, fixes);
1177
- return { props: next, violations };
1194
+ const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1195
+ return { props: next, violations: [...preExistingViolations, ...violations] };
1178
1196
  }
1179
1197
  report(violations) {
1180
1198
  iterate.forEach(violations, (v) => {
@@ -1183,12 +1201,12 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1183
1201
  else this.warn(d);
1184
1202
  });
1185
1203
  }
1186
- // Cache key covers only the aria-relevant subset of props (tag + role + aria-* attrs).
1187
- // Non-aria props (className, onClick, etc.) do not affect ARIA decisions and are excluded
1188
- // so cache hits survive re-renders that only change non-aria props.
1189
- // Note: #extraRules are NOT included in the key each engine instance has its own Map,
1190
- // so two engines with different rules never share cache entries. If caching ever becomes
1191
- // 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.
1192
1210
  static #createPlanKey(tag, props) {
1193
1211
  if (!isIntrinsicTag(tag)) return null;
1194
1212
  const parts = [tag];
@@ -1204,6 +1222,23 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1204
1222
  if (ariaEntries.length > 0) parts.push(...ariaEntries.sort());
1205
1223
  return parts.join("|");
1206
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
+ }
1207
1242
  static #computePlan(inputProps, resultProps) {
1208
1243
  const removals = /* @__PURE__ */ new Set();
1209
1244
  const updates = {};
@@ -1227,7 +1262,12 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1227
1262
  return next;
1228
1263
  }
1229
1264
  validate(tag, props) {
1230
- 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
+ }
1231
1271
  if (!isNull(key)) {
1232
1272
  const cached = this.#planCache.get(key);
1233
1273
  if (cached !== void 0) {
@@ -1317,7 +1357,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1317
1357
  implicitRole
1318
1358
  }) {
1319
1359
  const role = props.role;
1320
- if (!implicitRole || !role || role === implicitRole) return VALID;
1360
+ if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS;
1321
1361
  if (isStrongImplicitRole(tag) && role === "region") {
1322
1362
  return [
1323
1363
  {
@@ -1329,11 +1369,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1329
1369
  }
1330
1370
  ];
1331
1371
  }
1332
- return VALID;
1372
+ return NO_VIOLATIONS;
1333
1373
  }
1334
1374
  static #checkRedundantRole({ tag, props, implicitRole }) {
1335
1375
  const role = props.role;
1336
- if (!implicitRole || !role || role !== implicitRole) return VALID;
1376
+ if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS;
1337
1377
  return [
1338
1378
  {
1339
1379
  valid: false,
@@ -1346,8 +1386,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1346
1386
  }
1347
1387
  static #checkStandaloneRegion({ tag, props, implicitRole }) {
1348
1388
  const role = props.role;
1349
- if (role !== "region") return VALID;
1350
- if (!isStandaloneTag(tag)) return VALID;
1389
+ if (role !== "region") return NO_VIOLATIONS;
1390
+ if (!isStandaloneTag(tag)) return NO_VIOLATIONS;
1351
1391
  return [
1352
1392
  {
1353
1393
  valid: false,
@@ -1363,7 +1403,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1363
1403
  props,
1364
1404
  effectiveRole
1365
1405
  }) {
1366
- if (effectiveRole === "none" || effectiveRole === "presentation") return VALID;
1406
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1367
1407
  const results = [];
1368
1408
  iterate.forEachEntry(props, (key) => {
1369
1409
  if (!key.startsWith("aria-")) return;
@@ -1481,12 +1521,12 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1481
1521
  }
1482
1522
  }
1483
1523
  static #checkAriaAttributeValues({ props, effectiveRole }) {
1484
- if (effectiveRole === "none" || effectiveRole === "presentation") return VALID;
1524
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1485
1525
  const results = [];
1486
1526
  iterate.forEachEntry(props, (key, value) => {
1487
1527
  if (!key.startsWith("aria-")) return;
1488
1528
  const type = _AriaPolicyEngine.#ARIA_VALUE_TYPES.get(key);
1489
- if (type == null) return;
1529
+ if (!isNonNull(type)) return;
1490
1530
  if (_AriaPolicyEngine.#isValidAriaValue(value, type)) return;
1491
1531
  results.push({
1492
1532
  valid: false,
@@ -1517,13 +1557,13 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1517
1557
  props,
1518
1558
  effectiveRole
1519
1559
  }) {
1520
- if (effectiveRole === "none" || effectiveRole === "presentation") return VALID;
1560
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1521
1561
  const implicitLevel = _AriaPolicyEngine.#HEADING_IMPLICIT_LEVELS.get(tag);
1522
- if (implicitLevel == null) return VALID;
1562
+ if (!isNonNull(implicitLevel)) return NO_VIOLATIONS;
1523
1563
  const raw = props["aria-level"];
1524
- if (raw == null) return VALID;
1564
+ if (!isNonNull(raw)) return NO_VIOLATIONS;
1525
1565
  const n = typeof raw === "number" ? raw : typeof raw === "string" ? parseInt(raw, 10) : NaN;
1526
- if (!Number.isFinite(n) || n !== implicitLevel) return VALID;
1566
+ if (!Number.isFinite(n) || n !== implicitLevel) return NO_VIOLATIONS;
1527
1567
  return [
1528
1568
  {
1529
1569
  valid: false,
@@ -1546,9 +1586,10 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1546
1586
  props,
1547
1587
  effectiveRole
1548
1588
  }) {
1549
- if (!effectiveRole || !_AriaPolicyEngine.#NAME_REQUIRED_ROLES.has(effectiveRole)) return VALID;
1550
- if ("aria-label" in props || "aria-labelledby" in props) return VALID;
1551
- 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;
1552
1593
  return [
1553
1594
  {
1554
1595
  valid: false,
@@ -1571,9 +1612,9 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1571
1612
  props,
1572
1613
  effectiveRole
1573
1614
  }) {
1574
- if (!effectiveRole) return VALID;
1615
+ if (!effectiveRole) return NO_VIOLATIONS;
1575
1616
  const required = _AriaPolicyEngine.#REQUIRED_PROPERTIES.get(effectiveRole);
1576
- if (required == null) return VALID;
1617
+ if (!isNonNull(required)) return NO_VIOLATIONS;
1577
1618
  const results = [];
1578
1619
  iterate.forEach(required, (attr) => {
1579
1620
  if (attr in props) return;
@@ -1597,12 +1638,12 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1597
1638
  ]);
1598
1639
  // WAI-ARIA 1.2 §6.6: aria-hidden="true" must not be placed on focusable elements.
1599
1640
  static #checkAriaHiddenOnFocusable({ tag, props }) {
1600
- if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return VALID;
1641
+ if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return NO_VIOLATIONS;
1601
1642
  const isInteractive = _AriaPolicyEngine.#INTERACTIVE_TAGS.has(tag);
1602
1643
  if (!isInteractive) {
1603
1644
  const tabindex = props.tabindex;
1604
1645
  const n = typeof tabindex === "number" ? tabindex : typeof tabindex === "string" ? parseInt(tabindex, 10) : NaN;
1605
- if (!Number.isFinite(n) || n < 0) return VALID;
1646
+ if (!Number.isFinite(n) || n < 0) return NO_VIOLATIONS;
1606
1647
  }
1607
1648
  return [
1608
1649
  {
@@ -1621,7 +1662,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1621
1662
  props,
1622
1663
  effectiveRole
1623
1664
  }) {
1624
- if (effectiveRole !== "none" && effectiveRole !== "presentation") return VALID;
1665
+ if (effectiveRole !== "none" && effectiveRole !== "presentation") return NO_VIOLATIONS;
1625
1666
  const results = [];
1626
1667
  iterate.forEachEntry(props, (key) => {
1627
1668
  if (!key.startsWith("aria-")) return;
@@ -1645,10 +1686,10 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1645
1686
  ["timer", "off"]
1646
1687
  ]);
1647
1688
  static #checkMissingLiveRegion({ effectiveRole, props }) {
1648
- if (!effectiveRole) return VALID;
1689
+ if (!effectiveRole) return NO_VIOLATIONS;
1649
1690
  const impliedLive = _AriaPolicyEngine.#LIVE_REGION_ROLES.get(effectiveRole);
1650
- if (!impliedLive) return VALID;
1651
- if ("aria-live" in props) return VALID;
1691
+ if (!impliedLive) return NO_VIOLATIONS;
1692
+ if ("aria-live" in props) return NO_VIOLATIONS;
1652
1693
  const injectLive = {
1653
1694
  kind: `injectLive:${effectiveRole}`,
1654
1695
  apply: (ctx) => ({
@@ -1668,8 +1709,9 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1668
1709
  ];
1669
1710
  }
1670
1711
  static #checkMissingAtomic({ effectiveRole, props }) {
1671
- if (!effectiveRole || !_AriaPolicyEngine.#LIVE_REGION_ROLES.has(effectiveRole)) return VALID;
1672
- 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;
1673
1715
  return [
1674
1716
  {
1675
1717
  valid: false,
@@ -1693,8 +1735,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1693
1735
  };
1694
1736
  static #checkInvalidAriaRelevant({ props }) {
1695
1737
  const relevant = props["aria-relevant"];
1696
- if (relevant === void 0) return VALID;
1697
- if (typeof relevant !== "string") return VALID;
1738
+ if (relevant === void 0) return NO_VIOLATIONS;
1739
+ if (typeof relevant !== "string") return NO_VIOLATIONS;
1698
1740
  const tokens = relevant.trim().split(/\s+/);
1699
1741
  const invalid = tokens.filter((t) => !_AriaPolicyEngine.#VALID_RELEVANT_TOKENS.has(t));
1700
1742
  if (invalid.length > 0) {
@@ -1721,7 +1763,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1721
1763
  }
1722
1764
  ];
1723
1765
  }
1724
- return VALID;
1766
+ return NO_VIOLATIONS;
1725
1767
  }
1726
1768
  };
1727
1769
 
@@ -2422,7 +2464,7 @@ function createClassPipeline(resolved) {
2422
2464
  const staticClasses = staticResolver.resolve(tag, recipe !== void 0);
2423
2465
  const variantClasses = variantResolver.resolve({ props, recipe });
2424
2466
  if (!className)
2425
- return staticClasses && variantClasses ? `${staticClasses} ${variantClasses}` : staticClasses || variantClasses;
2467
+ return (staticClasses && variantClasses ? `${staticClasses} ${variantClasses}` : staticClasses || variantClasses) || void 0;
2426
2468
  return cn(staticClasses, variantClasses, className);
2427
2469
  };
2428
2470
  }
@@ -2633,7 +2675,7 @@ function createPolymorphic2(options = {}) {
2633
2675
  if (process.env.NODE_ENV !== "production") {
2634
2676
  validateRenderProps(resolved.diagnostics, resolved, props, recipe);
2635
2677
  }
2636
- return classPipeline(tag, props, className, recipe);
2678
+ return classPipeline(tag, props, className, recipe) || void 0;
2637
2679
  },
2638
2680
  resolveAria(tag, props) {
2639
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