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/{build-runtime-CJ_nQEaZ.js → build-runtime-cNkW-D62.js} +331 -207
- package/dist/contract/index.d.ts +396 -14
- package/dist/contract/index.js +66 -1
- package/dist/eslint/index.js +11 -5
- package/dist/{index-DiPzmjWW.d.ts → index-zJDSXTxL.d.ts} +342 -66
- package/dist/lit/index.d.ts +251 -55
- package/dist/lit/index.js +275 -215
- package/dist/preact/index.d.ts +296 -52
- package/dist/preact/index.js +277 -210
- package/dist/react/index.d.ts +13 -4
- package/dist/react/index.js +22 -72
- package/dist/react/legacy.d.ts +9 -4
- package/dist/react/legacy.js +14 -2
- package/dist/solid/index.d.ts +253 -40
- package/dist/solid/index.js +271 -210
- package/dist/svelte/index.d.ts +203 -33
- package/dist/svelte/index.js +270 -207
- package/dist/tailwind/index.d.ts +12 -1
- package/dist/vite-plugin/index.js +289 -199
- package/dist/vue/index.d.ts +252 -41
- package/dist/vue/index.js +277 -210
- package/dist/web/index.d.ts +271 -53
- package/dist/web/index.js +275 -215
- package/package.json +5 -5
package/dist/preact/index.js
CHANGED
|
@@ -18,11 +18,6 @@ function applyDisplayName(component, name) {
|
|
|
18
18
|
Object.assign(component, { displayName: name ?? "PolymorphicComponent" });
|
|
19
19
|
}
|
|
20
20
|
//#endregion
|
|
21
|
-
//#region ../../lib/adapter-utils/src/runtime/define-component.ts
|
|
22
|
-
function defineContractComponent(options) {
|
|
23
|
-
return (factory) => factory(options);
|
|
24
|
-
}
|
|
25
|
-
//#endregion
|
|
26
21
|
//#region ../../lib/adapter-utils/src/runtime/assemble-compound-component.ts
|
|
27
22
|
/**
|
|
28
23
|
* Assembles a compound component by attaching sub-components to a
|
|
@@ -1590,72 +1585,100 @@ var InvariantBase = class {
|
|
|
1590
1585
|
}
|
|
1591
1586
|
};
|
|
1592
1587
|
//#endregion
|
|
1593
|
-
//#region ../../lib/contract/src/aria/
|
|
1594
|
-
const REQUIRED_ARIA_PROPERTIES = {
|
|
1595
|
-
combobox: ["aria-expanded"],
|
|
1596
|
-
option: ["aria-selected"],
|
|
1597
|
-
slider: ["aria-valuenow"],
|
|
1598
|
-
scrollbar: ["aria-controls", "aria-valuenow"],
|
|
1599
|
-
spinbutton: ["aria-valuenow"]
|
|
1600
|
-
};
|
|
1601
|
-
//#endregion
|
|
1602
|
-
//#region ../../lib/contract/src/aria/spec/roles/name-required.ts
|
|
1603
|
-
const NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
|
|
1604
|
-
//#endregion
|
|
1605
|
-
//#region ../../lib/contract/src/aria/spec/roles/name-prohibited.ts
|
|
1606
|
-
const NAME_PROHIBITED_ROLES = /* @__PURE__ */ new Set([
|
|
1607
|
-
"caption",
|
|
1608
|
-
"code",
|
|
1609
|
-
"deletion",
|
|
1610
|
-
"emphasis",
|
|
1611
|
-
"generic",
|
|
1612
|
-
"insertion",
|
|
1613
|
-
"none",
|
|
1614
|
-
"paragraph",
|
|
1615
|
-
"presentation",
|
|
1616
|
-
"strong",
|
|
1617
|
-
"subscript",
|
|
1618
|
-
"superscript"
|
|
1619
|
-
]);
|
|
1620
|
-
const NAME_PROHIBITED_ATTRIBUTES = ["aria-label", "aria-labelledby"];
|
|
1621
|
-
//#endregion
|
|
1622
|
-
//#region ../../lib/contract/src/aria/spec/validators/required-properties-validator.ts
|
|
1623
|
-
const NO_VIOLATIONS$1 = [{ valid: true }];
|
|
1624
|
-
function requiredAttributeByRole(roles, attribute) {
|
|
1625
|
-
return Object.fromEntries([...roles].map((role) => [role, [attribute]]));
|
|
1626
|
-
}
|
|
1588
|
+
//#region ../../lib/contract/src/aria/plan-cache.ts
|
|
1627
1589
|
/**
|
|
1628
|
-
*
|
|
1590
|
+
* Validation-plan cache machinery for `AriaPolicyEngine`: computing a cache key from a
|
|
1591
|
+
* (tag, props) usage site, and diffing/replaying the props transformation a validation pass
|
|
1592
|
+
* produced (an {@link AriaPlan}'s `removals`/`updates`) without re-running the ARIA rule
|
|
1593
|
+
* pipeline itself. Extracted because these four functions are a cohesive subsystem in their own
|
|
1594
|
+
* right — a small revalidation-caching scheme — not specifically ARIA *policy*; the actual
|
|
1595
|
+
* cache storage (an `LRUCache` instance) stays owned by `AriaPolicyEngine` itself, since it's
|
|
1596
|
+
* per-engine-instance state, not something this module needs to hold.
|
|
1597
|
+
*/
|
|
1598
|
+
/**
|
|
1599
|
+
* Cache key covering only the aria-relevant subset of props (tag + role + aria-* attrs) —
|
|
1600
|
+
* exactly what the built-in pipeline reads. Non-aria props (className, onClick, etc.) do
|
|
1601
|
+
* not affect built-in ARIA decisions and are excluded so cache hits survive re-renders
|
|
1602
|
+
* that only change non-aria props. This key alone is unsound for a policy engine's own
|
|
1603
|
+
* extra rules, which may read arbitrary props outside this set — extend it with
|
|
1604
|
+
* {@link extraRulesKeySuffix}, or bypass the cache, to account for that.
|
|
1629
1605
|
*
|
|
1630
|
-
*
|
|
1631
|
-
*
|
|
1606
|
+
* Returns `null` (meaning "don't cache") when `tag` isn't a plain intrinsic string tag —
|
|
1607
|
+
* `isString(tag)` inline rather than importing `AriaPolicyEngine`'s own `isIntrinsicTag`: the
|
|
1608
|
+
* check is a one-liner, and importing it back from the engine module would be a needless
|
|
1609
|
+
* dependency in the other direction for a plan-cache module the engine itself depends on.
|
|
1632
1610
|
*/
|
|
1633
|
-
function
|
|
1634
|
-
if (!
|
|
1635
|
-
const
|
|
1636
|
-
if (
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1611
|
+
function createPlanKey(tag, props) {
|
|
1612
|
+
if (!isString(tag)) return null;
|
|
1613
|
+
const parts = [tag];
|
|
1614
|
+
if (isString(props.role)) parts.push(`role:${props.role}`);
|
|
1615
|
+
if (tag === "input" && isString(props.type)) parts.push(`type:${props.type}`);
|
|
1616
|
+
if (tag === "img") parts.push(`alt:${props.alt === "" ? "empty" : "present"}`);
|
|
1617
|
+
const ariaEntries = [];
|
|
1618
|
+
iterate.forEachEntry(props, (k, v) => {
|
|
1619
|
+
if (!k.startsWith("aria-")) return;
|
|
1620
|
+
if (!isString(v) && !isNumber(v) && typeof v !== "boolean") return;
|
|
1621
|
+
ariaEntries.push(`${k}:${String(v)}`);
|
|
1622
|
+
});
|
|
1623
|
+
if (ariaEntries.length > 0) parts.push(...ariaEntries.sort());
|
|
1624
|
+
return parts.join("|");
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Extends the base cache key with the props each extra rule declares it reads (`readsProps`).
|
|
1628
|
+
* Returns `null` — meaning "don't cache" — if any extra rule omits `readsProps` (it may read
|
|
1629
|
+
* arbitrary props the key can't account for) or if a declared prop's value isn't a primitive
|
|
1630
|
+
* (object/array identity isn't stably representable in a string key).
|
|
1631
|
+
*/
|
|
1632
|
+
function extraRulesKeySuffix(extraRules, props) {
|
|
1633
|
+
const parts = [];
|
|
1634
|
+
for (const rule of extraRules) {
|
|
1635
|
+
const readsProps = rule.readsProps;
|
|
1636
|
+
if (!isNonNull(readsProps)) return null;
|
|
1637
|
+
for (const propKey of readsProps) {
|
|
1638
|
+
const v = props[propKey];
|
|
1639
|
+
if (isDefined(v) && !isString(v) && !isNumber(v) && typeof v !== "boolean") return null;
|
|
1640
|
+
parts.push(`x:${propKey}:${String(v)}`);
|
|
1641
|
+
}
|
|
1647
1642
|
}
|
|
1648
|
-
return
|
|
1643
|
+
return parts.sort().join("|");
|
|
1644
|
+
}
|
|
1645
|
+
/** Diffs a validation pass's input/output props into a replayable plan: which keys were removed
|
|
1646
|
+
* entirely, and which keys were added or changed value (and to what). */
|
|
1647
|
+
function computePlan(inputProps, resultProps) {
|
|
1648
|
+
const removals = /* @__PURE__ */ new Set();
|
|
1649
|
+
const updates = {};
|
|
1650
|
+
iterate.forEachKey(inputProps, (key) => {
|
|
1651
|
+
if (!(key in resultProps)) removals.add(key);
|
|
1652
|
+
});
|
|
1653
|
+
iterate.forEachEntry(resultProps, (key, resultVal) => {
|
|
1654
|
+
if (inputProps[key] !== resultVal) updates[key] = resultVal;
|
|
1655
|
+
});
|
|
1656
|
+
return {
|
|
1657
|
+
removals,
|
|
1658
|
+
updates
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
/** Replays a cached plan's removals/updates against a fresh `props` object, without re-running
|
|
1662
|
+
* the rule pipeline that originally produced the plan. */
|
|
1663
|
+
function applyPlan(props, removals, updates) {
|
|
1664
|
+
const hasRemovals = removals.size > 0;
|
|
1665
|
+
const hasUpdates = Object.keys(updates).length > 0;
|
|
1666
|
+
if (!hasRemovals && !hasUpdates) return props;
|
|
1667
|
+
const next = {};
|
|
1668
|
+
iterate.forEachEntry(props, (k, v) => {
|
|
1669
|
+
if (!removals.has(k)) next[k] = v;
|
|
1670
|
+
});
|
|
1671
|
+
Object.assign(next, updates);
|
|
1672
|
+
return next;
|
|
1649
1673
|
}
|
|
1650
1674
|
//#endregion
|
|
1651
|
-
//#region ../../lib/contract/src/aria/spec/
|
|
1652
|
-
const
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1675
|
+
//#region ../../lib/contract/src/aria/spec/attributes/aria-relevant-tokens.ts
|
|
1676
|
+
const VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set([
|
|
1677
|
+
"additions",
|
|
1678
|
+
"removals",
|
|
1679
|
+
"text",
|
|
1680
|
+
"all"
|
|
1657
1681
|
]);
|
|
1658
|
-
const ATOMIC_REQUIREMENTS = requiredAttributeByRole(LIVE_REGION_ROLES.keys(), "aria-atomic");
|
|
1659
1682
|
//#endregion
|
|
1660
1683
|
//#region ../../lib/contract/src/aria/spec/attributes/aria-value-types.ts
|
|
1661
1684
|
const ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
|
|
@@ -1780,24 +1803,6 @@ const ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
|
|
|
1780
1803
|
}]
|
|
1781
1804
|
]);
|
|
1782
1805
|
//#endregion
|
|
1783
|
-
//#region ../../lib/contract/src/aria/spec/attributes/aria-relevant-tokens.ts
|
|
1784
|
-
const VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set([
|
|
1785
|
-
"additions",
|
|
1786
|
-
"removals",
|
|
1787
|
-
"text",
|
|
1788
|
-
"all"
|
|
1789
|
-
]);
|
|
1790
|
-
//#endregion
|
|
1791
|
-
//#region ../../lib/contract/src/aria/spec/elements/heading-implicit-levels.ts
|
|
1792
|
-
const HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
|
|
1793
|
-
["h1", 1],
|
|
1794
|
-
["h2", 2],
|
|
1795
|
-
["h3", 3],
|
|
1796
|
-
["h4", 4],
|
|
1797
|
-
["h5", 5],
|
|
1798
|
-
["h6", 6]
|
|
1799
|
-
]);
|
|
1800
|
-
//#endregion
|
|
1801
1806
|
//#region ../../lib/contract/src/aria/spec/elements/focusable.ts
|
|
1802
1807
|
function parseTabIndex(raw) {
|
|
1803
1808
|
if (typeof raw === "number") return Number.isInteger(raw) ? raw : void 0;
|
|
@@ -1848,24 +1853,156 @@ function isPotentiallyFocusable(tag, props) {
|
|
|
1848
1853
|
}
|
|
1849
1854
|
}
|
|
1850
1855
|
//#endregion
|
|
1851
|
-
//#region ../../lib/contract/src/aria/
|
|
1856
|
+
//#region ../../lib/contract/src/aria/spec/elements/heading-implicit-levels.ts
|
|
1857
|
+
const HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
|
|
1858
|
+
["h1", 1],
|
|
1859
|
+
["h2", 2],
|
|
1860
|
+
["h3", 3],
|
|
1861
|
+
["h4", 4],
|
|
1862
|
+
["h5", 5],
|
|
1863
|
+
["h6", 6]
|
|
1864
|
+
]);
|
|
1865
|
+
//#endregion
|
|
1866
|
+
//#region ../../lib/contract/src/aria/spec/validators/required-properties-validator.ts
|
|
1852
1867
|
const NO_VIOLATIONS = [{ valid: true }];
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
return isString(tag);
|
|
1868
|
+
function requiredAttributeByRole(roles, attribute) {
|
|
1869
|
+
return Object.fromEntries([...roles].map((role) => [role, [attribute]]));
|
|
1856
1870
|
}
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1871
|
+
/**
|
|
1872
|
+
* Reports missing attributes required by an element's effective ARIA role.
|
|
1873
|
+
*
|
|
1874
|
+
* Shared by validators that differ only in their role-to-attribute mapping and diagnostic
|
|
1875
|
+
* generation.
|
|
1876
|
+
*/
|
|
1877
|
+
function checkRequiredAttributes(requirement, { props, effectiveRole }) {
|
|
1878
|
+
if (!effectiveRole) return NO_VIOLATIONS;
|
|
1879
|
+
const requiredAttributes = requirement.attributesByRole[effectiveRole];
|
|
1880
|
+
if (!requiredAttributes) return NO_VIOLATIONS;
|
|
1881
|
+
const results = [];
|
|
1882
|
+
for (const attribute of requiredAttributes) {
|
|
1883
|
+
if (attribute in props) continue;
|
|
1884
|
+
results.push({
|
|
1885
|
+
valid: false,
|
|
1886
|
+
fixable: false,
|
|
1887
|
+
severity: "warning",
|
|
1888
|
+
attribute,
|
|
1889
|
+
diagnostic: requirement.diagnosticFor(attribute, effectiveRole)
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
return results;
|
|
1860
1893
|
}
|
|
1894
|
+
//#endregion
|
|
1895
|
+
//#region ../../lib/contract/src/aria/spec/roles/live-region.ts
|
|
1896
|
+
const LIVE_REGION_ROLES = /* @__PURE__ */ new Map([
|
|
1897
|
+
["alert", "assertive"],
|
|
1898
|
+
["status", "polite"],
|
|
1899
|
+
["log", "polite"],
|
|
1900
|
+
["timer", "off"]
|
|
1901
|
+
]);
|
|
1902
|
+
const ATOMIC_REQUIREMENTS = requiredAttributeByRole(LIVE_REGION_ROLES.keys(), "aria-atomic");
|
|
1903
|
+
//#endregion
|
|
1904
|
+
//#region ../../lib/contract/src/aria/spec/roles/name-prohibited.ts
|
|
1905
|
+
const NAME_PROHIBITED_ROLES = /* @__PURE__ */ new Set([
|
|
1906
|
+
"caption",
|
|
1907
|
+
"code",
|
|
1908
|
+
"deletion",
|
|
1909
|
+
"emphasis",
|
|
1910
|
+
"generic",
|
|
1911
|
+
"insertion",
|
|
1912
|
+
"none",
|
|
1913
|
+
"paragraph",
|
|
1914
|
+
"presentation",
|
|
1915
|
+
"strong",
|
|
1916
|
+
"subscript",
|
|
1917
|
+
"superscript"
|
|
1918
|
+
]);
|
|
1919
|
+
const NAME_PROHIBITED_ATTRIBUTES = ["aria-label", "aria-labelledby"];
|
|
1920
|
+
//#endregion
|
|
1921
|
+
//#region ../../lib/contract/src/aria/spec/roles/name-required.ts
|
|
1922
|
+
const NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
|
|
1923
|
+
//#endregion
|
|
1924
|
+
//#region ../../lib/contract/src/aria/spec/roles/required-properties.ts
|
|
1925
|
+
const REQUIRED_ARIA_PROPERTIES = {
|
|
1926
|
+
combobox: ["aria-expanded"],
|
|
1927
|
+
option: ["aria-selected"],
|
|
1928
|
+
slider: ["aria-valuenow"],
|
|
1929
|
+
scrollbar: ["aria-controls", "aria-valuenow"],
|
|
1930
|
+
spinbutton: ["aria-valuenow"]
|
|
1931
|
+
};
|
|
1932
|
+
//#endregion
|
|
1933
|
+
//#region ../../lib/contract/src/aria/value-validation.ts
|
|
1934
|
+
/**
|
|
1935
|
+
* ARIA attribute value type system: what counts as a valid value for a given
|
|
1936
|
+
* {@link AriaValueType}, and how to describe what was expected when it isn't. Extracted from
|
|
1937
|
+
* `AriaPolicyEngine` — these three functions are cohesive on their own (they're describing a
|
|
1938
|
+
* type system, not policy), and `#checkAriaAttributeValues` (kept in `aria-policy-engine.ts`
|
|
1939
|
+
* alongside every other rule implementation — see that file's own pipeline) is now just a thin
|
|
1940
|
+
* caller of {@link isValidAriaValue}/{@link describeExpected}.
|
|
1941
|
+
*/
|
|
1942
|
+
/**
|
|
1943
|
+
* Strict numeric coercion for ARIA attribute values. Unlike `parseFloat`/`parseInt`, the whole
|
|
1944
|
+
* string must be a number — `"12abc"` is rejected, not read as `12` — and an empty / whitespace
|
|
1945
|
+
* string is rejected rather than coerced to `0` the way `Number("")` would.
|
|
1946
|
+
*
|
|
1947
|
+
* Exported (not local to `isValidAriaValue`) because `AriaPolicyEngine.#checkRedundantAriaLevel`
|
|
1948
|
+
* also needs it, independent of the general ARIA value-type check this module otherwise exists
|
|
1949
|
+
* for — `aria-level`'s "redundant" check is about comparing a numeric attribute value to a
|
|
1950
|
+
* known implicit level, not about validating the value's type.
|
|
1951
|
+
*/
|
|
1861
1952
|
function strictNumeric(value) {
|
|
1862
|
-
if (
|
|
1863
|
-
if (
|
|
1953
|
+
if (isNumber(value)) return Number.isFinite(value) ? value : void 0;
|
|
1954
|
+
if (!isString(value)) return void 0;
|
|
1864
1955
|
const trimmed = value.trim();
|
|
1865
1956
|
if (trimmed === "") return void 0;
|
|
1866
1957
|
const n = Number(trimmed);
|
|
1867
1958
|
return Number.isFinite(n) ? n : void 0;
|
|
1868
1959
|
}
|
|
1960
|
+
/** Returns whether `value` satisfies `type`'s own value-type constraint (boolean/tristate/
|
|
1961
|
+
* number/integer/enum) — the WAI-ARIA attribute value type system, independent of which
|
|
1962
|
+
* attribute or role it's being checked for. */
|
|
1963
|
+
function isValidAriaValue(value, type) {
|
|
1964
|
+
switch (type.kind) {
|
|
1965
|
+
case "boolean": return value === "true" || value === "false" || value === true || value === false;
|
|
1966
|
+
case "tristate": return value === "true" || value === "false" || value === "mixed" || value === true || value === false;
|
|
1967
|
+
case "number": return strictNumeric(value) !== void 0;
|
|
1968
|
+
case "integer": {
|
|
1969
|
+
const n = strictNumeric(value);
|
|
1970
|
+
if (isUndefined(n) || !Number.isInteger(n)) return false;
|
|
1971
|
+
if (isDefined(type.min) && n < type.min) return false;
|
|
1972
|
+
if (isDefined(type.max) && n > type.max) return false;
|
|
1973
|
+
return true;
|
|
1974
|
+
}
|
|
1975
|
+
case "enum": return isString(value) && type.values.has(value);
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
/** Describes what a valid value for `type` looks like, for use in an invalid-value diagnostic
|
|
1979
|
+
* message (e.g. `aria-valuenow` got `"abc"`, expected "a finite number"). */
|
|
1980
|
+
function describeExpected(type) {
|
|
1981
|
+
switch (type.kind) {
|
|
1982
|
+
case "boolean": return "\"true\" or \"false\"";
|
|
1983
|
+
case "tristate": return "\"true\", \"false\", or \"mixed\"";
|
|
1984
|
+
case "number": return "a finite number";
|
|
1985
|
+
case "integer": {
|
|
1986
|
+
const parts = ["an integer"];
|
|
1987
|
+
if (isDefined(type.min) && isDefined(type.max)) parts.push(`between ${type.min} and ${type.max}`);
|
|
1988
|
+
else if (isDefined(type.min)) parts.push(`≥ ${type.min}`);
|
|
1989
|
+
else if (isDefined(type.max)) parts.push(`≤ ${type.max}`);
|
|
1990
|
+
return parts.join(" ");
|
|
1991
|
+
}
|
|
1992
|
+
case "enum": return [...type.values].map((v) => `"${v}"`).join(", ");
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
//#endregion
|
|
1996
|
+
//#region ../../lib/contract/src/aria/aria-policy-engine.ts
|
|
1997
|
+
const VALID_RESULT = [{ valid: true }];
|
|
1998
|
+
const NO_VARIANT_KEYS = /* @__PURE__ */ new Set();
|
|
1999
|
+
function isIntrinsicTag(tag) {
|
|
2000
|
+
return isString(tag);
|
|
2001
|
+
}
|
|
2002
|
+
function omitProp(obj, key) {
|
|
2003
|
+
const { [key]: _, ...rest } = obj;
|
|
2004
|
+
return rest;
|
|
2005
|
+
}
|
|
1869
2006
|
var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
1870
2007
|
#extraRules;
|
|
1871
2008
|
#variantKeys;
|
|
@@ -1998,73 +2135,20 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
1998
2135
|
else this.warn(d);
|
|
1999
2136
|
});
|
|
2000
2137
|
}
|
|
2001
|
-
static #createPlanKey(tag, props) {
|
|
2002
|
-
if (!isIntrinsicTag(tag)) return null;
|
|
2003
|
-
const parts = [tag];
|
|
2004
|
-
if (typeof props.role === "string") parts.push(`role:${props.role}`);
|
|
2005
|
-
if (tag === "input" && typeof props.type === "string") parts.push(`type:${props.type}`);
|
|
2006
|
-
if (tag === "img") parts.push(`alt:${props.alt === "" ? "empty" : "present"}`);
|
|
2007
|
-
const ariaEntries = [];
|
|
2008
|
-
iterate.forEachEntry(props, (k, v) => {
|
|
2009
|
-
if (!k.startsWith("aria-")) return;
|
|
2010
|
-
if (!isString(v) && !isNumber(v) && typeof v !== "boolean") return;
|
|
2011
|
-
ariaEntries.push(`${k}:${String(v)}`);
|
|
2012
|
-
});
|
|
2013
|
-
if (ariaEntries.length > 0) parts.push(...ariaEntries.sort());
|
|
2014
|
-
return parts.join("|");
|
|
2015
|
-
}
|
|
2016
|
-
static #extraRulesKeySuffix(extraRules, props) {
|
|
2017
|
-
const parts = [];
|
|
2018
|
-
for (const rule of extraRules) {
|
|
2019
|
-
const readsProps = rule.readsProps;
|
|
2020
|
-
if (!isNonNull(readsProps)) return null;
|
|
2021
|
-
for (const propKey of readsProps) {
|
|
2022
|
-
const v = props[propKey];
|
|
2023
|
-
if (v !== void 0 && !isString(v) && !isNumber(v) && typeof v !== "boolean") return null;
|
|
2024
|
-
parts.push(`x:${propKey}:${String(v)}`);
|
|
2025
|
-
}
|
|
2026
|
-
}
|
|
2027
|
-
return parts.sort().join("|");
|
|
2028
|
-
}
|
|
2029
|
-
static #computePlan(inputProps, resultProps) {
|
|
2030
|
-
const removals = /* @__PURE__ */ new Set();
|
|
2031
|
-
const updates = {};
|
|
2032
|
-
iterate.forEachKey(inputProps, (key) => {
|
|
2033
|
-
if (!(key in resultProps)) removals.add(key);
|
|
2034
|
-
});
|
|
2035
|
-
iterate.forEachEntry(resultProps, (key, resultVal) => {
|
|
2036
|
-
if (inputProps[key] !== resultVal) updates[key] = resultVal;
|
|
2037
|
-
});
|
|
2038
|
-
return {
|
|
2039
|
-
removals,
|
|
2040
|
-
updates
|
|
2041
|
-
};
|
|
2042
|
-
}
|
|
2043
|
-
static #applyPlan(props, removals, updates) {
|
|
2044
|
-
const hasRemovals = removals.size > 0;
|
|
2045
|
-
const hasUpdates = Object.keys(updates).length > 0;
|
|
2046
|
-
if (!hasRemovals && !hasUpdates) return props;
|
|
2047
|
-
const next = {};
|
|
2048
|
-
iterate.forEachEntry(props, (k, v) => {
|
|
2049
|
-
if (!removals.has(k)) next[k] = v;
|
|
2050
|
-
});
|
|
2051
|
-
Object.assign(next, updates);
|
|
2052
|
-
return next;
|
|
2053
|
-
}
|
|
2054
2138
|
validate(tag, props, extraProps) {
|
|
2055
2139
|
const ruleProps = extraProps ?? props;
|
|
2056
|
-
const baseKey =
|
|
2140
|
+
const baseKey = createPlanKey(tag, props);
|
|
2057
2141
|
let key = baseKey;
|
|
2058
2142
|
if (this.#extraRules.length > 0) {
|
|
2059
|
-
const suffix =
|
|
2143
|
+
const suffix = extraRulesKeySuffix(this.#extraRules, ruleProps);
|
|
2060
2144
|
key = isNonNull(baseKey) && isNonNull(suffix) ? `${baseKey}|${suffix}` : null;
|
|
2061
2145
|
}
|
|
2062
2146
|
if (!isNull(key)) {
|
|
2063
2147
|
const cached = this.#planCache.get(key);
|
|
2064
|
-
if (cached
|
|
2148
|
+
if (isDefined(cached)) {
|
|
2065
2149
|
if (cached.violations.length > 0) this.report(cached.violations);
|
|
2066
2150
|
return {
|
|
2067
|
-
props:
|
|
2151
|
+
props: applyPlan(props, cached.removals, cached.updates),
|
|
2068
2152
|
violations: cached.violations
|
|
2069
2153
|
};
|
|
2070
2154
|
}
|
|
@@ -2072,7 +2156,7 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2072
2156
|
const result = this.#extraRules.length ? AriaPolicyEngine.#evaluateWithRules(tag, props, this.#extraRules, extraProps, this.#variantKeys) : AriaPolicyEngine.evaluate(tag, props);
|
|
2073
2157
|
if (result.violations.length > 0) this.report(result.violations);
|
|
2074
2158
|
if (!isNull(key)) {
|
|
2075
|
-
const { removals, updates } =
|
|
2159
|
+
const { removals, updates } = computePlan(props, result.props);
|
|
2076
2160
|
const plan = {
|
|
2077
2161
|
removals,
|
|
2078
2162
|
updates,
|
|
@@ -2163,7 +2247,7 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2163
2247
|
];
|
|
2164
2248
|
static #checkInvalidRoleOverride({ tag, props, implicitRole }) {
|
|
2165
2249
|
const role = props.role;
|
|
2166
|
-
if (!implicitRole ||
|
|
2250
|
+
if (!implicitRole || isUndefined(role) || role === implicitRole) return VALID_RESULT;
|
|
2167
2251
|
if (isStrongImplicitRole(tag) && role === "region") {
|
|
2168
2252
|
const diagnostic = HtmlDiagnostics.implicitRoleOverride(tag, implicitRole, role);
|
|
2169
2253
|
return [{
|
|
@@ -2174,11 +2258,11 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2174
2258
|
diagnostic
|
|
2175
2259
|
}];
|
|
2176
2260
|
}
|
|
2177
|
-
return
|
|
2261
|
+
return VALID_RESULT;
|
|
2178
2262
|
}
|
|
2179
2263
|
static #checkRedundantRole({ tag, props, implicitRole }) {
|
|
2180
2264
|
const role = props.role;
|
|
2181
|
-
if (!implicitRole ||
|
|
2265
|
+
if (!implicitRole || isUndefined(role) || role !== implicitRole) return VALID_RESULT;
|
|
2182
2266
|
const diagnostic = HtmlDiagnostics.implicitRoleRedundant(tag, implicitRole);
|
|
2183
2267
|
return [{
|
|
2184
2268
|
valid: false,
|
|
@@ -2189,8 +2273,8 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2189
2273
|
}];
|
|
2190
2274
|
}
|
|
2191
2275
|
static #checkStandaloneRegion({ tag, props, implicitRole }) {
|
|
2192
|
-
if (props.role !== "region") return
|
|
2193
|
-
if (!hasStandaloneRole(tag)) return
|
|
2276
|
+
if (props.role !== "region") return VALID_RESULT;
|
|
2277
|
+
if (!hasStandaloneRole(tag)) return VALID_RESULT;
|
|
2194
2278
|
const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
|
|
2195
2279
|
return [{
|
|
2196
2280
|
valid: false,
|
|
@@ -2201,7 +2285,7 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2201
2285
|
}];
|
|
2202
2286
|
}
|
|
2203
2287
|
static #checkInvalidAriaAttributes({ tag, props, effectiveRole }) {
|
|
2204
|
-
if (effectiveRole === "none" || effectiveRole === "presentation") return
|
|
2288
|
+
if (effectiveRole === "none" || effectiveRole === "presentation") return VALID_RESULT;
|
|
2205
2289
|
const results = [];
|
|
2206
2290
|
iterate.forEachEntry(props, (key) => {
|
|
2207
2291
|
if (!key.startsWith("aria-")) return;
|
|
@@ -2218,63 +2302,33 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2218
2302
|
});
|
|
2219
2303
|
return results;
|
|
2220
2304
|
}
|
|
2221
|
-
static #isValidAriaValue(value, type) {
|
|
2222
|
-
switch (type.kind) {
|
|
2223
|
-
case "boolean": return value === "true" || value === "false" || value === true || value === false;
|
|
2224
|
-
case "tristate": return value === "true" || value === "false" || value === "mixed" || value === true || value === false;
|
|
2225
|
-
case "number": return strictNumeric(value) !== void 0;
|
|
2226
|
-
case "integer": {
|
|
2227
|
-
const n = strictNumeric(value);
|
|
2228
|
-
if (n === void 0 || !Number.isInteger(n)) return false;
|
|
2229
|
-
if (type.min !== void 0 && n < type.min) return false;
|
|
2230
|
-
if (type.max !== void 0 && n > type.max) return false;
|
|
2231
|
-
return true;
|
|
2232
|
-
}
|
|
2233
|
-
case "enum": return typeof value === "string" && type.values.has(value);
|
|
2234
|
-
}
|
|
2235
|
-
}
|
|
2236
|
-
static #describeExpected(type) {
|
|
2237
|
-
switch (type.kind) {
|
|
2238
|
-
case "boolean": return "\"true\" or \"false\"";
|
|
2239
|
-
case "tristate": return "\"true\", \"false\", or \"mixed\"";
|
|
2240
|
-
case "number": return "a finite number";
|
|
2241
|
-
case "integer": {
|
|
2242
|
-
const parts = ["an integer"];
|
|
2243
|
-
if (type.min !== void 0 && type.max !== void 0) parts.push(`between ${type.min} and ${type.max}`);
|
|
2244
|
-
else if (type.min !== void 0) parts.push(`≥ ${type.min}`);
|
|
2245
|
-
else if (type.max !== void 0) parts.push(`≤ ${type.max}`);
|
|
2246
|
-
return parts.join(" ");
|
|
2247
|
-
}
|
|
2248
|
-
case "enum": return [...type.values].map((v) => `"${v}"`).join(", ");
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
2251
2305
|
static #checkAriaAttributeValues({ props, effectiveRole }) {
|
|
2252
|
-
if (effectiveRole === "none" || effectiveRole === "presentation") return
|
|
2306
|
+
if (effectiveRole === "none" || effectiveRole === "presentation") return VALID_RESULT;
|
|
2253
2307
|
const results = [];
|
|
2254
2308
|
iterate.forEachEntry(props, (key, value) => {
|
|
2255
2309
|
if (!key.startsWith("aria-")) return;
|
|
2256
2310
|
const type = ARIA_VALUE_TYPES.get(key);
|
|
2257
2311
|
if (!isNonNull(type)) return;
|
|
2258
|
-
if (
|
|
2312
|
+
if (isValidAriaValue(value, type)) return;
|
|
2259
2313
|
results.push({
|
|
2260
2314
|
valid: false,
|
|
2261
2315
|
fixable: true,
|
|
2262
2316
|
severity: "warning",
|
|
2263
2317
|
attribute: key,
|
|
2264
|
-
diagnostic: AriaDiagnostics.invalidAttributeValue(key, value,
|
|
2318
|
+
diagnostic: AriaDiagnostics.invalidAttributeValue(key, value, describeExpected(type)),
|
|
2265
2319
|
fix: AriaPolicyEngine.#makeRemoveAttributeFix(key)
|
|
2266
2320
|
});
|
|
2267
2321
|
});
|
|
2268
2322
|
return results;
|
|
2269
2323
|
}
|
|
2270
2324
|
static #checkRedundantAriaLevel({ tag, props, effectiveRole }) {
|
|
2271
|
-
if (effectiveRole === "none" || effectiveRole === "presentation") return
|
|
2325
|
+
if (effectiveRole === "none" || effectiveRole === "presentation") return VALID_RESULT;
|
|
2272
2326
|
const implicitLevel = HEADING_IMPLICIT_LEVELS.get(tag);
|
|
2273
|
-
if (!isNonNull(implicitLevel)) return
|
|
2327
|
+
if (!isNonNull(implicitLevel)) return VALID_RESULT;
|
|
2274
2328
|
const raw = props["aria-level"];
|
|
2275
|
-
if (!isNonNull(raw)) return
|
|
2329
|
+
if (!isNonNull(raw)) return VALID_RESULT;
|
|
2276
2330
|
const n = strictNumeric(raw);
|
|
2277
|
-
if (n === void 0 || n !== implicitLevel) return
|
|
2331
|
+
if (n === void 0 || n !== implicitLevel) return VALID_RESULT;
|
|
2278
2332
|
return [{
|
|
2279
2333
|
valid: false,
|
|
2280
2334
|
fixable: true,
|
|
@@ -2285,7 +2339,7 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2285
2339
|
}];
|
|
2286
2340
|
}
|
|
2287
2341
|
static #checkNameProhibitedRoles({ props, effectiveRole }) {
|
|
2288
|
-
if (!effectiveRole || effectiveRole === "none" || effectiveRole === "presentation" || !NAME_PROHIBITED_ROLES.has(effectiveRole)) return
|
|
2342
|
+
if (!effectiveRole || effectiveRole === "none" || effectiveRole === "presentation" || !NAME_PROHIBITED_ROLES.has(effectiveRole)) return VALID_RESULT;
|
|
2289
2343
|
const results = [];
|
|
2290
2344
|
for (const key of NAME_PROHIBITED_ATTRIBUTES) {
|
|
2291
2345
|
if (!(key in props)) continue;
|
|
@@ -2301,9 +2355,9 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2301
2355
|
return results;
|
|
2302
2356
|
}
|
|
2303
2357
|
static #checkNameRequiredRoles({ tag, props, effectiveRole }) {
|
|
2304
|
-
if (!effectiveRole || !NAME_REQUIRED_ROLES.has(effectiveRole)) return
|
|
2305
|
-
if ("aria-label" in props || "aria-labelledby" in props) return
|
|
2306
|
-
if (tag === "img" &&
|
|
2358
|
+
if (!effectiveRole || !NAME_REQUIRED_ROLES.has(effectiveRole)) return VALID_RESULT;
|
|
2359
|
+
if ("aria-label" in props || "aria-labelledby" in props) return VALID_RESULT;
|
|
2360
|
+
if (tag === "img" && isString(props.alt) && props.alt.length > 0) return VALID_RESULT;
|
|
2307
2361
|
return [{
|
|
2308
2362
|
valid: false,
|
|
2309
2363
|
fixable: false,
|
|
@@ -2316,12 +2370,12 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2316
2370
|
diagnosticFor: (attribute, role) => AriaDiagnostics.requiredProperty(attribute, role)
|
|
2317
2371
|
};
|
|
2318
2372
|
static #checkRequiredAriaProperties(context) {
|
|
2319
|
-
if (!isNonNull(context.props.role)) return
|
|
2373
|
+
if (!isNonNull(context.props.role)) return VALID_RESULT;
|
|
2320
2374
|
return checkRequiredAttributes(AriaPolicyEngine.#requiredAriaPropertiesRule, context);
|
|
2321
2375
|
}
|
|
2322
2376
|
static #checkAriaHiddenOnFocusable({ tag, props }) {
|
|
2323
|
-
if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return
|
|
2324
|
-
if (!isPotentiallyFocusable(tag, props)) return
|
|
2377
|
+
if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return VALID_RESULT;
|
|
2378
|
+
if (!isPotentiallyFocusable(tag, props)) return VALID_RESULT;
|
|
2325
2379
|
return [{
|
|
2326
2380
|
valid: false,
|
|
2327
2381
|
fixable: false,
|
|
@@ -2331,7 +2385,7 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2331
2385
|
}];
|
|
2332
2386
|
}
|
|
2333
2387
|
static #checkPresentationalAriaAttributes({ tag, props, effectiveRole }) {
|
|
2334
|
-
if (effectiveRole !== "none" && effectiveRole !== "presentation") return
|
|
2388
|
+
if (effectiveRole !== "none" && effectiveRole !== "presentation") return VALID_RESULT;
|
|
2335
2389
|
const results = [];
|
|
2336
2390
|
iterate.forEachEntry(props, (key) => {
|
|
2337
2391
|
if (!key.startsWith("aria-")) return;
|
|
@@ -2348,10 +2402,10 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2348
2402
|
return results;
|
|
2349
2403
|
}
|
|
2350
2404
|
static #checkMissingLiveRegion({ effectiveRole, props }) {
|
|
2351
|
-
if (!effectiveRole) return
|
|
2405
|
+
if (!effectiveRole) return VALID_RESULT;
|
|
2352
2406
|
const impliedLive = LIVE_REGION_ROLES.get(effectiveRole);
|
|
2353
|
-
if (!impliedLive) return
|
|
2354
|
-
if ("aria-live" in props) return
|
|
2407
|
+
if (!impliedLive) return VALID_RESULT;
|
|
2408
|
+
if ("aria-live" in props) return VALID_RESULT;
|
|
2355
2409
|
return [{
|
|
2356
2410
|
valid: false,
|
|
2357
2411
|
fixable: true,
|
|
@@ -2391,8 +2445,8 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2391
2445
|
};
|
|
2392
2446
|
static #checkInvalidAriaRelevant({ props }) {
|
|
2393
2447
|
const relevant = props["aria-relevant"];
|
|
2394
|
-
if (relevant
|
|
2395
|
-
if (
|
|
2448
|
+
if (isUndefined(relevant)) return VALID_RESULT;
|
|
2449
|
+
if (!isString(relevant)) return VALID_RESULT;
|
|
2396
2450
|
const tokens = relevant.trim().split(/\s+/);
|
|
2397
2451
|
const invalid = tokens.filter((t) => !VALID_RELEVANT_TOKENS.has(t));
|
|
2398
2452
|
if (invalid.length > 0) return [{
|
|
@@ -2411,7 +2465,7 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2411
2465
|
diagnostic: AriaDiagnostics.relevantSuperseded(),
|
|
2412
2466
|
fix: AriaPolicyEngine.#normalizeRelevantAllFix
|
|
2413
2467
|
}];
|
|
2414
|
-
return
|
|
2468
|
+
return VALID_RESULT;
|
|
2415
2469
|
}
|
|
2416
2470
|
};
|
|
2417
2471
|
//#endregion
|
|
@@ -4978,9 +5032,10 @@ const PREACT_FIELD_VALIDATORS = {
|
|
|
4978
5032
|
filterProps: (v) => v === void 0 || isFunction(v)
|
|
4979
5033
|
};
|
|
4980
5034
|
/**
|
|
4981
|
-
* Type guard narrowing
|
|
4982
|
-
* `
|
|
4983
|
-
* `
|
|
5035
|
+
* Type guard narrowing a concrete contract `C` down to `C & PreactFactoryOptions` — the type
|
|
5036
|
+
* `buildRuntime` is declared against. Single-generic, matching `createContractComponent`'s own
|
|
5037
|
+
* `C extends PreactFactoryOptions` (Phase 2 of the `defineContract` refactor — was 5 independent
|
|
5038
|
+
* generics). See `isFactoryOptionsLike` for what this does and doesn't validate.
|
|
4984
5039
|
*/
|
|
4985
5040
|
function isPreactFactoryOptions(options) {
|
|
4986
5041
|
return isFactoryOptionsLike(options, PREACT_FIELD_VALIDATORS);
|
|
@@ -5007,9 +5062,21 @@ function isPreactFactoryOptions(options) {
|
|
|
5007
5062
|
* Returns a `forwardRef` component — `ref` is forwarded to the rendered host element. Pass
|
|
5008
5063
|
* `subComponents` to attach named sub-components (`Card.Header`) and `onElement` to run setup
|
|
5009
5064
|
* once the real DOM element exists.
|
|
5065
|
+
*
|
|
5066
|
+
* `TDefault`/`Props`/`Variants`/`TPreset`/`TPlugin` are each `ContractXOf<C>`-derived *defaults*
|
|
5067
|
+
* on this function's own type parameter list, mirroring `@praxis-kit/react`'s identical fix — see
|
|
5068
|
+
* that adapter's own doc comment for why (computed as function type-parameter defaults, not inline
|
|
5069
|
+
* body computations, which doesn't resolve for a still-abstract `C`). No `TAllowed` here, matching
|
|
5070
|
+
* this adapter's pre-refactor behavior — Preact never threaded it as its own generic.
|
|
5010
5071
|
*/
|
|
5011
5072
|
function createContractComponent(options) {
|
|
5012
5073
|
invariant(isPreactFactoryOptions(options), "options is not a valid PreactFactoryOptions object");
|
|
5074
|
+
/**
|
|
5075
|
+
* `C` and `TDefault`/`Props`/`Variants`/`TPreset` are formally independent type parameters to
|
|
5076
|
+
* the checker — see `@praxis-kit/react`'s identical comment for the full explanation. The
|
|
5077
|
+
* `invariant` above is what actually guarantees `options` is `PreactFactoryOptions` shaped; this
|
|
5078
|
+
* assertion bridges the gap in the compiler's reasoning.
|
|
5079
|
+
*/
|
|
5013
5080
|
const bundle = buildRuntime(options);
|
|
5014
5081
|
const { onElement } = options;
|
|
5015
5082
|
const Component = forwardRef(function Component(props, ref) {
|
|
@@ -5040,4 +5107,4 @@ function createContractComponent(options) {
|
|
|
5040
5107
|
return assembled;
|
|
5041
5108
|
}
|
|
5042
5109
|
//#endregion
|
|
5043
|
-
export { Slottable, createContractComponent
|
|
5110
|
+
export { Slottable, createContractComponent };
|