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.
@@ -28,6 +28,9 @@ function isNull(value) {
28
28
  function isNonNull(value) {
29
29
  return value != null;
30
30
  }
31
+ function isNullish(value) {
32
+ return isNull(value) || value === void 0;
33
+ }
31
34
 
32
35
  // ../../lib/primitive/src/utils/type-guards.ts
33
36
  function isObject(value, excludeArrays = false) {
@@ -655,20 +658,29 @@ function isAriaAttributeValidForRole(attr, role) {
655
658
  }
656
659
 
657
660
  // ../../lib/primitive/src/guards/aria/is-aria-role.ts
661
+ function lookupImplicitRole(tag) {
662
+ return IMPLICIT_ROLE_RECORD[tag];
663
+ }
658
664
  function isStrongImplicitRole(tag) {
659
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
660
- return STRONG_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
665
+ const role = lookupImplicitRole(tag);
666
+ return !isNullish(role) && STRONG_ROLES_SET.has(role);
661
667
  }
662
- function isStandaloneTag(tag) {
663
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
664
- return STANDALONE_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
668
+ function hasStandaloneRole(tag) {
669
+ const role = lookupImplicitRole(tag);
670
+ return !isNullish(role) && STANDALONE_ROLES_SET.has(role);
665
671
  }
666
- function getInputImplicitRole(type) {
667
- if (!isString(type) || !(type in INPUT_TYPE_ROLE_MAP)) return void 0;
668
- return INPUT_TYPE_ROLE_MAP[type];
672
+ var LIST_ELIGIBLE_INPUT_TYPES = /* @__PURE__ */ new Set(["text", "search", "tel", "url", "email"]);
673
+ function getInputImplicitRole(type, list) {
674
+ if (!isString(type)) return void 0;
675
+ const role = INPUT_TYPE_ROLE_MAP[type];
676
+ if (!role) return void 0;
677
+ if (!isNullish(list) && LIST_ELIGIBLE_INPUT_TYPES.has(type)) {
678
+ return "combobox";
679
+ }
680
+ return role;
669
681
  }
670
682
  function getConditionalImplicitRole(tag, ariaLabel, ariaLabelledBy) {
671
- const isNamed = isString(ariaLabel) || isString(ariaLabelledBy);
683
+ const isNamed = isString(ariaLabel) && ariaLabel.trim().length > 0 || isString(ariaLabelledBy) && ariaLabelledBy.trim().length > 0;
672
684
  if (!isNamed) return void 0;
673
685
  if (tag === "section") return "region";
674
686
  if (tag === "form") return "form";
@@ -720,7 +732,7 @@ function isTag(...args) {
720
732
  // ../../lib/contract/src/aria/aria-role-policy.ts
721
733
  function getImplicitRole(tag, props) {
722
734
  if (tag in IMPLICIT_ROLE_RECORD) return IMPLICIT_ROLE_RECORD[tag];
723
- if (tag === "input") return getInputImplicitRole(props?.type);
735
+ if (tag === "input") return getInputImplicitRole(props?.type, props?.list);
724
736
  if (tag === "img") return props?.alt === "" ? "none" : "img";
725
737
  if (tag === "section" || tag === "form") {
726
738
  return getConditionalImplicitRole(tag, props?.["aria-label"], props?.["aria-labelledby"]);
@@ -1409,7 +1421,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1409
1421
  if (!isIntrinsicTag(tag)) return { proceed: false, result: { props, violations: [] } };
1410
1422
  const implicitRole = getImplicitRole(tag, props);
1411
1423
  const hasRole2 = isNonNull(implicitRole) || isString(props.role) && props.role.length > 0;
1412
- if (!hasRole2) return { proceed: false, result: { props, violations: [] } };
1413
1424
  const normalized = _AriaPolicyEngine.#normalizeEmptyRole(tag, props);
1414
1425
  const workingProps = normalized.normalized ? normalized.result.props : props;
1415
1426
  const preExistingViolations = normalized.normalized ? normalized.result.violations : [];
@@ -1419,6 +1430,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1419
1430
  tag,
1420
1431
  implicitRole,
1421
1432
  effectiveRole,
1433
+ hasRole: hasRole2,
1422
1434
  props: workingProps,
1423
1435
  preExistingViolations,
1424
1436
  context: { tag, props: workingProps, implicitRole, effectiveRole }
@@ -1462,6 +1474,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1462
1474
  static evaluate(tag, props) {
1463
1475
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1464
1476
  if (!derived.proceed) return derived.result;
1477
+ if (!derived.hasRole)
1478
+ return { props: derived.props, violations: [...derived.preExistingViolations] };
1465
1479
  const {
1466
1480
  tag: narrowedTag,
1467
1481
  implicitRole,
@@ -1486,10 +1500,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1486
1500
  props: workingProps,
1487
1501
  preExistingViolations
1488
1502
  } = derived;
1489
- const { violations, fixes } = _AriaPolicyEngine.#runRules(
1490
- [..._AriaPolicyEngine.#getRules(context), ...extraRules],
1491
- context
1492
- );
1503
+ const rules = derived.hasRole ? [..._AriaPolicyEngine.#getRules(context), ...extraRules] : extraRules;
1504
+ const { violations, fixes } = _AriaPolicyEngine.#runRules(rules, context);
1493
1505
  const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1494
1506
  return { props: next, violations: [...preExistingViolations, ...violations] };
1495
1507
  }
@@ -1688,7 +1700,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1688
1700
  static #checkStandaloneRegion({ tag, props, implicitRole }) {
1689
1701
  const role = props.role;
1690
1702
  if (role !== "region") return NO_VIOLATIONS2;
1691
- if (!isStandaloneTag(tag)) return NO_VIOLATIONS2;
1703
+ if (!hasStandaloneRole(tag)) return NO_VIOLATIONS2;
1692
1704
  const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
1693
1705
  return [
1694
1706
  {
@@ -3178,6 +3190,11 @@ function composeNormalizers(normalizers, fn) {
3178
3190
  function whenDefined(key, value) {
3179
3191
  return value === void 0 ? {} : { [key]: value };
3180
3192
  }
3193
+ function mergeAriaRules(aria, rules) {
3194
+ if (!aria?.length) return rules;
3195
+ if (!rules?.length) return aria;
3196
+ return [...aria, ...rules];
3197
+ }
3181
3198
  function resolveFactoryOptions(options = {}) {
3182
3199
  const { styling, enforcement } = options;
3183
3200
  const composedNormalizeFn = composeNormalizers(enforcement?.props, options.normalize);
@@ -3198,7 +3215,7 @@ function resolveFactoryOptions(options = {}) {
3198
3215
  ...whenDefined("defaultVariants", styling?.defaults),
3199
3216
  ...whenDefined("compoundVariants", styling?.compounds),
3200
3217
  ...whenDefined("normalizeFn", composedNormalizeFn),
3201
- ...whenDefined("ariaRules", enforcement?.aria),
3218
+ ...whenDefined("ariaRules", mergeAriaRules(enforcement?.aria, enforcement?.rules)),
3202
3219
  ...whenDefined("childRules", enforcement?.children),
3203
3220
  ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
3204
3221
  ...whenDefined("allowText", enforcement?.allowText),
@@ -231,6 +231,16 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
231
231
  */
232
232
  readonly diagnostics?: Diagnostics$1 | DiagnosticsMode;
233
233
  readonly aria?: readonly AriaRule[];
234
+ /**
235
+ * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
236
+ * (`AriaRule`'s `readsProps`, fixable `AriaFix` results) but have no
237
+ * relationship to ARIA semantics — an HTML fact or a security check like a
238
+ * dangerous-URL-scheme guard, for example. Evaluated together with `aria`
239
+ * (both run through the same engine, merged into one rule set) — this is a
240
+ * separate bucket purely so a non-ARIA rule doesn't have to sit under the
241
+ * misleading `aria` name to get the machinery it needs.
242
+ */
243
+ readonly rules?: readonly AriaRule[];
234
244
  readonly children?: readonly ChildRuleInput[];
235
245
  /**
236
246
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -313,6 +313,7 @@ var selectedContract = stateContract([selectedProps]);
313
313
  function mergeContracts(...contracts) {
314
314
  const props = contracts.flatMap((c) => c.props ?? []);
315
315
  const aria = contracts.flatMap((c) => c.aria ?? []);
316
+ const rules = contracts.flatMap((c) => c.rules ?? []);
316
317
  const children = contracts.flatMap((c) => c.children ?? []);
317
318
  let diagnostics;
318
319
  let allowedAs;
@@ -323,6 +324,7 @@ function mergeContracts(...contracts) {
323
324
  return {
324
325
  ...props.length > 0 && { props },
325
326
  ...aria.length > 0 && { aria },
327
+ ...rules.length > 0 && { rules },
326
328
  ...children.length > 0 && { children },
327
329
  ...diagnostics !== void 0 && { diagnostics },
328
330
  ...allowedAs !== void 0 && { allowedAs }
@@ -22,15 +22,24 @@ type TagChild = {
22
22
  };
23
23
  declare function isTag(tag: string, ...tags: readonly string[]): (child: unknown) => child is TagChild;
24
24
  declare function isTag(child: unknown, tag: string, ...tags: readonly string[]): boolean;
25
+ /**
26
+ * A vnode (or text node) that qualifies as flow content: text nodes
27
+ * (string/number) always qualify, and elements/components qualify unless
28
+ * their resolved tag is blocked — see `isFlowContent`.
29
+ */
30
+ type FlowContentChild = string | number | TagChild;
25
31
  /**
26
32
  * Checks whether a vnode is flow content per the HTML content model: text nodes
27
33
  * (string/number) always qualify, and elements/components qualify unless their
28
34
  * resolved tag is in the blocked set.
35
+ *
36
+ * Returns a type guard (not a plain boolean predicate) so it can be used
37
+ * directly as a `ChildRuleInput.match`, which requires the narrowed type.
29
38
  */
30
- declare function isFlowContent(...blockedTags: readonly string[]): (child: unknown) => boolean;
39
+ declare function isFlowContent(...blockedTags: readonly string[]): (child: unknown) => child is FlowContentChild;
31
40
 
32
41
  declare function isObject(value: unknown, excludeArrays: true): value is AnyRecord;
33
42
  declare function isObject(value: unknown, excludeArrays?: false): value is object;
34
43
  declare function isString(value: unknown): value is string;
35
44
 
36
- export { getTag, isFlowContent, isObject, isString, isTag };
45
+ export { type FlowContentChild, type TagChild, getTag, isFlowContent, isObject, isString, isTag };
@@ -0,0 +1,98 @@
1
+ import { ReadonlyDeep } from 'type-fest';
2
+ import { DiagnosticInput } from '../_shared/diagnostics.js';
3
+
4
+ type StringMap<T = unknown> = Record<string, T>;
5
+ type AnyRecord = StringMap<unknown>;
6
+
7
+ type IntrinsicTag = keyof HTMLElementTagNameMap;
8
+
9
+ declare const KNOWN_ARIA_ROLES: readonly ["alert", "alertdialog", "application", "article", "banner", "blockquote", "button", "caption", "cell", "checkbox", "code", "columnheader", "combobox", "complementary", "contentinfo", "definition", "deletion", "dialog", "document", "emphasis", "feed", "figure", "form", "generic", "grid", "gridcell", "group", "heading", "img", "insertion", "link", "list", "listbox", "listitem", "log", "main", "marquee", "math", "menu", "menubar", "menuitem", "menuitemcheckbox", "menuitemradio", "meter", "navigation", "none", "note", "option", "paragraph", "presentation", "progressbar", "radio", "radiogroup", "region", "row", "rowgroup", "rowheader", "scrollbar", "search", "searchbox", "separator", "slider", "spinbutton", "status", "strong", "subscript", "superscript", "switch", "tab", "table", "tablist", "tabpanel", "term", "textbox", "time", "timer", "toolbar", "tooltip", "tree", "treegrid", "treeitem"];
10
+ type KnownAriaRole = (typeof KNOWN_ARIA_ROLES)[number];
11
+
12
+ type AriaRole = KnownAriaRole | (string & {});
13
+ type IntrinsicProps = AnyRecord & {
14
+ role?: AriaRole;
15
+ };
16
+
17
+ type ValidResult = {
18
+ valid: true;
19
+ };
20
+
21
+ type AriaContext = {
22
+ readonly tag: IntrinsicTag;
23
+ readonly implicitRole: AriaRole | undefined;
24
+ readonly effectiveRole: string | undefined;
25
+ readonly props: ReadonlyDeep<IntrinsicProps>;
26
+ };
27
+
28
+ type RemoveAttributeFixKind = `removeAttribute:${string}`;
29
+ type InjectLiveFixKind = `injectLive:${string}`;
30
+ type FixKind = 'removeRole' | 'setRole' | 'normalizeRelevantAll' | RemoveAttributeFixKind | InjectLiveFixKind;
31
+
32
+ type AriaFixResult = {
33
+ applied: false;
34
+ next: ReadonlyDeep<IntrinsicProps>;
35
+ } | {
36
+ applied: true;
37
+ next: ReadonlyDeep<IntrinsicProps>;
38
+ previous: ReadonlyDeep<IntrinsicProps>;
39
+ };
40
+ type AriaFix = {
41
+ readonly kind: FixKind;
42
+ readonly priority?: number;
43
+ readonly source?: string;
44
+ readonly apply: (context: AriaContext) => AriaFixResult;
45
+ };
46
+
47
+ type Severity = 'error' | 'warning' | (string & {});
48
+
49
+ type AriaInvalidBase<M extends string = string> = {
50
+ valid: false;
51
+ severity: Severity;
52
+ message?: M;
53
+ attribute?: string;
54
+ diagnostic?: DiagnosticInput;
55
+ };
56
+ type AriaInvalidWithFix<M extends string = string> = AriaInvalidBase<M> & {
57
+ fixable: true;
58
+ fix: AriaFix;
59
+ };
60
+ type AriaInvalidWithoutFix<M extends string = string> = AriaInvalidBase<M> & {
61
+ fixable: false;
62
+ };
63
+ type AriaInvalidResult<M extends string = string> = AriaInvalidWithFix<M> | AriaInvalidWithoutFix<M>;
64
+ type AriaResult = ValidResult | AriaInvalidResult;
65
+
66
+ type AriaRule<C extends AriaContext = AriaContext> = ((context: C) => readonly AriaResult[]) & {
67
+ readonly readsProps?: readonly string[];
68
+ readonly tags?: readonly string[];
69
+ };
70
+
71
+ declare const landmarkRoleRule: AriaRule;
72
+ declare function requireAccessibleName({ tag, props }: AriaContext): readonly AriaResult[];
73
+ declare const landmarkNameAdvisory: AriaRule;
74
+ declare const HTML_ARIA_RULES: readonly AriaRule[];
75
+
76
+ declare const roleNotPermittedRule: AriaRule;
77
+
78
+ declare const supportedInputTypeRule: AriaRule;
79
+ declare const checkedRequiresCheckableTypeRule: AriaRule;
80
+ declare const multipleRequiresSupportedTypeRule: AriaRule;
81
+ declare const maxLengthRequiresTextTypeRule: AriaRule;
82
+ declare const minLengthRequiresTextTypeRule: AriaRule;
83
+ declare const patternRequiresTextTypeRule: AriaRule;
84
+ declare const minRequiresNumericTypeRule: AriaRule;
85
+ declare const maxRequiresNumericTypeRule: AriaRule;
86
+ declare const stepRequiresNumericTypeRule: AriaRule;
87
+ declare const acceptRequiresFileTypeRule: AriaRule;
88
+ declare const captureRequiresFileTypeRule: AriaRule;
89
+ declare const sizeRequiresTextTypeRule: AriaRule;
90
+ declare const altRequiresImageTypeRule: AriaRule;
91
+ declare const heightRequiresImageTypeRule: AriaRule;
92
+ declare const widthRequiresImageTypeRule: AriaRule;
93
+ declare const inputAccessibleNameRule: AriaRule;
94
+ declare const passwordAutocompleteRule: AriaRule;
95
+ declare const requiredReadOnlyConflictRule: AriaRule;
96
+ declare const INPUT_RULES: readonly AriaRule[];
97
+
98
+ export { HTML_ARIA_RULES, INPUT_RULES, acceptRequiresFileTypeRule, altRequiresImageTypeRule, captureRequiresFileTypeRule, checkedRequiresCheckableTypeRule, heightRequiresImageTypeRule, inputAccessibleNameRule, landmarkNameAdvisory, landmarkRoleRule, maxLengthRequiresTextTypeRule, maxRequiresNumericTypeRule, minLengthRequiresTextTypeRule, minRequiresNumericTypeRule, multipleRequiresSupportedTypeRule, passwordAutocompleteRule, patternRequiresTextTypeRule, requireAccessibleName, requiredReadOnlyConflictRule, roleNotPermittedRule, sizeRequiresTextTypeRule, stepRequiresNumericTypeRule, supportedInputTypeRule, widthRequiresImageTypeRule };