praxis-kit 6.5.0 → 6.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -232,6 +232,16 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
232
232
  */
233
233
  readonly diagnostics?: Diagnostics | DiagnosticsMode;
234
234
  readonly aria?: readonly AriaRule[];
235
+ /**
236
+ * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
237
+ * (`AriaRule`'s `readsProps`, fixable `AriaFix` results) but have no
238
+ * relationship to ARIA semantics — an HTML fact or a security check like a
239
+ * dangerous-URL-scheme guard, for example. Evaluated together with `aria`
240
+ * (both run through the same engine, merged into one rule set) — this is a
241
+ * separate bucket purely so a non-ARIA rule doesn't have to sit under the
242
+ * misleading `aria` name to get the machinery it needs.
243
+ */
244
+ readonly rules?: readonly AriaRule[];
235
245
  readonly children?: readonly ChildRuleInput[];
236
246
  /**
237
247
  * When true, only children matching a `children` rule (or text, per `allowText`)
package/dist/lit/index.js CHANGED
@@ -25,6 +25,9 @@ function isNull(value) {
25
25
  function isNonNull(value) {
26
26
  return value != null;
27
27
  }
28
+ function isNullish(value) {
29
+ return isNull(value) || value === void 0;
30
+ }
28
31
 
29
32
  // ../../lib/primitive/src/utils/type-guards.ts
30
33
  function isObject(value, excludeArrays = false) {
@@ -526,20 +529,29 @@ function isAriaAttributeValidForRole(attr, role) {
526
529
  }
527
530
 
528
531
  // ../../lib/primitive/src/guards/aria/is-aria-role.ts
532
+ function lookupImplicitRole(tag) {
533
+ return IMPLICIT_ROLE_RECORD[tag];
534
+ }
529
535
  function isStrongImplicitRole(tag) {
530
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
531
- return STRONG_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
536
+ const role = lookupImplicitRole(tag);
537
+ return !isNullish(role) && STRONG_ROLES_SET.has(role);
532
538
  }
533
- function isStandaloneTag(tag) {
534
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
535
- return STANDALONE_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
539
+ function hasStandaloneRole(tag) {
540
+ const role = lookupImplicitRole(tag);
541
+ return !isNullish(role) && STANDALONE_ROLES_SET.has(role);
536
542
  }
537
- function getInputImplicitRole(type) {
538
- if (!isString(type) || !(type in INPUT_TYPE_ROLE_MAP)) return void 0;
539
- return INPUT_TYPE_ROLE_MAP[type];
543
+ var LIST_ELIGIBLE_INPUT_TYPES = /* @__PURE__ */ new Set(["text", "search", "tel", "url", "email"]);
544
+ function getInputImplicitRole(type, list) {
545
+ if (!isString(type)) return void 0;
546
+ const role = INPUT_TYPE_ROLE_MAP[type];
547
+ if (!role) return void 0;
548
+ if (!isNullish(list) && LIST_ELIGIBLE_INPUT_TYPES.has(type)) {
549
+ return "combobox";
550
+ }
551
+ return role;
540
552
  }
541
553
  function getConditionalImplicitRole(tag, ariaLabel, ariaLabelledBy) {
542
- const isNamed = isString(ariaLabel) || isString(ariaLabelledBy);
554
+ const isNamed = isString(ariaLabel) && ariaLabel.trim().length > 0 || isString(ariaLabelledBy) && ariaLabelledBy.trim().length > 0;
543
555
  if (!isNamed) return void 0;
544
556
  if (tag === "section") return "region";
545
557
  if (tag === "form") return "form";
@@ -585,7 +597,7 @@ function isTag(...args) {
585
597
  // ../../lib/contract/src/aria/aria-role-policy.ts
586
598
  function getImplicitRole(tag, props) {
587
599
  if (tag in IMPLICIT_ROLE_RECORD) return IMPLICIT_ROLE_RECORD[tag];
588
- if (tag === "input") return getInputImplicitRole(props?.type);
600
+ if (tag === "input") return getInputImplicitRole(props?.type, props?.list);
589
601
  if (tag === "img") return props?.alt === "" ? "none" : "img";
590
602
  if (tag === "section" || tag === "form") {
591
603
  return getConditionalImplicitRole(tag, props?.["aria-label"], props?.["aria-labelledby"]);
@@ -1228,7 +1240,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1228
1240
  if (!isIntrinsicTag(tag)) return { proceed: false, result: { props, violations: [] } };
1229
1241
  const implicitRole = getImplicitRole(tag, props);
1230
1242
  const hasRole = isNonNull(implicitRole) || isString(props.role) && props.role.length > 0;
1231
- if (!hasRole) return { proceed: false, result: { props, violations: [] } };
1232
1243
  const normalized = _AriaPolicyEngine.#normalizeEmptyRole(tag, props);
1233
1244
  const workingProps = normalized.normalized ? normalized.result.props : props;
1234
1245
  const preExistingViolations = normalized.normalized ? normalized.result.violations : [];
@@ -1238,6 +1249,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1238
1249
  tag,
1239
1250
  implicitRole,
1240
1251
  effectiveRole,
1252
+ hasRole,
1241
1253
  props: workingProps,
1242
1254
  preExistingViolations,
1243
1255
  context: { tag, props: workingProps, implicitRole, effectiveRole }
@@ -1281,6 +1293,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1281
1293
  static evaluate(tag, props) {
1282
1294
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1283
1295
  if (!derived.proceed) return derived.result;
1296
+ if (!derived.hasRole)
1297
+ return { props: derived.props, violations: [...derived.preExistingViolations] };
1284
1298
  const {
1285
1299
  tag: narrowedTag,
1286
1300
  implicitRole,
@@ -1305,10 +1319,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1305
1319
  props: workingProps,
1306
1320
  preExistingViolations
1307
1321
  } = derived;
1308
- const { violations, fixes } = _AriaPolicyEngine.#runRules(
1309
- [..._AriaPolicyEngine.#getRules(context), ...extraRules],
1310
- context
1311
- );
1322
+ const rules = derived.hasRole ? [..._AriaPolicyEngine.#getRules(context), ...extraRules] : extraRules;
1323
+ const { violations, fixes } = _AriaPolicyEngine.#runRules(rules, context);
1312
1324
  const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1313
1325
  return { props: next, violations: [...preExistingViolations, ...violations] };
1314
1326
  }
@@ -1507,7 +1519,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1507
1519
  static #checkStandaloneRegion({ tag, props, implicitRole }) {
1508
1520
  const role = props.role;
1509
1521
  if (role !== "region") return NO_VIOLATIONS2;
1510
- if (!isStandaloneTag(tag)) return NO_VIOLATIONS2;
1522
+ if (!hasStandaloneRole(tag)) return NO_VIOLATIONS2;
1511
1523
  const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
1512
1524
  return [
1513
1525
  {
@@ -2997,6 +3009,11 @@ function composeNormalizers(normalizers, fn) {
2997
3009
  function whenDefined(key, value) {
2998
3010
  return value === void 0 ? {} : { [key]: value };
2999
3011
  }
3012
+ function mergeAriaRules(aria, rules) {
3013
+ if (!aria?.length) return rules;
3014
+ if (!rules?.length) return aria;
3015
+ return [...aria, ...rules];
3016
+ }
3000
3017
  function resolveFactoryOptions(options = {}) {
3001
3018
  const { styling, enforcement } = options;
3002
3019
  const composedNormalizeFn = composeNormalizers(enforcement?.props, options.normalize);
@@ -3017,7 +3034,7 @@ function resolveFactoryOptions(options = {}) {
3017
3034
  ...whenDefined("defaultVariants", styling?.defaults),
3018
3035
  ...whenDefined("compoundVariants", styling?.compounds),
3019
3036
  ...whenDefined("normalizeFn", composedNormalizeFn),
3020
- ...whenDefined("ariaRules", enforcement?.aria),
3037
+ ...whenDefined("ariaRules", mergeAriaRules(enforcement?.aria, enforcement?.rules)),
3021
3038
  ...whenDefined("childRules", enforcement?.children),
3022
3039
  ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
3023
3040
  ...whenDefined("allowText", enforcement?.allowText),
@@ -249,6 +249,16 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
249
249
  */
250
250
  readonly diagnostics?: Diagnostics | DiagnosticsMode;
251
251
  readonly aria?: readonly AriaRule[];
252
+ /**
253
+ * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
254
+ * (`AriaRule`'s `readsProps`, fixable `AriaFix` results) but have no
255
+ * relationship to ARIA semantics — an HTML fact or a security check like a
256
+ * dangerous-URL-scheme guard, for example. Evaluated together with `aria`
257
+ * (both run through the same engine, merged into one rule set) — this is a
258
+ * separate bucket purely so a non-ARIA rule doesn't have to sit under the
259
+ * misleading `aria` name to get the machinery it needs.
260
+ */
261
+ readonly rules?: readonly AriaRule[];
252
262
  readonly children?: readonly ChildRuleInput[];
253
263
  /**
254
264
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -337,6 +347,15 @@ type PolymorphicWithAsChild<G extends PolymorphicGenerics, TAs extends ElementTy
337
347
  type PolymorphicComponent<G extends PolymorphicGenerics> = {
338
348
  <TAs extends ElementType = DefaultOf<G>>(props: PolymorphicWithAsChild<G, TAs>): AnyVNode;
339
349
  <TAs extends ElementType = DefaultOf<G>>(props: PolymorphicProps<G, TAs>): AnyVNode;
350
+ /**
351
+ * Non-generic fallback overload used for type extraction.
352
+ *
353
+ * TypeScript resolves conditional types such as
354
+ * `ComponentProps<typeof Component>` against only the final overload.
355
+ * Anchoring that overload to the default element preserves correct prop
356
+ * inference for tools such as Storybook and `ComponentProps`.
357
+ */
358
+ (props: PolymorphicProps<G, DefaultOf<G>>): AnyVNode;
340
359
  displayName?: string;
341
360
  };
342
361
 
@@ -23,6 +23,9 @@ function isNull(value) {
23
23
  function isNonNull(value) {
24
24
  return value != null;
25
25
  }
26
+ function isNullish(value) {
27
+ return isNull(value) || value === void 0;
28
+ }
26
29
 
27
30
  // ../../lib/primitive/src/utils/type-guards.ts
28
31
  function isObject(value, excludeArrays = false) {
@@ -637,20 +640,29 @@ function isAriaAttributeValidForRole(attr, role) {
637
640
  }
638
641
 
639
642
  // ../../lib/primitive/src/guards/aria/is-aria-role.ts
643
+ function lookupImplicitRole(tag) {
644
+ return IMPLICIT_ROLE_RECORD[tag];
645
+ }
640
646
  function isStrongImplicitRole(tag) {
641
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
642
- return STRONG_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
647
+ const role = lookupImplicitRole(tag);
648
+ return !isNullish(role) && STRONG_ROLES_SET.has(role);
643
649
  }
644
- function isStandaloneTag(tag) {
645
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
646
- return STANDALONE_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
650
+ function hasStandaloneRole(tag) {
651
+ const role = lookupImplicitRole(tag);
652
+ return !isNullish(role) && STANDALONE_ROLES_SET.has(role);
647
653
  }
648
- function getInputImplicitRole(type) {
649
- if (!isString(type) || !(type in INPUT_TYPE_ROLE_MAP)) return void 0;
650
- return INPUT_TYPE_ROLE_MAP[type];
654
+ var LIST_ELIGIBLE_INPUT_TYPES = /* @__PURE__ */ new Set(["text", "search", "tel", "url", "email"]);
655
+ function getInputImplicitRole(type, list) {
656
+ if (!isString(type)) return void 0;
657
+ const role = INPUT_TYPE_ROLE_MAP[type];
658
+ if (!role) return void 0;
659
+ if (!isNullish(list) && LIST_ELIGIBLE_INPUT_TYPES.has(type)) {
660
+ return "combobox";
661
+ }
662
+ return role;
651
663
  }
652
664
  function getConditionalImplicitRole(tag, ariaLabel, ariaLabelledBy) {
653
- const isNamed = isString(ariaLabel) || isString(ariaLabelledBy);
665
+ const isNamed = isString(ariaLabel) && ariaLabel.trim().length > 0 || isString(ariaLabelledBy) && ariaLabelledBy.trim().length > 0;
654
666
  if (!isNamed) return void 0;
655
667
  if (tag === "section") return "region";
656
668
  if (tag === "form") return "form";
@@ -722,7 +734,7 @@ function defineContractComponent(options) {
722
734
  // ../../lib/contract/src/aria/aria-role-policy.ts
723
735
  function getImplicitRole(tag, props) {
724
736
  if (tag in IMPLICIT_ROLE_RECORD) return IMPLICIT_ROLE_RECORD[tag];
725
- if (tag === "input") return getInputImplicitRole(props?.type);
737
+ if (tag === "input") return getInputImplicitRole(props?.type, props?.list);
726
738
  if (tag === "img") return props?.alt === "" ? "none" : "img";
727
739
  if (tag === "section" || tag === "form") {
728
740
  return getConditionalImplicitRole(tag, props?.["aria-label"], props?.["aria-labelledby"]);
@@ -1411,7 +1423,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1411
1423
  if (!isIntrinsicTag(tag)) return { proceed: false, result: { props, violations: [] } };
1412
1424
  const implicitRole = getImplicitRole(tag, props);
1413
1425
  const hasRole2 = isNonNull(implicitRole) || isString(props.role) && props.role.length > 0;
1414
- if (!hasRole2) return { proceed: false, result: { props, violations: [] } };
1415
1426
  const normalized = _AriaPolicyEngine.#normalizeEmptyRole(tag, props);
1416
1427
  const workingProps = normalized.normalized ? normalized.result.props : props;
1417
1428
  const preExistingViolations = normalized.normalized ? normalized.result.violations : [];
@@ -1421,6 +1432,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1421
1432
  tag,
1422
1433
  implicitRole,
1423
1434
  effectiveRole,
1435
+ hasRole: hasRole2,
1424
1436
  props: workingProps,
1425
1437
  preExistingViolations,
1426
1438
  context: { tag, props: workingProps, implicitRole, effectiveRole }
@@ -1464,6 +1476,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1464
1476
  static evaluate(tag, props) {
1465
1477
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1466
1478
  if (!derived.proceed) return derived.result;
1479
+ if (!derived.hasRole)
1480
+ return { props: derived.props, violations: [...derived.preExistingViolations] };
1467
1481
  const {
1468
1482
  tag: narrowedTag,
1469
1483
  implicitRole,
@@ -1488,10 +1502,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1488
1502
  props: workingProps,
1489
1503
  preExistingViolations
1490
1504
  } = derived;
1491
- const { violations, fixes } = _AriaPolicyEngine.#runRules(
1492
- [..._AriaPolicyEngine.#getRules(context), ...extraRules],
1493
- context
1494
- );
1505
+ const rules = derived.hasRole ? [..._AriaPolicyEngine.#getRules(context), ...extraRules] : extraRules;
1506
+ const { violations, fixes } = _AriaPolicyEngine.#runRules(rules, context);
1495
1507
  const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1496
1508
  return { props: next, violations: [...preExistingViolations, ...violations] };
1497
1509
  }
@@ -1690,7 +1702,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1690
1702
  static #checkStandaloneRegion({ tag, props, implicitRole }) {
1691
1703
  const role = props.role;
1692
1704
  if (role !== "region") return NO_VIOLATIONS2;
1693
- if (!isStandaloneTag(tag)) return NO_VIOLATIONS2;
1705
+ if (!hasStandaloneRole(tag)) return NO_VIOLATIONS2;
1694
1706
  const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
1695
1707
  return [
1696
1708
  {
@@ -3180,6 +3192,11 @@ function composeNormalizers(normalizers, fn) {
3180
3192
  function whenDefined(key, value) {
3181
3193
  return value === void 0 ? {} : { [key]: value };
3182
3194
  }
3195
+ function mergeAriaRules(aria, rules) {
3196
+ if (!aria?.length) return rules;
3197
+ if (!rules?.length) return aria;
3198
+ return [...aria, ...rules];
3199
+ }
3183
3200
  function resolveFactoryOptions(options = {}) {
3184
3201
  const { styling, enforcement } = options;
3185
3202
  const composedNormalizeFn = composeNormalizers(enforcement?.props, options.normalize);
@@ -3200,7 +3217,7 @@ function resolveFactoryOptions(options = {}) {
3200
3217
  ...whenDefined("defaultVariants", styling?.defaults),
3201
3218
  ...whenDefined("compoundVariants", styling?.compounds),
3202
3219
  ...whenDefined("normalizeFn", composedNormalizeFn),
3203
- ...whenDefined("ariaRules", enforcement?.aria),
3220
+ ...whenDefined("ariaRules", mergeAriaRules(enforcement?.aria, enforcement?.rules)),
3204
3221
  ...whenDefined("childRules", enforcement?.children),
3205
3222
  ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
3206
3223
  ...whenDefined("allowText", enforcement?.allowText),
@@ -1,5 +1,5 @@
1
- import { U as UnknownProps, E as ElementType, a as EmptyRecord, V as VariantMap, R as RecipeMap, A as AnyClassPluginFactory, b as ReactFactoryOptions, P as PolymorphicComponent, c as PolymorphicGenerics, d as ExtractPluginProps } from '../react-options-DoFNqNdY.js';
2
- export { e as AnyFactoryOptions, f as ElementRef, F as FactoryOptions, g as PolymorphicProps, h as PolymorphicWithAsChild, i as PolymorphicWithRender, j as RenderCallbackProps, S as Slottable, k as SlottableProps, m as composeRefs, l as defineContractComponent, m as mergeRefs } from '../react-options-DoFNqNdY.js';
1
+ import { U as UnknownProps, E as ElementType, a as EmptyRecord, V as VariantMap, R as RecipeMap, A as AnyClassPluginFactory, b as ReactFactoryOptions, P as PolymorphicComponent, c as PolymorphicGenerics, d as ExtractPluginProps } from '../react-options-BUBVohU6.js';
2
+ export { e as AnyFactoryOptions, f as ElementRef, F as FactoryOptions, g as PolymorphicProps, h as PolymorphicWithAsChild, i as PolymorphicWithRender, j as RenderCallbackProps, S as Slottable, k as SlottableProps, m as composeRefs, l as defineContractComponent, m as mergeRefs } from '../react-options-BUBVohU6.js';
3
3
  import * as react from 'react';
4
4
  import { ReactElement, Ref } from 'react';
5
5
  import 'type-fest';
@@ -12,7 +12,7 @@ import {
12
12
  makeCloneSlotChild,
13
13
  mergeRefs,
14
14
  render
15
- } from "../chunk-AM5J6PY3.js";
15
+ } from "../chunk-OCG5VM4H.js";
16
16
 
17
17
  // ../../adapters/react/src/current/slot/composeRefs.ts
18
18
  function getChildRef(element) {
@@ -1,5 +1,5 @@
1
- import { E as ElementType, U as UnknownProps, a as EmptyRecord, V as VariantMap, R as RecipeMap, A as AnyClassPluginFactory, b as ReactFactoryOptions, P as PolymorphicComponent, c as PolymorphicGenerics, d as ExtractPluginProps } from '../react-options-DoFNqNdY.js';
2
- export { e as AnyFactoryOptions, f as ElementRef, F as FactoryOptions, g as PolymorphicProps, h as PolymorphicWithAsChild, i as PolymorphicWithRender, j as RenderCallbackProps, S as Slottable, k as SlottableProps, l as defineContractComponent, m as mergeRefs } from '../react-options-DoFNqNdY.js';
1
+ import { E as ElementType, U as UnknownProps, a as EmptyRecord, V as VariantMap, R as RecipeMap, A as AnyClassPluginFactory, b as ReactFactoryOptions, P as PolymorphicComponent, c as PolymorphicGenerics, d as ExtractPluginProps } from '../react-options-BUBVohU6.js';
2
+ export { e as AnyFactoryOptions, f as ElementRef, F as FactoryOptions, g as PolymorphicProps, h as PolymorphicWithAsChild, i as PolymorphicWithRender, j as RenderCallbackProps, S as Slottable, k as SlottableProps, l as defineContractComponent, m as mergeRefs } from '../react-options-BUBVohU6.js';
3
3
  import * as react from 'react';
4
4
  import 'type-fest';
5
5
  import '../_shared/diagnostics.js';
@@ -13,7 +13,7 @@ import {
13
13
  makeCloneSlotChild,
14
14
  mergeRefs,
15
15
  render
16
- } from "../chunk-AM5J6PY3.js";
16
+ } from "../chunk-OCG5VM4H.js";
17
17
 
18
18
  // ../../adapters/react/src/legacy/create-contract-component.ts
19
19
  import { forwardRef as forwardRef2 } from "react";
@@ -250,6 +250,16 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
250
250
  */
251
251
  readonly diagnostics?: Diagnostics | DiagnosticsMode;
252
252
  readonly aria?: readonly AriaRule[];
253
+ /**
254
+ * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
255
+ * (`AriaRule`'s `readsProps`, fixable `AriaFix` results) but have no
256
+ * relationship to ARIA semantics — an HTML fact or a security check like a
257
+ * dangerous-URL-scheme guard, for example. Evaluated together with `aria`
258
+ * (both run through the same engine, merged into one rule set) — this is a
259
+ * separate bucket purely so a non-ARIA rule doesn't have to sit under the
260
+ * misleading `aria` name to get the machinery it needs.
261
+ */
262
+ readonly rules?: readonly AriaRule[];
253
263
  readonly children?: readonly ChildRuleInput[];
254
264
  /**
255
265
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -339,25 +349,27 @@ type SlotComponent = ComponentType<UnknownProps>;
339
349
  type RenderCallbackProps = Readonly<AnyRecord>;
340
350
 
341
351
  /**
342
- * Maps a polymorphic element type to the instance type exposed through `ref`.
352
+ * Resolves the instance type exposed through `ref` for a polymorphic
353
+ * element.
343
354
  *
344
- * Intrinsic HTML elements resolve to their corresponding DOM element type;
345
- * custom React components currently fall back to `unknown`.
355
+ * Intrinsic HTML elements map to their corresponding DOM element type;
356
+ * custom React components currently resolve to `unknown`.
346
357
  */
347
358
  type ElementRef<T extends ElementType> = T extends IntrinsicTag ? HTMLElementTagNameMap[T] : unknown;
348
359
  /**
349
- * React's intrinsic JSX props for a given element.
360
+ * React's intrinsic JSX props for an element type.
350
361
  *
351
- * Custom components intentionally fall back to `UnknownProps`; the component's
352
- * own prop model defines their accepted props instead.
362
+ * Custom components intentionally resolve to `UnknownProps`; their own
363
+ * prop definitions determine the accepted props.
353
364
  */
354
365
  type IntrinsicJSXProps<T extends ElementType> = T extends IntrinsicTag ? JSX.IntrinsicElements[T] : UnknownProps;
355
366
  /**
356
- * Removes index signatures while preserving explicitly named properties.
367
+ * Removes index signatures while preserving explicitly declared
368
+ * properties.
357
369
  *
358
- * This prevents generic fallback constraints such as `Record<string, unknown>`
359
- * from collapsing `keyof T` to `string`, which would cause
360
- * `Omit<IntrinsicJSXProps<T>, keyof ...>` to remove every intrinsic prop.
370
+ * Prevents broad index signatures (for example `Record<string, unknown>`)
371
+ * from causing `keyof T` to become `string`, which would otherwise erase
372
+ * every intrinsic prop during `Omit`.
361
373
  */
362
374
  type StripIndexSignature<T> = {
363
375
  [K in keyof T as string extends K ? never : K]: T[K];
@@ -367,18 +379,16 @@ type ComponentProps<G extends PolymorphicGenerics> = StripIndexSignature<PropsOf
367
379
  /** Variant props generated from the component's variant definitions. */
368
380
  type ComponentVariants<G extends PolymorphicGenerics> = StripIndexSignature<VariantProps<VariantsOf<G>>>;
369
381
  /**
370
- * Props owned by the component itself.
382
+ * Props defined by the component itself.
371
383
  *
372
- * These take precedence over intrinsic HTML props when names overlap.
384
+ * These override intrinsic JSX props with the same name.
373
385
  */
374
386
  type OwnedProps<G extends PolymorphicGenerics> = ComponentProps<G> & ComponentVariants<G>;
375
387
  /**
376
- * Polymorphic rendering controls.
388
+ * Props that control how the component renders.
377
389
  *
378
- * These describe *how* the component renders rather than the data it owns.
379
- *
380
- * `children` and `asChild` are intentionally omitted so each render mode
381
- * can provide its own stricter contract.
390
+ * `children` and `asChild` are intentionally omitted so each render
391
+ * strategy can define its own contract.
382
392
  */
383
393
  type PolymorphicControlProps<G extends PolymorphicGenerics, TAs extends ElementType> = {
384
394
  /**
@@ -398,22 +408,21 @@ type PolymorphicControlProps<G extends PolymorphicGenerics, TAs extends ElementT
398
408
  ref?: Ref<ElementRef<TAs>>;
399
409
  };
400
410
  /**
401
- * Complete set of props owned by the component.
411
+ * All props reserved by the polymorphic component.
402
412
  *
403
- * Used primarily as the exclusion list when inheriting intrinsic JSX props.
413
+ * Used primarily to exclude conflicting intrinsic JSX props.
404
414
  */
405
415
  type ControlProps<G extends PolymorphicGenerics, TAs extends ElementType> = OwnedProps<G> & PolymorphicControlProps<G, TAs>;
406
416
  /**
407
- * Intrinsic JSX props after removing every prop owned by the component.
417
+ * Intrinsic JSX props after removing every reserved component prop.
408
418
  *
409
- * Component props always win over intrinsic props with the same name.
419
+ * Component-defined props always take precedence.
410
420
  */
411
421
  type IntrinsicPropsWithoutOwned<G extends PolymorphicGenerics, TAs extends ElementType> = Omit<IntrinsicJSXProps<TAs>, keyof ControlProps<G, TAs> | 'children'>;
412
422
  /**
413
- * Base props shared by every render mode.
423
+ * Props shared by every rendering strategy.
414
424
  *
415
- * Render modes contribute only their discriminants (`asChild`, `render`,
416
- * `children`, etc.).
425
+ * Each render mode contributes only its discriminating props.
417
426
  */
418
427
  type BaseProps<G extends PolymorphicGenerics, TAs extends ElementType> = IntrinsicPropsWithoutOwned<G, TAs> & ControlProps<G, TAs>;
419
428
  /** Standard rendering (`asChild` absent or false). */
@@ -433,12 +442,9 @@ type SlotRenderMode = {
433
442
  children: ReactElement | NonEmptyTuple<ReactElement>;
434
443
  };
435
444
  /**
436
- * Render callback.
437
- *
438
- * The callback receives fully resolved props (classes, refs, filtered props)
439
- * and is responsible for rendering the target element.
445
+ * Render callback mode.
440
446
  *
441
- * This provides the flexibility of `asChild` without `cloneElement`.
447
+ * Receives the fully resolved props and returns the rendered element.
442
448
  */
443
449
  type CallbackRenderMode = {
444
450
  render: (props: RenderCallbackProps) => ReactElement;
@@ -464,16 +470,26 @@ type PolymorphicWithRender<G extends PolymorphicGenerics, TAs extends ElementTyp
464
470
  /**
465
471
  * A polymorphic React component.
466
472
  *
467
- * Overloads form a discriminated union:
473
+ * Overloads provide three rendering strategies:
468
474
  *
469
- * `render` render callback
470
- * `asChild` Slot rendering
471
- * otherwise → normal rendering
475
+ * - `render` render callback
476
+ * - `asChild` slot rendering
477
+ * - default — standard polymorphic rendering
472
478
  */
473
479
  type PolymorphicComponent<G extends PolymorphicGenerics> = {
474
480
  <TAs extends ElementType = DefaultOf<G>>(props: PolymorphicWithRender<G, TAs>): ReactElement;
475
481
  <TAs extends ElementType = DefaultOf<G>>(props: PolymorphicWithAsChild<G, TAs>): ReactElement;
476
482
  <TAs extends ElementType = DefaultOf<G>>(props: PolymorphicProps<G, TAs>): ReactElement;
483
+ /**
484
+ * Non-generic fallback overload used for type extraction.
485
+ *
486
+ * TypeScript resolves conditional types such as
487
+ * `React.ComponentProps<typeof Component>` against only the final
488
+ * overload. Anchoring that overload to the default element preserves
489
+ * correct prop inference for tools such as Storybook and
490
+ * `React.ComponentProps`.
491
+ */
492
+ (props: PolymorphicProps<G, DefaultOf<G>>): ReactElement;
477
493
  displayName?: string;
478
494
  };
479
495
 
@@ -249,6 +249,16 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
249
249
  */
250
250
  readonly diagnostics?: Diagnostics | DiagnosticsMode;
251
251
  readonly aria?: readonly AriaRule[];
252
+ /**
253
+ * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
254
+ * (`AriaRule`'s `readsProps`, fixable `AriaFix` results) but have no
255
+ * relationship to ARIA semantics — an HTML fact or a security check like a
256
+ * dangerous-URL-scheme guard, for example. Evaluated together with `aria`
257
+ * (both run through the same engine, merged into one rule set) — this is a
258
+ * separate bucket purely so a non-ARIA rule doesn't have to sit under the
259
+ * misleading `aria` name to get the machinery it needs.
260
+ */
261
+ readonly rules?: readonly AriaRule[];
252
262
  readonly children?: readonly ChildRuleInput[];
253
263
  /**
254
264
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -332,6 +342,15 @@ type PolymorphicProps<G extends PolymorphicGenerics, TAs extends ElementType = D
332
342
  }) | AsChildProps<G>>;
333
343
  type PolymorphicComponent<G extends PolymorphicGenerics> = {
334
344
  <TAs extends ElementType = DefaultOf<G>>(props: PolymorphicProps<G, TAs>): JSX.Element;
345
+ /**
346
+ * Non-generic fallback overload used for type extraction.
347
+ *
348
+ * TypeScript resolves conditional types such as
349
+ * `ComponentProps<typeof Component>` against only the final overload.
350
+ * Anchoring that overload to the default element preserves correct prop
351
+ * inference for tools such as Storybook and `ComponentProps`.
352
+ */
353
+ (props: PolymorphicProps<G, DefaultOf<G>>): JSX.Element;
335
354
  displayName?: string;
336
355
  };
337
356
 
@@ -20,6 +20,9 @@ function isNull(value) {
20
20
  function isNonNull(value) {
21
21
  return value != null;
22
22
  }
23
+ function isNullish(value) {
24
+ return isNull(value) || value === void 0;
25
+ }
23
26
 
24
27
  // ../../lib/primitive/src/utils/type-guards.ts
25
28
  function isObject(value, excludeArrays = false) {
@@ -607,20 +610,29 @@ function isAriaAttributeValidForRole(attr, role) {
607
610
  }
608
611
 
609
612
  // ../../lib/primitive/src/guards/aria/is-aria-role.ts
613
+ function lookupImplicitRole(tag) {
614
+ return IMPLICIT_ROLE_RECORD[tag];
615
+ }
610
616
  function isStrongImplicitRole(tag) {
611
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
612
- return STRONG_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
617
+ const role = lookupImplicitRole(tag);
618
+ return !isNullish(role) && STRONG_ROLES_SET.has(role);
613
619
  }
614
- function isStandaloneTag(tag) {
615
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
616
- return STANDALONE_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
620
+ function hasStandaloneRole(tag) {
621
+ const role = lookupImplicitRole(tag);
622
+ return !isNullish(role) && STANDALONE_ROLES_SET.has(role);
617
623
  }
618
- function getInputImplicitRole(type) {
619
- if (!isString(type) || !(type in INPUT_TYPE_ROLE_MAP)) return void 0;
620
- return INPUT_TYPE_ROLE_MAP[type];
624
+ var LIST_ELIGIBLE_INPUT_TYPES = /* @__PURE__ */ new Set(["text", "search", "tel", "url", "email"]);
625
+ function getInputImplicitRole(type, list) {
626
+ if (!isString(type)) return void 0;
627
+ const role = INPUT_TYPE_ROLE_MAP[type];
628
+ if (!role) return void 0;
629
+ if (!isNullish(list) && LIST_ELIGIBLE_INPUT_TYPES.has(type)) {
630
+ return "combobox";
631
+ }
632
+ return role;
621
633
  }
622
634
  function getConditionalImplicitRole(tag, ariaLabel, ariaLabelledBy) {
623
- const isNamed = isString(ariaLabel) || isString(ariaLabelledBy);
635
+ const isNamed = isString(ariaLabel) && ariaLabel.trim().length > 0 || isString(ariaLabelledBy) && ariaLabelledBy.trim().length > 0;
624
636
  if (!isNamed) return void 0;
625
637
  if (tag === "section") return "region";
626
638
  if (tag === "form") return "form";
@@ -681,7 +693,7 @@ function defineContractComponent(options) {
681
693
  // ../../lib/contract/src/aria/aria-role-policy.ts
682
694
  function getImplicitRole(tag, props) {
683
695
  if (tag in IMPLICIT_ROLE_RECORD) return IMPLICIT_ROLE_RECORD[tag];
684
- if (tag === "input") return getInputImplicitRole(props?.type);
696
+ if (tag === "input") return getInputImplicitRole(props?.type, props?.list);
685
697
  if (tag === "img") return props?.alt === "" ? "none" : "img";
686
698
  if (tag === "section" || tag === "form") {
687
699
  return getConditionalImplicitRole(tag, props?.["aria-label"], props?.["aria-labelledby"]);
@@ -1370,7 +1382,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1370
1382
  if (!isIntrinsicTag(tag)) return { proceed: false, result: { props, violations: [] } };
1371
1383
  const implicitRole = getImplicitRole(tag, props);
1372
1384
  const hasRole2 = isNonNull(implicitRole) || isString(props.role) && props.role.length > 0;
1373
- if (!hasRole2) return { proceed: false, result: { props, violations: [] } };
1374
1385
  const normalized = _AriaPolicyEngine.#normalizeEmptyRole(tag, props);
1375
1386
  const workingProps = normalized.normalized ? normalized.result.props : props;
1376
1387
  const preExistingViolations = normalized.normalized ? normalized.result.violations : [];
@@ -1380,6 +1391,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1380
1391
  tag,
1381
1392
  implicitRole,
1382
1393
  effectiveRole,
1394
+ hasRole: hasRole2,
1383
1395
  props: workingProps,
1384
1396
  preExistingViolations,
1385
1397
  context: { tag, props: workingProps, implicitRole, effectiveRole }
@@ -1423,6 +1435,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1423
1435
  static evaluate(tag, props) {
1424
1436
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1425
1437
  if (!derived.proceed) return derived.result;
1438
+ if (!derived.hasRole)
1439
+ return { props: derived.props, violations: [...derived.preExistingViolations] };
1426
1440
  const {
1427
1441
  tag: narrowedTag,
1428
1442
  implicitRole,
@@ -1447,10 +1461,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1447
1461
  props: workingProps,
1448
1462
  preExistingViolations
1449
1463
  } = derived;
1450
- const { violations, fixes } = _AriaPolicyEngine.#runRules(
1451
- [..._AriaPolicyEngine.#getRules(context), ...extraRules],
1452
- context
1453
- );
1464
+ const rules = derived.hasRole ? [..._AriaPolicyEngine.#getRules(context), ...extraRules] : extraRules;
1465
+ const { violations, fixes } = _AriaPolicyEngine.#runRules(rules, context);
1454
1466
  const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1455
1467
  return { props: next, violations: [...preExistingViolations, ...violations] };
1456
1468
  }
@@ -1649,7 +1661,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1649
1661
  static #checkStandaloneRegion({ tag, props, implicitRole }) {
1650
1662
  const role = props.role;
1651
1663
  if (role !== "region") return NO_VIOLATIONS2;
1652
- if (!isStandaloneTag(tag)) return NO_VIOLATIONS2;
1664
+ if (!hasStandaloneRole(tag)) return NO_VIOLATIONS2;
1653
1665
  const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
1654
1666
  return [
1655
1667
  {
@@ -3139,6 +3151,11 @@ function composeNormalizers(normalizers, fn) {
3139
3151
  function whenDefined(key, value) {
3140
3152
  return value === void 0 ? {} : { [key]: value };
3141
3153
  }
3154
+ function mergeAriaRules(aria, rules) {
3155
+ if (!aria?.length) return rules;
3156
+ if (!rules?.length) return aria;
3157
+ return [...aria, ...rules];
3158
+ }
3142
3159
  function resolveFactoryOptions(options = {}) {
3143
3160
  const { styling, enforcement } = options;
3144
3161
  const composedNormalizeFn = composeNormalizers(enforcement?.props, options.normalize);
@@ -3159,7 +3176,7 @@ function resolveFactoryOptions(options = {}) {
3159
3176
  ...whenDefined("defaultVariants", styling?.defaults),
3160
3177
  ...whenDefined("compoundVariants", styling?.compounds),
3161
3178
  ...whenDefined("normalizeFn", composedNormalizeFn),
3162
- ...whenDefined("ariaRules", enforcement?.aria),
3179
+ ...whenDefined("ariaRules", mergeAriaRules(enforcement?.aria, enforcement?.rules)),
3163
3180
  ...whenDefined("childRules", enforcement?.children),
3164
3181
  ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
3165
3182
  ...whenDefined("allowText", enforcement?.allowText),