praxis-kit 0.1.1 → 1.0.2

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.
package/dist/vue/index.js CHANGED
@@ -15,11 +15,6 @@ function applyDisplayName(component, name) {
15
15
  Object.assign(component, { displayName: name ?? "PolymorphicComponent" });
16
16
  }
17
17
  //#endregion
18
- //#region ../../lib/adapter-utils/src/runtime/define-component.ts
19
- function defineContractComponent(options) {
20
- return (factory) => factory(options);
21
- }
22
- //#endregion
23
18
  //#region ../../lib/adapter-utils/src/runtime/assemble-compound-component.ts
24
19
  /**
25
20
  * Assembles a compound component by attaching sub-components to a
@@ -1563,72 +1558,100 @@ var InvariantBase = class {
1563
1558
  }
1564
1559
  };
1565
1560
  //#endregion
1566
- //#region ../../lib/contract/src/aria/spec/roles/required-properties.ts
1567
- const REQUIRED_ARIA_PROPERTIES = {
1568
- combobox: ["aria-expanded"],
1569
- option: ["aria-selected"],
1570
- slider: ["aria-valuenow"],
1571
- scrollbar: ["aria-controls", "aria-valuenow"],
1572
- spinbutton: ["aria-valuenow"]
1573
- };
1574
- //#endregion
1575
- //#region ../../lib/contract/src/aria/spec/roles/name-required.ts
1576
- const NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
1577
- //#endregion
1578
- //#region ../../lib/contract/src/aria/spec/roles/name-prohibited.ts
1579
- const NAME_PROHIBITED_ROLES = /* @__PURE__ */ new Set([
1580
- "caption",
1581
- "code",
1582
- "deletion",
1583
- "emphasis",
1584
- "generic",
1585
- "insertion",
1586
- "none",
1587
- "paragraph",
1588
- "presentation",
1589
- "strong",
1590
- "subscript",
1591
- "superscript"
1592
- ]);
1593
- const NAME_PROHIBITED_ATTRIBUTES = ["aria-label", "aria-labelledby"];
1594
- //#endregion
1595
- //#region ../../lib/contract/src/aria/spec/validators/required-properties-validator.ts
1596
- const NO_VIOLATIONS$1 = [{ valid: true }];
1597
- function requiredAttributeByRole(roles, attribute) {
1598
- return Object.fromEntries([...roles].map((role) => [role, [attribute]]));
1599
- }
1561
+ //#region ../../lib/contract/src/aria/plan-cache.ts
1600
1562
  /**
1601
- * Reports missing attributes required by an element's effective ARIA role.
1563
+ * Validation-plan cache machinery for `AriaPolicyEngine`: computing a cache key from a
1564
+ * (tag, props) usage site, and diffing/replaying the props transformation a validation pass
1565
+ * produced (an {@link AriaPlan}'s `removals`/`updates`) without re-running the ARIA rule
1566
+ * pipeline itself. Extracted because these four functions are a cohesive subsystem in their own
1567
+ * right — a small revalidation-caching scheme — not specifically ARIA *policy*; the actual
1568
+ * cache storage (an `LRUCache` instance) stays owned by `AriaPolicyEngine` itself, since it's
1569
+ * per-engine-instance state, not something this module needs to hold.
1570
+ */
1571
+ /**
1572
+ * Cache key covering only the aria-relevant subset of props (tag + role + aria-* attrs) —
1573
+ * exactly what the built-in pipeline reads. Non-aria props (className, onClick, etc.) do
1574
+ * not affect built-in ARIA decisions and are excluded so cache hits survive re-renders
1575
+ * that only change non-aria props. This key alone is unsound for a policy engine's own
1576
+ * extra rules, which may read arbitrary props outside this set — extend it with
1577
+ * {@link extraRulesKeySuffix}, or bypass the cache, to account for that.
1602
1578
  *
1603
- * Shared by validators that differ only in their role-to-attribute mapping and diagnostic
1604
- * generation.
1579
+ * Returns `null` (meaning "don't cache") when `tag` isn't a plain intrinsic string tag —
1580
+ * `isString(tag)` inline rather than importing `AriaPolicyEngine`'s own `isIntrinsicTag`: the
1581
+ * check is a one-liner, and importing it back from the engine module would be a needless
1582
+ * dependency in the other direction for a plan-cache module the engine itself depends on.
1605
1583
  */
1606
- function checkRequiredAttributes(requirement, { props, effectiveRole }) {
1607
- if (!effectiveRole) return NO_VIOLATIONS$1;
1608
- const requiredAttributes = requirement.attributesByRole[effectiveRole];
1609
- if (!requiredAttributes) return NO_VIOLATIONS$1;
1610
- const results = [];
1611
- for (const attribute of requiredAttributes) {
1612
- if (attribute in props) continue;
1613
- results.push({
1614
- valid: false,
1615
- fixable: false,
1616
- severity: "warning",
1617
- attribute,
1618
- diagnostic: requirement.diagnosticFor(attribute, effectiveRole)
1619
- });
1584
+ function createPlanKey(tag, props) {
1585
+ if (!isString(tag)) return null;
1586
+ const parts = [tag];
1587
+ if (isString(props.role)) parts.push(`role:${props.role}`);
1588
+ if (tag === "input" && isString(props.type)) parts.push(`type:${props.type}`);
1589
+ if (tag === "img") parts.push(`alt:${props.alt === "" ? "empty" : "present"}`);
1590
+ const ariaEntries = [];
1591
+ iterate.forEachEntry(props, (k, v) => {
1592
+ if (!k.startsWith("aria-")) return;
1593
+ if (!isString(v) && !isNumber(v) && typeof v !== "boolean") return;
1594
+ ariaEntries.push(`${k}:${String(v)}`);
1595
+ });
1596
+ if (ariaEntries.length > 0) parts.push(...ariaEntries.sort());
1597
+ return parts.join("|");
1598
+ }
1599
+ /**
1600
+ * Extends the base cache key with the props each extra rule declares it reads (`readsProps`).
1601
+ * Returns `null` — meaning "don't cache" — if any extra rule omits `readsProps` (it may read
1602
+ * arbitrary props the key can't account for) or if a declared prop's value isn't a primitive
1603
+ * (object/array identity isn't stably representable in a string key).
1604
+ */
1605
+ function extraRulesKeySuffix(extraRules, props) {
1606
+ const parts = [];
1607
+ for (const rule of extraRules) {
1608
+ const readsProps = rule.readsProps;
1609
+ if (!isNonNull(readsProps)) return null;
1610
+ for (const propKey of readsProps) {
1611
+ const v = props[propKey];
1612
+ if (isDefined(v) && !isString(v) && !isNumber(v) && typeof v !== "boolean") return null;
1613
+ parts.push(`x:${propKey}:${String(v)}`);
1614
+ }
1620
1615
  }
1621
- return results;
1616
+ return parts.sort().join("|");
1617
+ }
1618
+ /** Diffs a validation pass's input/output props into a replayable plan: which keys were removed
1619
+ * entirely, and which keys were added or changed value (and to what). */
1620
+ function computePlan(inputProps, resultProps) {
1621
+ const removals = /* @__PURE__ */ new Set();
1622
+ const updates = {};
1623
+ iterate.forEachKey(inputProps, (key) => {
1624
+ if (!(key in resultProps)) removals.add(key);
1625
+ });
1626
+ iterate.forEachEntry(resultProps, (key, resultVal) => {
1627
+ if (inputProps[key] !== resultVal) updates[key] = resultVal;
1628
+ });
1629
+ return {
1630
+ removals,
1631
+ updates
1632
+ };
1633
+ }
1634
+ /** Replays a cached plan's removals/updates against a fresh `props` object, without re-running
1635
+ * the rule pipeline that originally produced the plan. */
1636
+ function applyPlan(props, removals, updates) {
1637
+ const hasRemovals = removals.size > 0;
1638
+ const hasUpdates = Object.keys(updates).length > 0;
1639
+ if (!hasRemovals && !hasUpdates) return props;
1640
+ const next = {};
1641
+ iterate.forEachEntry(props, (k, v) => {
1642
+ if (!removals.has(k)) next[k] = v;
1643
+ });
1644
+ Object.assign(next, updates);
1645
+ return next;
1622
1646
  }
1623
1647
  //#endregion
1624
- //#region ../../lib/contract/src/aria/spec/roles/live-region.ts
1625
- const LIVE_REGION_ROLES = /* @__PURE__ */ new Map([
1626
- ["alert", "assertive"],
1627
- ["status", "polite"],
1628
- ["log", "polite"],
1629
- ["timer", "off"]
1648
+ //#region ../../lib/contract/src/aria/spec/attributes/aria-relevant-tokens.ts
1649
+ const VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set([
1650
+ "additions",
1651
+ "removals",
1652
+ "text",
1653
+ "all"
1630
1654
  ]);
1631
- const ATOMIC_REQUIREMENTS = requiredAttributeByRole(LIVE_REGION_ROLES.keys(), "aria-atomic");
1632
1655
  //#endregion
1633
1656
  //#region ../../lib/contract/src/aria/spec/attributes/aria-value-types.ts
1634
1657
  const ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
@@ -1753,24 +1776,6 @@ const ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
1753
1776
  }]
1754
1777
  ]);
1755
1778
  //#endregion
1756
- //#region ../../lib/contract/src/aria/spec/attributes/aria-relevant-tokens.ts
1757
- const VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set([
1758
- "additions",
1759
- "removals",
1760
- "text",
1761
- "all"
1762
- ]);
1763
- //#endregion
1764
- //#region ../../lib/contract/src/aria/spec/elements/heading-implicit-levels.ts
1765
- const HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
1766
- ["h1", 1],
1767
- ["h2", 2],
1768
- ["h3", 3],
1769
- ["h4", 4],
1770
- ["h5", 5],
1771
- ["h6", 6]
1772
- ]);
1773
- //#endregion
1774
1779
  //#region ../../lib/contract/src/aria/spec/elements/focusable.ts
1775
1780
  function parseTabIndex(raw) {
1776
1781
  if (typeof raw === "number") return Number.isInteger(raw) ? raw : void 0;
@@ -1821,24 +1826,156 @@ function isPotentiallyFocusable(tag, props) {
1821
1826
  }
1822
1827
  }
1823
1828
  //#endregion
1824
- //#region ../../lib/contract/src/aria/aria-policy-engine.ts
1829
+ //#region ../../lib/contract/src/aria/spec/elements/heading-implicit-levels.ts
1830
+ const HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
1831
+ ["h1", 1],
1832
+ ["h2", 2],
1833
+ ["h3", 3],
1834
+ ["h4", 4],
1835
+ ["h5", 5],
1836
+ ["h6", 6]
1837
+ ]);
1838
+ //#endregion
1839
+ //#region ../../lib/contract/src/aria/spec/validators/required-properties-validator.ts
1825
1840
  const NO_VIOLATIONS = [{ valid: true }];
1826
- const NO_VARIANT_KEYS = /* @__PURE__ */ new Set();
1827
- function isIntrinsicTag(tag) {
1828
- return isString(tag);
1841
+ function requiredAttributeByRole(roles, attribute) {
1842
+ return Object.fromEntries([...roles].map((role) => [role, [attribute]]));
1829
1843
  }
1830
- function omitProp(obj, key) {
1831
- const { [key]: _, ...rest } = obj;
1832
- return rest;
1844
+ /**
1845
+ * Reports missing attributes required by an element's effective ARIA role.
1846
+ *
1847
+ * Shared by validators that differ only in their role-to-attribute mapping and diagnostic
1848
+ * generation.
1849
+ */
1850
+ function checkRequiredAttributes(requirement, { props, effectiveRole }) {
1851
+ if (!effectiveRole) return NO_VIOLATIONS;
1852
+ const requiredAttributes = requirement.attributesByRole[effectiveRole];
1853
+ if (!requiredAttributes) return NO_VIOLATIONS;
1854
+ const results = [];
1855
+ for (const attribute of requiredAttributes) {
1856
+ if (attribute in props) continue;
1857
+ results.push({
1858
+ valid: false,
1859
+ fixable: false,
1860
+ severity: "warning",
1861
+ attribute,
1862
+ diagnostic: requirement.diagnosticFor(attribute, effectiveRole)
1863
+ });
1864
+ }
1865
+ return results;
1833
1866
  }
1867
+ //#endregion
1868
+ //#region ../../lib/contract/src/aria/spec/roles/live-region.ts
1869
+ const LIVE_REGION_ROLES = /* @__PURE__ */ new Map([
1870
+ ["alert", "assertive"],
1871
+ ["status", "polite"],
1872
+ ["log", "polite"],
1873
+ ["timer", "off"]
1874
+ ]);
1875
+ const ATOMIC_REQUIREMENTS = requiredAttributeByRole(LIVE_REGION_ROLES.keys(), "aria-atomic");
1876
+ //#endregion
1877
+ //#region ../../lib/contract/src/aria/spec/roles/name-prohibited.ts
1878
+ const NAME_PROHIBITED_ROLES = /* @__PURE__ */ new Set([
1879
+ "caption",
1880
+ "code",
1881
+ "deletion",
1882
+ "emphasis",
1883
+ "generic",
1884
+ "insertion",
1885
+ "none",
1886
+ "paragraph",
1887
+ "presentation",
1888
+ "strong",
1889
+ "subscript",
1890
+ "superscript"
1891
+ ]);
1892
+ const NAME_PROHIBITED_ATTRIBUTES = ["aria-label", "aria-labelledby"];
1893
+ //#endregion
1894
+ //#region ../../lib/contract/src/aria/spec/roles/name-required.ts
1895
+ const NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
1896
+ //#endregion
1897
+ //#region ../../lib/contract/src/aria/spec/roles/required-properties.ts
1898
+ const REQUIRED_ARIA_PROPERTIES = {
1899
+ combobox: ["aria-expanded"],
1900
+ option: ["aria-selected"],
1901
+ slider: ["aria-valuenow"],
1902
+ scrollbar: ["aria-controls", "aria-valuenow"],
1903
+ spinbutton: ["aria-valuenow"]
1904
+ };
1905
+ //#endregion
1906
+ //#region ../../lib/contract/src/aria/value-validation.ts
1907
+ /**
1908
+ * ARIA attribute value type system: what counts as a valid value for a given
1909
+ * {@link AriaValueType}, and how to describe what was expected when it isn't. Extracted from
1910
+ * `AriaPolicyEngine` — these three functions are cohesive on their own (they're describing a
1911
+ * type system, not policy), and `#checkAriaAttributeValues` (kept in `aria-policy-engine.ts`
1912
+ * alongside every other rule implementation — see that file's own pipeline) is now just a thin
1913
+ * caller of {@link isValidAriaValue}/{@link describeExpected}.
1914
+ */
1915
+ /**
1916
+ * Strict numeric coercion for ARIA attribute values. Unlike `parseFloat`/`parseInt`, the whole
1917
+ * string must be a number — `"12abc"` is rejected, not read as `12` — and an empty / whitespace
1918
+ * string is rejected rather than coerced to `0` the way `Number("")` would.
1919
+ *
1920
+ * Exported (not local to `isValidAriaValue`) because `AriaPolicyEngine.#checkRedundantAriaLevel`
1921
+ * also needs it, independent of the general ARIA value-type check this module otherwise exists
1922
+ * for — `aria-level`'s "redundant" check is about comparing a numeric attribute value to a
1923
+ * known implicit level, not about validating the value's type.
1924
+ */
1834
1925
  function strictNumeric(value) {
1835
- if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
1836
- if (typeof value !== "string") return void 0;
1926
+ if (isNumber(value)) return Number.isFinite(value) ? value : void 0;
1927
+ if (!isString(value)) return void 0;
1837
1928
  const trimmed = value.trim();
1838
1929
  if (trimmed === "") return void 0;
1839
1930
  const n = Number(trimmed);
1840
1931
  return Number.isFinite(n) ? n : void 0;
1841
1932
  }
1933
+ /** Returns whether `value` satisfies `type`'s own value-type constraint (boolean/tristate/
1934
+ * number/integer/enum) — the WAI-ARIA attribute value type system, independent of which
1935
+ * attribute or role it's being checked for. */
1936
+ function isValidAriaValue(value, type) {
1937
+ switch (type.kind) {
1938
+ case "boolean": return value === "true" || value === "false" || value === true || value === false;
1939
+ case "tristate": return value === "true" || value === "false" || value === "mixed" || value === true || value === false;
1940
+ case "number": return strictNumeric(value) !== void 0;
1941
+ case "integer": {
1942
+ const n = strictNumeric(value);
1943
+ if (isUndefined(n) || !Number.isInteger(n)) return false;
1944
+ if (isDefined(type.min) && n < type.min) return false;
1945
+ if (isDefined(type.max) && n > type.max) return false;
1946
+ return true;
1947
+ }
1948
+ case "enum": return isString(value) && type.values.has(value);
1949
+ }
1950
+ }
1951
+ /** Describes what a valid value for `type` looks like, for use in an invalid-value diagnostic
1952
+ * message (e.g. `aria-valuenow` got `"abc"`, expected "a finite number"). */
1953
+ function describeExpected(type) {
1954
+ switch (type.kind) {
1955
+ case "boolean": return "\"true\" or \"false\"";
1956
+ case "tristate": return "\"true\", \"false\", or \"mixed\"";
1957
+ case "number": return "a finite number";
1958
+ case "integer": {
1959
+ const parts = ["an integer"];
1960
+ if (isDefined(type.min) && isDefined(type.max)) parts.push(`between ${type.min} and ${type.max}`);
1961
+ else if (isDefined(type.min)) parts.push(`≥ ${type.min}`);
1962
+ else if (isDefined(type.max)) parts.push(`≤ ${type.max}`);
1963
+ return parts.join(" ");
1964
+ }
1965
+ case "enum": return [...type.values].map((v) => `"${v}"`).join(", ");
1966
+ }
1967
+ }
1968
+ //#endregion
1969
+ //#region ../../lib/contract/src/aria/aria-policy-engine.ts
1970
+ const VALID_RESULT = [{ valid: true }];
1971
+ const NO_VARIANT_KEYS = /* @__PURE__ */ new Set();
1972
+ function isIntrinsicTag(tag) {
1973
+ return isString(tag);
1974
+ }
1975
+ function omitProp(obj, key) {
1976
+ const { [key]: _, ...rest } = obj;
1977
+ return rest;
1978
+ }
1842
1979
  var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
1843
1980
  #extraRules;
1844
1981
  #variantKeys;
@@ -1971,73 +2108,20 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
1971
2108
  else this.warn(d);
1972
2109
  });
1973
2110
  }
1974
- static #createPlanKey(tag, props) {
1975
- if (!isIntrinsicTag(tag)) return null;
1976
- const parts = [tag];
1977
- if (typeof props.role === "string") parts.push(`role:${props.role}`);
1978
- if (tag === "input" && typeof props.type === "string") parts.push(`type:${props.type}`);
1979
- if (tag === "img") parts.push(`alt:${props.alt === "" ? "empty" : "present"}`);
1980
- const ariaEntries = [];
1981
- iterate.forEachEntry(props, (k, v) => {
1982
- if (!k.startsWith("aria-")) return;
1983
- if (!isString(v) && !isNumber(v) && typeof v !== "boolean") return;
1984
- ariaEntries.push(`${k}:${String(v)}`);
1985
- });
1986
- if (ariaEntries.length > 0) parts.push(...ariaEntries.sort());
1987
- return parts.join("|");
1988
- }
1989
- static #extraRulesKeySuffix(extraRules, props) {
1990
- const parts = [];
1991
- for (const rule of extraRules) {
1992
- const readsProps = rule.readsProps;
1993
- if (!isNonNull(readsProps)) return null;
1994
- for (const propKey of readsProps) {
1995
- const v = props[propKey];
1996
- if (v !== void 0 && !isString(v) && !isNumber(v) && typeof v !== "boolean") return null;
1997
- parts.push(`x:${propKey}:${String(v)}`);
1998
- }
1999
- }
2000
- return parts.sort().join("|");
2001
- }
2002
- static #computePlan(inputProps, resultProps) {
2003
- const removals = /* @__PURE__ */ new Set();
2004
- const updates = {};
2005
- iterate.forEachKey(inputProps, (key) => {
2006
- if (!(key in resultProps)) removals.add(key);
2007
- });
2008
- iterate.forEachEntry(resultProps, (key, resultVal) => {
2009
- if (inputProps[key] !== resultVal) updates[key] = resultVal;
2010
- });
2011
- return {
2012
- removals,
2013
- updates
2014
- };
2015
- }
2016
- static #applyPlan(props, removals, updates) {
2017
- const hasRemovals = removals.size > 0;
2018
- const hasUpdates = Object.keys(updates).length > 0;
2019
- if (!hasRemovals && !hasUpdates) return props;
2020
- const next = {};
2021
- iterate.forEachEntry(props, (k, v) => {
2022
- if (!removals.has(k)) next[k] = v;
2023
- });
2024
- Object.assign(next, updates);
2025
- return next;
2026
- }
2027
2111
  validate(tag, props, extraProps) {
2028
2112
  const ruleProps = extraProps ?? props;
2029
- const baseKey = AriaPolicyEngine.#createPlanKey(tag, props);
2113
+ const baseKey = createPlanKey(tag, props);
2030
2114
  let key = baseKey;
2031
2115
  if (this.#extraRules.length > 0) {
2032
- const suffix = AriaPolicyEngine.#extraRulesKeySuffix(this.#extraRules, ruleProps);
2116
+ const suffix = extraRulesKeySuffix(this.#extraRules, ruleProps);
2033
2117
  key = isNonNull(baseKey) && isNonNull(suffix) ? `${baseKey}|${suffix}` : null;
2034
2118
  }
2035
2119
  if (!isNull(key)) {
2036
2120
  const cached = this.#planCache.get(key);
2037
- if (cached !== void 0) {
2121
+ if (isDefined(cached)) {
2038
2122
  if (cached.violations.length > 0) this.report(cached.violations);
2039
2123
  return {
2040
- props: AriaPolicyEngine.#applyPlan(props, cached.removals, cached.updates),
2124
+ props: applyPlan(props, cached.removals, cached.updates),
2041
2125
  violations: cached.violations
2042
2126
  };
2043
2127
  }
@@ -2045,7 +2129,7 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
2045
2129
  const result = this.#extraRules.length ? AriaPolicyEngine.#evaluateWithRules(tag, props, this.#extraRules, extraProps, this.#variantKeys) : AriaPolicyEngine.evaluate(tag, props);
2046
2130
  if (result.violations.length > 0) this.report(result.violations);
2047
2131
  if (!isNull(key)) {
2048
- const { removals, updates } = AriaPolicyEngine.#computePlan(props, result.props);
2132
+ const { removals, updates } = computePlan(props, result.props);
2049
2133
  const plan = {
2050
2134
  removals,
2051
2135
  updates,
@@ -2136,7 +2220,7 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
2136
2220
  ];
2137
2221
  static #checkInvalidRoleOverride({ tag, props, implicitRole }) {
2138
2222
  const role = props.role;
2139
- if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS;
2223
+ if (!implicitRole || isUndefined(role) || role === implicitRole) return VALID_RESULT;
2140
2224
  if (isStrongImplicitRole(tag) && role === "region") {
2141
2225
  const diagnostic = HtmlDiagnostics.implicitRoleOverride(tag, implicitRole, role);
2142
2226
  return [{
@@ -2147,11 +2231,11 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
2147
2231
  diagnostic
2148
2232
  }];
2149
2233
  }
2150
- return NO_VIOLATIONS;
2234
+ return VALID_RESULT;
2151
2235
  }
2152
2236
  static #checkRedundantRole({ tag, props, implicitRole }) {
2153
2237
  const role = props.role;
2154
- if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS;
2238
+ if (!implicitRole || isUndefined(role) || role !== implicitRole) return VALID_RESULT;
2155
2239
  const diagnostic = HtmlDiagnostics.implicitRoleRedundant(tag, implicitRole);
2156
2240
  return [{
2157
2241
  valid: false,
@@ -2162,8 +2246,8 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
2162
2246
  }];
2163
2247
  }
2164
2248
  static #checkStandaloneRegion({ tag, props, implicitRole }) {
2165
- if (props.role !== "region") return NO_VIOLATIONS;
2166
- if (!hasStandaloneRole(tag)) return NO_VIOLATIONS;
2249
+ if (props.role !== "region") return VALID_RESULT;
2250
+ if (!hasStandaloneRole(tag)) return VALID_RESULT;
2167
2251
  const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
2168
2252
  return [{
2169
2253
  valid: false,
@@ -2174,7 +2258,7 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
2174
2258
  }];
2175
2259
  }
2176
2260
  static #checkInvalidAriaAttributes({ tag, props, effectiveRole }) {
2177
- if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
2261
+ if (effectiveRole === "none" || effectiveRole === "presentation") return VALID_RESULT;
2178
2262
  const results = [];
2179
2263
  iterate.forEachEntry(props, (key) => {
2180
2264
  if (!key.startsWith("aria-")) return;
@@ -2191,63 +2275,33 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
2191
2275
  });
2192
2276
  return results;
2193
2277
  }
2194
- static #isValidAriaValue(value, type) {
2195
- switch (type.kind) {
2196
- case "boolean": return value === "true" || value === "false" || value === true || value === false;
2197
- case "tristate": return value === "true" || value === "false" || value === "mixed" || value === true || value === false;
2198
- case "number": return strictNumeric(value) !== void 0;
2199
- case "integer": {
2200
- const n = strictNumeric(value);
2201
- if (n === void 0 || !Number.isInteger(n)) return false;
2202
- if (type.min !== void 0 && n < type.min) return false;
2203
- if (type.max !== void 0 && n > type.max) return false;
2204
- return true;
2205
- }
2206
- case "enum": return typeof value === "string" && type.values.has(value);
2207
- }
2208
- }
2209
- static #describeExpected(type) {
2210
- switch (type.kind) {
2211
- case "boolean": return "\"true\" or \"false\"";
2212
- case "tristate": return "\"true\", \"false\", or \"mixed\"";
2213
- case "number": return "a finite number";
2214
- case "integer": {
2215
- const parts = ["an integer"];
2216
- if (type.min !== void 0 && type.max !== void 0) parts.push(`between ${type.min} and ${type.max}`);
2217
- else if (type.min !== void 0) parts.push(`≥ ${type.min}`);
2218
- else if (type.max !== void 0) parts.push(`≤ ${type.max}`);
2219
- return parts.join(" ");
2220
- }
2221
- case "enum": return [...type.values].map((v) => `"${v}"`).join(", ");
2222
- }
2223
- }
2224
2278
  static #checkAriaAttributeValues({ props, effectiveRole }) {
2225
- if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
2279
+ if (effectiveRole === "none" || effectiveRole === "presentation") return VALID_RESULT;
2226
2280
  const results = [];
2227
2281
  iterate.forEachEntry(props, (key, value) => {
2228
2282
  if (!key.startsWith("aria-")) return;
2229
2283
  const type = ARIA_VALUE_TYPES.get(key);
2230
2284
  if (!isNonNull(type)) return;
2231
- if (AriaPolicyEngine.#isValidAriaValue(value, type)) return;
2285
+ if (isValidAriaValue(value, type)) return;
2232
2286
  results.push({
2233
2287
  valid: false,
2234
2288
  fixable: true,
2235
2289
  severity: "warning",
2236
2290
  attribute: key,
2237
- diagnostic: AriaDiagnostics.invalidAttributeValue(key, value, AriaPolicyEngine.#describeExpected(type)),
2291
+ diagnostic: AriaDiagnostics.invalidAttributeValue(key, value, describeExpected(type)),
2238
2292
  fix: AriaPolicyEngine.#makeRemoveAttributeFix(key)
2239
2293
  });
2240
2294
  });
2241
2295
  return results;
2242
2296
  }
2243
2297
  static #checkRedundantAriaLevel({ tag, props, effectiveRole }) {
2244
- if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
2298
+ if (effectiveRole === "none" || effectiveRole === "presentation") return VALID_RESULT;
2245
2299
  const implicitLevel = HEADING_IMPLICIT_LEVELS.get(tag);
2246
- if (!isNonNull(implicitLevel)) return NO_VIOLATIONS;
2300
+ if (!isNonNull(implicitLevel)) return VALID_RESULT;
2247
2301
  const raw = props["aria-level"];
2248
- if (!isNonNull(raw)) return NO_VIOLATIONS;
2302
+ if (!isNonNull(raw)) return VALID_RESULT;
2249
2303
  const n = strictNumeric(raw);
2250
- if (n === void 0 || n !== implicitLevel) return NO_VIOLATIONS;
2304
+ if (n === void 0 || n !== implicitLevel) return VALID_RESULT;
2251
2305
  return [{
2252
2306
  valid: false,
2253
2307
  fixable: true,
@@ -2258,7 +2312,7 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
2258
2312
  }];
2259
2313
  }
2260
2314
  static #checkNameProhibitedRoles({ props, effectiveRole }) {
2261
- if (!effectiveRole || effectiveRole === "none" || effectiveRole === "presentation" || !NAME_PROHIBITED_ROLES.has(effectiveRole)) return NO_VIOLATIONS;
2315
+ if (!effectiveRole || effectiveRole === "none" || effectiveRole === "presentation" || !NAME_PROHIBITED_ROLES.has(effectiveRole)) return VALID_RESULT;
2262
2316
  const results = [];
2263
2317
  for (const key of NAME_PROHIBITED_ATTRIBUTES) {
2264
2318
  if (!(key in props)) continue;
@@ -2274,9 +2328,9 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
2274
2328
  return results;
2275
2329
  }
2276
2330
  static #checkNameRequiredRoles({ tag, props, effectiveRole }) {
2277
- if (!effectiveRole || !NAME_REQUIRED_ROLES.has(effectiveRole)) return NO_VIOLATIONS;
2278
- if ("aria-label" in props || "aria-labelledby" in props) return NO_VIOLATIONS;
2279
- if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return NO_VIOLATIONS;
2331
+ if (!effectiveRole || !NAME_REQUIRED_ROLES.has(effectiveRole)) return VALID_RESULT;
2332
+ if ("aria-label" in props || "aria-labelledby" in props) return VALID_RESULT;
2333
+ if (tag === "img" && isString(props.alt) && props.alt.length > 0) return VALID_RESULT;
2280
2334
  return [{
2281
2335
  valid: false,
2282
2336
  fixable: false,
@@ -2289,12 +2343,12 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
2289
2343
  diagnosticFor: (attribute, role) => AriaDiagnostics.requiredProperty(attribute, role)
2290
2344
  };
2291
2345
  static #checkRequiredAriaProperties(context) {
2292
- if (!isNonNull(context.props.role)) return NO_VIOLATIONS;
2346
+ if (!isNonNull(context.props.role)) return VALID_RESULT;
2293
2347
  return checkRequiredAttributes(AriaPolicyEngine.#requiredAriaPropertiesRule, context);
2294
2348
  }
2295
2349
  static #checkAriaHiddenOnFocusable({ tag, props }) {
2296
- if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return NO_VIOLATIONS;
2297
- if (!isPotentiallyFocusable(tag, props)) return NO_VIOLATIONS;
2350
+ if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return VALID_RESULT;
2351
+ if (!isPotentiallyFocusable(tag, props)) return VALID_RESULT;
2298
2352
  return [{
2299
2353
  valid: false,
2300
2354
  fixable: false,
@@ -2304,7 +2358,7 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
2304
2358
  }];
2305
2359
  }
2306
2360
  static #checkPresentationalAriaAttributes({ tag, props, effectiveRole }) {
2307
- if (effectiveRole !== "none" && effectiveRole !== "presentation") return NO_VIOLATIONS;
2361
+ if (effectiveRole !== "none" && effectiveRole !== "presentation") return VALID_RESULT;
2308
2362
  const results = [];
2309
2363
  iterate.forEachEntry(props, (key) => {
2310
2364
  if (!key.startsWith("aria-")) return;
@@ -2321,10 +2375,10 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
2321
2375
  return results;
2322
2376
  }
2323
2377
  static #checkMissingLiveRegion({ effectiveRole, props }) {
2324
- if (!effectiveRole) return NO_VIOLATIONS;
2378
+ if (!effectiveRole) return VALID_RESULT;
2325
2379
  const impliedLive = LIVE_REGION_ROLES.get(effectiveRole);
2326
- if (!impliedLive) return NO_VIOLATIONS;
2327
- if ("aria-live" in props) return NO_VIOLATIONS;
2380
+ if (!impliedLive) return VALID_RESULT;
2381
+ if ("aria-live" in props) return VALID_RESULT;
2328
2382
  return [{
2329
2383
  valid: false,
2330
2384
  fixable: true,
@@ -2364,8 +2418,8 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
2364
2418
  };
2365
2419
  static #checkInvalidAriaRelevant({ props }) {
2366
2420
  const relevant = props["aria-relevant"];
2367
- if (relevant === void 0) return NO_VIOLATIONS;
2368
- if (typeof relevant !== "string") return NO_VIOLATIONS;
2421
+ if (isUndefined(relevant)) return VALID_RESULT;
2422
+ if (!isString(relevant)) return VALID_RESULT;
2369
2423
  const tokens = relevant.trim().split(/\s+/);
2370
2424
  const invalid = tokens.filter((t) => !VALID_RELEVANT_TOKENS.has(t));
2371
2425
  if (invalid.length > 0) return [{
@@ -2384,7 +2438,7 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
2384
2438
  diagnostic: AriaDiagnostics.relevantSuperseded(),
2385
2439
  fix: AriaPolicyEngine.#normalizeRelevantAllFix
2386
2440
  }];
2387
- return NO_VIOLATIONS;
2441
+ return VALID_RESULT;
2388
2442
  }
2389
2443
  };
2390
2444
  //#endregion
@@ -4876,9 +4930,10 @@ function render({ runtime, state, slots, slotValidator, childrenEvaluator, eleme
4876
4930
  /** Vue-specific addition on top of `FactoryOptions`. */
4877
4931
  const VUE_FIELD_VALIDATORS = { filterProps: (v) => v === void 0 || isFunction(v) };
4878
4932
  /**
4879
- * Type guard narrowing the generic `FactoryOptions` shape down to
4880
- * `VueFactoryOptions` — the type `buildRuntime` is declared against. See
4881
- * `isFactoryOptionsLike` for what this does and doesn't validate.
4933
+ * Type guard narrowing a concrete contract `C` down to `C & VueFactoryOptions` — the type
4934
+ * `buildRuntime` is declared against. Single-generic, matching `createContractComponent`'s own
4935
+ * `C extends VueFactoryOptions` (Phase 2 of the `defineContract` refactor — was 5 independent
4936
+ * generics). See `isFactoryOptionsLike` for what this does and doesn't validate.
4882
4937
  */
4883
4938
  function isVueFactoryOptions(options) {
4884
4939
  return isFactoryOptionsLike(options, VUE_FIELD_VALIDATORS);
@@ -4907,9 +4962,21 @@ function isVueFactoryOptions(options) {
4907
4962
  * Pass `subComponents` to attach named sub-components (`Card.Header`) and `onElement` to run
4908
4963
  * setup once the real DOM element exists — both purely additive on top of the generated
4909
4964
  * component.
4965
+ *
4966
+ * `TDefault`/`Props`/`Variants`/`TPreset`/`TPlugin` are each `ContractXOf<C>`-derived *defaults*
4967
+ * on this function's own type parameter list, mirroring `@praxis-kit/react`'s identical fix — see
4968
+ * that adapter's own doc comment for why (computed as function type-parameter defaults, not inline
4969
+ * body computations, which doesn't resolve for a still-abstract `C`). No `TAllowed` here, matching
4970
+ * this adapter's pre-refactor behavior — Vue never threaded it as its own generic.
4910
4971
  */
4911
4972
  function createContractComponent(options) {
4912
4973
  invariant(isVueFactoryOptions(options), "options is not a valid VueFactoryOptions object");
4974
+ /**
4975
+ * `C` and `TDefault`/`Props`/`Variants`/`TPreset` are formally independent type parameters to
4976
+ * the checker — see `@praxis-kit/react`'s identical comment for the full explanation. The
4977
+ * `invariant` above is what actually guarantees `options` is `VueFactoryOptions` shaped; this
4978
+ * assertion bridges the gap in the compiler's reasoning.
4979
+ */
4913
4980
  const bundle = buildRuntime(options);
4914
4981
  const { onElement } = options;
4915
4982
  const Component = defineComponent({
@@ -4942,4 +5009,4 @@ function createContractComponent(options) {
4942
5009
  return assembled;
4943
5010
  }
4944
5011
  //#endregion
4945
- export { Slottable, createContractComponent, defineContractComponent };
5012
+ export { Slottable, createContractComponent };