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