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/web/index.js
CHANGED
|
@@ -9,11 +9,6 @@ function invariant(condition, message) {
|
|
|
9
9
|
if (!condition) panic(message);
|
|
10
10
|
}
|
|
11
11
|
//#endregion
|
|
12
|
-
//#region ../../lib/adapter-utils/src/runtime/define-component.ts
|
|
13
|
-
function defineContractComponent(options) {
|
|
14
|
-
return (factory) => factory(options);
|
|
15
|
-
}
|
|
16
|
-
//#endregion
|
|
17
12
|
//#region ../../lib/adapter-utils/src/runtime/assemble-compound-component.ts
|
|
18
13
|
/**
|
|
19
14
|
* Assembles a compound component by attaching sub-components to a
|
|
@@ -1405,72 +1400,100 @@ var InvariantBase = class {
|
|
|
1405
1400
|
}
|
|
1406
1401
|
};
|
|
1407
1402
|
//#endregion
|
|
1408
|
-
//#region ../../lib/contract/src/aria/
|
|
1409
|
-
const REQUIRED_ARIA_PROPERTIES = {
|
|
1410
|
-
combobox: ["aria-expanded"],
|
|
1411
|
-
option: ["aria-selected"],
|
|
1412
|
-
slider: ["aria-valuenow"],
|
|
1413
|
-
scrollbar: ["aria-controls", "aria-valuenow"],
|
|
1414
|
-
spinbutton: ["aria-valuenow"]
|
|
1415
|
-
};
|
|
1416
|
-
//#endregion
|
|
1417
|
-
//#region ../../lib/contract/src/aria/spec/roles/name-required.ts
|
|
1418
|
-
const NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
|
|
1419
|
-
//#endregion
|
|
1420
|
-
//#region ../../lib/contract/src/aria/spec/roles/name-prohibited.ts
|
|
1421
|
-
const NAME_PROHIBITED_ROLES = /* @__PURE__ */ new Set([
|
|
1422
|
-
"caption",
|
|
1423
|
-
"code",
|
|
1424
|
-
"deletion",
|
|
1425
|
-
"emphasis",
|
|
1426
|
-
"generic",
|
|
1427
|
-
"insertion",
|
|
1428
|
-
"none",
|
|
1429
|
-
"paragraph",
|
|
1430
|
-
"presentation",
|
|
1431
|
-
"strong",
|
|
1432
|
-
"subscript",
|
|
1433
|
-
"superscript"
|
|
1434
|
-
]);
|
|
1435
|
-
const NAME_PROHIBITED_ATTRIBUTES = ["aria-label", "aria-labelledby"];
|
|
1436
|
-
//#endregion
|
|
1437
|
-
//#region ../../lib/contract/src/aria/spec/validators/required-properties-validator.ts
|
|
1438
|
-
const NO_VIOLATIONS$1 = [{ valid: true }];
|
|
1439
|
-
function requiredAttributeByRole(roles, attribute) {
|
|
1440
|
-
return Object.fromEntries([...roles].map((role) => [role, [attribute]]));
|
|
1441
|
-
}
|
|
1403
|
+
//#region ../../lib/contract/src/aria/plan-cache.ts
|
|
1442
1404
|
/**
|
|
1443
|
-
*
|
|
1405
|
+
* Validation-plan cache machinery for `AriaPolicyEngine`: computing a cache key from a
|
|
1406
|
+
* (tag, props) usage site, and diffing/replaying the props transformation a validation pass
|
|
1407
|
+
* produced (an {@link AriaPlan}'s `removals`/`updates`) without re-running the ARIA rule
|
|
1408
|
+
* pipeline itself. Extracted because these four functions are a cohesive subsystem in their own
|
|
1409
|
+
* right — a small revalidation-caching scheme — not specifically ARIA *policy*; the actual
|
|
1410
|
+
* cache storage (an `LRUCache` instance) stays owned by `AriaPolicyEngine` itself, since it's
|
|
1411
|
+
* per-engine-instance state, not something this module needs to hold.
|
|
1412
|
+
*/
|
|
1413
|
+
/**
|
|
1414
|
+
* Cache key covering only the aria-relevant subset of props (tag + role + aria-* attrs) —
|
|
1415
|
+
* exactly what the built-in pipeline reads. Non-aria props (className, onClick, etc.) do
|
|
1416
|
+
* not affect built-in ARIA decisions and are excluded so cache hits survive re-renders
|
|
1417
|
+
* that only change non-aria props. This key alone is unsound for a policy engine's own
|
|
1418
|
+
* extra rules, which may read arbitrary props outside this set — extend it with
|
|
1419
|
+
* {@link extraRulesKeySuffix}, or bypass the cache, to account for that.
|
|
1444
1420
|
*
|
|
1445
|
-
*
|
|
1446
|
-
*
|
|
1421
|
+
* Returns `null` (meaning "don't cache") when `tag` isn't a plain intrinsic string tag —
|
|
1422
|
+
* `isString(tag)` inline rather than importing `AriaPolicyEngine`'s own `isIntrinsicTag`: the
|
|
1423
|
+
* check is a one-liner, and importing it back from the engine module would be a needless
|
|
1424
|
+
* dependency in the other direction for a plan-cache module the engine itself depends on.
|
|
1447
1425
|
*/
|
|
1448
|
-
function
|
|
1449
|
-
if (!
|
|
1450
|
-
const
|
|
1451
|
-
if (
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1426
|
+
function createPlanKey(tag, props) {
|
|
1427
|
+
if (!isString(tag)) return null;
|
|
1428
|
+
const parts = [tag];
|
|
1429
|
+
if (isString(props.role)) parts.push(`role:${props.role}`);
|
|
1430
|
+
if (tag === "input" && isString(props.type)) parts.push(`type:${props.type}`);
|
|
1431
|
+
if (tag === "img") parts.push(`alt:${props.alt === "" ? "empty" : "present"}`);
|
|
1432
|
+
const ariaEntries = [];
|
|
1433
|
+
iterate.forEachEntry(props, (k, v) => {
|
|
1434
|
+
if (!k.startsWith("aria-")) return;
|
|
1435
|
+
if (!isString(v) && !isNumber(v) && typeof v !== "boolean") return;
|
|
1436
|
+
ariaEntries.push(`${k}:${String(v)}`);
|
|
1437
|
+
});
|
|
1438
|
+
if (ariaEntries.length > 0) parts.push(...ariaEntries.sort());
|
|
1439
|
+
return parts.join("|");
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Extends the base cache key with the props each extra rule declares it reads (`readsProps`).
|
|
1443
|
+
* Returns `null` — meaning "don't cache" — if any extra rule omits `readsProps` (it may read
|
|
1444
|
+
* arbitrary props the key can't account for) or if a declared prop's value isn't a primitive
|
|
1445
|
+
* (object/array identity isn't stably representable in a string key).
|
|
1446
|
+
*/
|
|
1447
|
+
function extraRulesKeySuffix(extraRules, props) {
|
|
1448
|
+
const parts = [];
|
|
1449
|
+
for (const rule of extraRules) {
|
|
1450
|
+
const readsProps = rule.readsProps;
|
|
1451
|
+
if (!isNonNull(readsProps)) return null;
|
|
1452
|
+
for (const propKey of readsProps) {
|
|
1453
|
+
const v = props[propKey];
|
|
1454
|
+
if (isDefined(v) && !isString(v) && !isNumber(v) && typeof v !== "boolean") return null;
|
|
1455
|
+
parts.push(`x:${propKey}:${String(v)}`);
|
|
1456
|
+
}
|
|
1462
1457
|
}
|
|
1463
|
-
return
|
|
1458
|
+
return parts.sort().join("|");
|
|
1459
|
+
}
|
|
1460
|
+
/** Diffs a validation pass's input/output props into a replayable plan: which keys were removed
|
|
1461
|
+
* entirely, and which keys were added or changed value (and to what). */
|
|
1462
|
+
function computePlan(inputProps, resultProps) {
|
|
1463
|
+
const removals = /* @__PURE__ */ new Set();
|
|
1464
|
+
const updates = {};
|
|
1465
|
+
iterate.forEachKey(inputProps, (key) => {
|
|
1466
|
+
if (!(key in resultProps)) removals.add(key);
|
|
1467
|
+
});
|
|
1468
|
+
iterate.forEachEntry(resultProps, (key, resultVal) => {
|
|
1469
|
+
if (inputProps[key] !== resultVal) updates[key] = resultVal;
|
|
1470
|
+
});
|
|
1471
|
+
return {
|
|
1472
|
+
removals,
|
|
1473
|
+
updates
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
/** Replays a cached plan's removals/updates against a fresh `props` object, without re-running
|
|
1477
|
+
* the rule pipeline that originally produced the plan. */
|
|
1478
|
+
function applyPlan(props, removals, updates) {
|
|
1479
|
+
const hasRemovals = removals.size > 0;
|
|
1480
|
+
const hasUpdates = Object.keys(updates).length > 0;
|
|
1481
|
+
if (!hasRemovals && !hasUpdates) return props;
|
|
1482
|
+
const next = {};
|
|
1483
|
+
iterate.forEachEntry(props, (k, v) => {
|
|
1484
|
+
if (!removals.has(k)) next[k] = v;
|
|
1485
|
+
});
|
|
1486
|
+
Object.assign(next, updates);
|
|
1487
|
+
return next;
|
|
1464
1488
|
}
|
|
1465
1489
|
//#endregion
|
|
1466
|
-
//#region ../../lib/contract/src/aria/spec/
|
|
1467
|
-
const
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1490
|
+
//#region ../../lib/contract/src/aria/spec/attributes/aria-relevant-tokens.ts
|
|
1491
|
+
const VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set([
|
|
1492
|
+
"additions",
|
|
1493
|
+
"removals",
|
|
1494
|
+
"text",
|
|
1495
|
+
"all"
|
|
1472
1496
|
]);
|
|
1473
|
-
const ATOMIC_REQUIREMENTS = requiredAttributeByRole(LIVE_REGION_ROLES.keys(), "aria-atomic");
|
|
1474
1497
|
//#endregion
|
|
1475
1498
|
//#region ../../lib/contract/src/aria/spec/attributes/aria-value-types.ts
|
|
1476
1499
|
const ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
|
|
@@ -1595,24 +1618,6 @@ const ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
|
|
|
1595
1618
|
}]
|
|
1596
1619
|
]);
|
|
1597
1620
|
//#endregion
|
|
1598
|
-
//#region ../../lib/contract/src/aria/spec/attributes/aria-relevant-tokens.ts
|
|
1599
|
-
const VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set([
|
|
1600
|
-
"additions",
|
|
1601
|
-
"removals",
|
|
1602
|
-
"text",
|
|
1603
|
-
"all"
|
|
1604
|
-
]);
|
|
1605
|
-
//#endregion
|
|
1606
|
-
//#region ../../lib/contract/src/aria/spec/elements/heading-implicit-levels.ts
|
|
1607
|
-
const HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
|
|
1608
|
-
["h1", 1],
|
|
1609
|
-
["h2", 2],
|
|
1610
|
-
["h3", 3],
|
|
1611
|
-
["h4", 4],
|
|
1612
|
-
["h5", 5],
|
|
1613
|
-
["h6", 6]
|
|
1614
|
-
]);
|
|
1615
|
-
//#endregion
|
|
1616
1621
|
//#region ../../lib/contract/src/aria/spec/elements/focusable.ts
|
|
1617
1622
|
function parseTabIndex(raw) {
|
|
1618
1623
|
if (typeof raw === "number") return Number.isInteger(raw) ? raw : void 0;
|
|
@@ -1663,24 +1668,156 @@ function isPotentiallyFocusable(tag, props) {
|
|
|
1663
1668
|
}
|
|
1664
1669
|
}
|
|
1665
1670
|
//#endregion
|
|
1666
|
-
//#region ../../lib/contract/src/aria/
|
|
1671
|
+
//#region ../../lib/contract/src/aria/spec/elements/heading-implicit-levels.ts
|
|
1672
|
+
const HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
|
|
1673
|
+
["h1", 1],
|
|
1674
|
+
["h2", 2],
|
|
1675
|
+
["h3", 3],
|
|
1676
|
+
["h4", 4],
|
|
1677
|
+
["h5", 5],
|
|
1678
|
+
["h6", 6]
|
|
1679
|
+
]);
|
|
1680
|
+
//#endregion
|
|
1681
|
+
//#region ../../lib/contract/src/aria/spec/validators/required-properties-validator.ts
|
|
1667
1682
|
const NO_VIOLATIONS = [{ valid: true }];
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
return isString(tag);
|
|
1683
|
+
function requiredAttributeByRole(roles, attribute) {
|
|
1684
|
+
return Object.fromEntries([...roles].map((role) => [role, [attribute]]));
|
|
1671
1685
|
}
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1686
|
+
/**
|
|
1687
|
+
* Reports missing attributes required by an element's effective ARIA role.
|
|
1688
|
+
*
|
|
1689
|
+
* Shared by validators that differ only in their role-to-attribute mapping and diagnostic
|
|
1690
|
+
* generation.
|
|
1691
|
+
*/
|
|
1692
|
+
function checkRequiredAttributes(requirement, { props, effectiveRole }) {
|
|
1693
|
+
if (!effectiveRole) return NO_VIOLATIONS;
|
|
1694
|
+
const requiredAttributes = requirement.attributesByRole[effectiveRole];
|
|
1695
|
+
if (!requiredAttributes) return NO_VIOLATIONS;
|
|
1696
|
+
const results = [];
|
|
1697
|
+
for (const attribute of requiredAttributes) {
|
|
1698
|
+
if (attribute in props) continue;
|
|
1699
|
+
results.push({
|
|
1700
|
+
valid: false,
|
|
1701
|
+
fixable: false,
|
|
1702
|
+
severity: "warning",
|
|
1703
|
+
attribute,
|
|
1704
|
+
diagnostic: requirement.diagnosticFor(attribute, effectiveRole)
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
return results;
|
|
1675
1708
|
}
|
|
1709
|
+
//#endregion
|
|
1710
|
+
//#region ../../lib/contract/src/aria/spec/roles/live-region.ts
|
|
1711
|
+
const LIVE_REGION_ROLES = /* @__PURE__ */ new Map([
|
|
1712
|
+
["alert", "assertive"],
|
|
1713
|
+
["status", "polite"],
|
|
1714
|
+
["log", "polite"],
|
|
1715
|
+
["timer", "off"]
|
|
1716
|
+
]);
|
|
1717
|
+
const ATOMIC_REQUIREMENTS = requiredAttributeByRole(LIVE_REGION_ROLES.keys(), "aria-atomic");
|
|
1718
|
+
//#endregion
|
|
1719
|
+
//#region ../../lib/contract/src/aria/spec/roles/name-prohibited.ts
|
|
1720
|
+
const NAME_PROHIBITED_ROLES = /* @__PURE__ */ new Set([
|
|
1721
|
+
"caption",
|
|
1722
|
+
"code",
|
|
1723
|
+
"deletion",
|
|
1724
|
+
"emphasis",
|
|
1725
|
+
"generic",
|
|
1726
|
+
"insertion",
|
|
1727
|
+
"none",
|
|
1728
|
+
"paragraph",
|
|
1729
|
+
"presentation",
|
|
1730
|
+
"strong",
|
|
1731
|
+
"subscript",
|
|
1732
|
+
"superscript"
|
|
1733
|
+
]);
|
|
1734
|
+
const NAME_PROHIBITED_ATTRIBUTES = ["aria-label", "aria-labelledby"];
|
|
1735
|
+
//#endregion
|
|
1736
|
+
//#region ../../lib/contract/src/aria/spec/roles/name-required.ts
|
|
1737
|
+
const NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
|
|
1738
|
+
//#endregion
|
|
1739
|
+
//#region ../../lib/contract/src/aria/spec/roles/required-properties.ts
|
|
1740
|
+
const REQUIRED_ARIA_PROPERTIES = {
|
|
1741
|
+
combobox: ["aria-expanded"],
|
|
1742
|
+
option: ["aria-selected"],
|
|
1743
|
+
slider: ["aria-valuenow"],
|
|
1744
|
+
scrollbar: ["aria-controls", "aria-valuenow"],
|
|
1745
|
+
spinbutton: ["aria-valuenow"]
|
|
1746
|
+
};
|
|
1747
|
+
//#endregion
|
|
1748
|
+
//#region ../../lib/contract/src/aria/value-validation.ts
|
|
1749
|
+
/**
|
|
1750
|
+
* ARIA attribute value type system: what counts as a valid value for a given
|
|
1751
|
+
* {@link AriaValueType}, and how to describe what was expected when it isn't. Extracted from
|
|
1752
|
+
* `AriaPolicyEngine` — these three functions are cohesive on their own (they're describing a
|
|
1753
|
+
* type system, not policy), and `#checkAriaAttributeValues` (kept in `aria-policy-engine.ts`
|
|
1754
|
+
* alongside every other rule implementation — see that file's own pipeline) is now just a thin
|
|
1755
|
+
* caller of {@link isValidAriaValue}/{@link describeExpected}.
|
|
1756
|
+
*/
|
|
1757
|
+
/**
|
|
1758
|
+
* Strict numeric coercion for ARIA attribute values. Unlike `parseFloat`/`parseInt`, the whole
|
|
1759
|
+
* string must be a number — `"12abc"` is rejected, not read as `12` — and an empty / whitespace
|
|
1760
|
+
* string is rejected rather than coerced to `0` the way `Number("")` would.
|
|
1761
|
+
*
|
|
1762
|
+
* Exported (not local to `isValidAriaValue`) because `AriaPolicyEngine.#checkRedundantAriaLevel`
|
|
1763
|
+
* also needs it, independent of the general ARIA value-type check this module otherwise exists
|
|
1764
|
+
* for — `aria-level`'s "redundant" check is about comparing a numeric attribute value to a
|
|
1765
|
+
* known implicit level, not about validating the value's type.
|
|
1766
|
+
*/
|
|
1676
1767
|
function strictNumeric(value) {
|
|
1677
|
-
if (
|
|
1678
|
-
if (
|
|
1768
|
+
if (isNumber(value)) return Number.isFinite(value) ? value : void 0;
|
|
1769
|
+
if (!isString(value)) return void 0;
|
|
1679
1770
|
const trimmed = value.trim();
|
|
1680
1771
|
if (trimmed === "") return void 0;
|
|
1681
1772
|
const n = Number(trimmed);
|
|
1682
1773
|
return Number.isFinite(n) ? n : void 0;
|
|
1683
1774
|
}
|
|
1775
|
+
/** Returns whether `value` satisfies `type`'s own value-type constraint (boolean/tristate/
|
|
1776
|
+
* number/integer/enum) — the WAI-ARIA attribute value type system, independent of which
|
|
1777
|
+
* attribute or role it's being checked for. */
|
|
1778
|
+
function isValidAriaValue(value, type) {
|
|
1779
|
+
switch (type.kind) {
|
|
1780
|
+
case "boolean": return value === "true" || value === "false" || value === true || value === false;
|
|
1781
|
+
case "tristate": return value === "true" || value === "false" || value === "mixed" || value === true || value === false;
|
|
1782
|
+
case "number": return strictNumeric(value) !== void 0;
|
|
1783
|
+
case "integer": {
|
|
1784
|
+
const n = strictNumeric(value);
|
|
1785
|
+
if (isUndefined(n) || !Number.isInteger(n)) return false;
|
|
1786
|
+
if (isDefined(type.min) && n < type.min) return false;
|
|
1787
|
+
if (isDefined(type.max) && n > type.max) return false;
|
|
1788
|
+
return true;
|
|
1789
|
+
}
|
|
1790
|
+
case "enum": return isString(value) && type.values.has(value);
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
/** Describes what a valid value for `type` looks like, for use in an invalid-value diagnostic
|
|
1794
|
+
* message (e.g. `aria-valuenow` got `"abc"`, expected "a finite number"). */
|
|
1795
|
+
function describeExpected(type) {
|
|
1796
|
+
switch (type.kind) {
|
|
1797
|
+
case "boolean": return "\"true\" or \"false\"";
|
|
1798
|
+
case "tristate": return "\"true\", \"false\", or \"mixed\"";
|
|
1799
|
+
case "number": return "a finite number";
|
|
1800
|
+
case "integer": {
|
|
1801
|
+
const parts = ["an integer"];
|
|
1802
|
+
if (isDefined(type.min) && isDefined(type.max)) parts.push(`between ${type.min} and ${type.max}`);
|
|
1803
|
+
else if (isDefined(type.min)) parts.push(`≥ ${type.min}`);
|
|
1804
|
+
else if (isDefined(type.max)) parts.push(`≤ ${type.max}`);
|
|
1805
|
+
return parts.join(" ");
|
|
1806
|
+
}
|
|
1807
|
+
case "enum": return [...type.values].map((v) => `"${v}"`).join(", ");
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
//#endregion
|
|
1811
|
+
//#region ../../lib/contract/src/aria/aria-policy-engine.ts
|
|
1812
|
+
const VALID_RESULT = [{ valid: true }];
|
|
1813
|
+
const NO_VARIANT_KEYS = /* @__PURE__ */ new Set();
|
|
1814
|
+
function isIntrinsicTag(tag) {
|
|
1815
|
+
return isString(tag);
|
|
1816
|
+
}
|
|
1817
|
+
function omitProp(obj, key) {
|
|
1818
|
+
const { [key]: _, ...rest } = obj;
|
|
1819
|
+
return rest;
|
|
1820
|
+
}
|
|
1684
1821
|
var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
1685
1822
|
#extraRules;
|
|
1686
1823
|
#variantKeys;
|
|
@@ -1813,73 +1950,20 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
1813
1950
|
else this.warn(d);
|
|
1814
1951
|
});
|
|
1815
1952
|
}
|
|
1816
|
-
static #createPlanKey(tag, props) {
|
|
1817
|
-
if (!isIntrinsicTag(tag)) return null;
|
|
1818
|
-
const parts = [tag];
|
|
1819
|
-
if (typeof props.role === "string") parts.push(`role:${props.role}`);
|
|
1820
|
-
if (tag === "input" && typeof props.type === "string") parts.push(`type:${props.type}`);
|
|
1821
|
-
if (tag === "img") parts.push(`alt:${props.alt === "" ? "empty" : "present"}`);
|
|
1822
|
-
const ariaEntries = [];
|
|
1823
|
-
iterate.forEachEntry(props, (k, v) => {
|
|
1824
|
-
if (!k.startsWith("aria-")) return;
|
|
1825
|
-
if (!isString(v) && !isNumber(v) && typeof v !== "boolean") return;
|
|
1826
|
-
ariaEntries.push(`${k}:${String(v)}`);
|
|
1827
|
-
});
|
|
1828
|
-
if (ariaEntries.length > 0) parts.push(...ariaEntries.sort());
|
|
1829
|
-
return parts.join("|");
|
|
1830
|
-
}
|
|
1831
|
-
static #extraRulesKeySuffix(extraRules, props) {
|
|
1832
|
-
const parts = [];
|
|
1833
|
-
for (const rule of extraRules) {
|
|
1834
|
-
const readsProps = rule.readsProps;
|
|
1835
|
-
if (!isNonNull(readsProps)) return null;
|
|
1836
|
-
for (const propKey of readsProps) {
|
|
1837
|
-
const v = props[propKey];
|
|
1838
|
-
if (v !== void 0 && !isString(v) && !isNumber(v) && typeof v !== "boolean") return null;
|
|
1839
|
-
parts.push(`x:${propKey}:${String(v)}`);
|
|
1840
|
-
}
|
|
1841
|
-
}
|
|
1842
|
-
return parts.sort().join("|");
|
|
1843
|
-
}
|
|
1844
|
-
static #computePlan(inputProps, resultProps) {
|
|
1845
|
-
const removals = /* @__PURE__ */ new Set();
|
|
1846
|
-
const updates = {};
|
|
1847
|
-
iterate.forEachKey(inputProps, (key) => {
|
|
1848
|
-
if (!(key in resultProps)) removals.add(key);
|
|
1849
|
-
});
|
|
1850
|
-
iterate.forEachEntry(resultProps, (key, resultVal) => {
|
|
1851
|
-
if (inputProps[key] !== resultVal) updates[key] = resultVal;
|
|
1852
|
-
});
|
|
1853
|
-
return {
|
|
1854
|
-
removals,
|
|
1855
|
-
updates
|
|
1856
|
-
};
|
|
1857
|
-
}
|
|
1858
|
-
static #applyPlan(props, removals, updates) {
|
|
1859
|
-
const hasRemovals = removals.size > 0;
|
|
1860
|
-
const hasUpdates = Object.keys(updates).length > 0;
|
|
1861
|
-
if (!hasRemovals && !hasUpdates) return props;
|
|
1862
|
-
const next = {};
|
|
1863
|
-
iterate.forEachEntry(props, (k, v) => {
|
|
1864
|
-
if (!removals.has(k)) next[k] = v;
|
|
1865
|
-
});
|
|
1866
|
-
Object.assign(next, updates);
|
|
1867
|
-
return next;
|
|
1868
|
-
}
|
|
1869
1953
|
validate(tag, props, extraProps) {
|
|
1870
1954
|
const ruleProps = extraProps ?? props;
|
|
1871
|
-
const baseKey =
|
|
1955
|
+
const baseKey = createPlanKey(tag, props);
|
|
1872
1956
|
let key = baseKey;
|
|
1873
1957
|
if (this.#extraRules.length > 0) {
|
|
1874
|
-
const suffix =
|
|
1958
|
+
const suffix = extraRulesKeySuffix(this.#extraRules, ruleProps);
|
|
1875
1959
|
key = isNonNull(baseKey) && isNonNull(suffix) ? `${baseKey}|${suffix}` : null;
|
|
1876
1960
|
}
|
|
1877
1961
|
if (!isNull(key)) {
|
|
1878
1962
|
const cached = this.#planCache.get(key);
|
|
1879
|
-
if (cached
|
|
1963
|
+
if (isDefined(cached)) {
|
|
1880
1964
|
if (cached.violations.length > 0) this.report(cached.violations);
|
|
1881
1965
|
return {
|
|
1882
|
-
props:
|
|
1966
|
+
props: applyPlan(props, cached.removals, cached.updates),
|
|
1883
1967
|
violations: cached.violations
|
|
1884
1968
|
};
|
|
1885
1969
|
}
|
|
@@ -1887,7 +1971,7 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
1887
1971
|
const result = this.#extraRules.length ? AriaPolicyEngine.#evaluateWithRules(tag, props, this.#extraRules, extraProps, this.#variantKeys) : AriaPolicyEngine.evaluate(tag, props);
|
|
1888
1972
|
if (result.violations.length > 0) this.report(result.violations);
|
|
1889
1973
|
if (!isNull(key)) {
|
|
1890
|
-
const { removals, updates } =
|
|
1974
|
+
const { removals, updates } = computePlan(props, result.props);
|
|
1891
1975
|
const plan = {
|
|
1892
1976
|
removals,
|
|
1893
1977
|
updates,
|
|
@@ -1978,7 +2062,7 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
1978
2062
|
];
|
|
1979
2063
|
static #checkInvalidRoleOverride({ tag, props, implicitRole }) {
|
|
1980
2064
|
const role = props.role;
|
|
1981
|
-
if (!implicitRole ||
|
|
2065
|
+
if (!implicitRole || isUndefined(role) || role === implicitRole) return VALID_RESULT;
|
|
1982
2066
|
if (isStrongImplicitRole(tag) && role === "region") {
|
|
1983
2067
|
const diagnostic = HtmlDiagnostics.implicitRoleOverride(tag, implicitRole, role);
|
|
1984
2068
|
return [{
|
|
@@ -1989,11 +2073,11 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
1989
2073
|
diagnostic
|
|
1990
2074
|
}];
|
|
1991
2075
|
}
|
|
1992
|
-
return
|
|
2076
|
+
return VALID_RESULT;
|
|
1993
2077
|
}
|
|
1994
2078
|
static #checkRedundantRole({ tag, props, implicitRole }) {
|
|
1995
2079
|
const role = props.role;
|
|
1996
|
-
if (!implicitRole ||
|
|
2080
|
+
if (!implicitRole || isUndefined(role) || role !== implicitRole) return VALID_RESULT;
|
|
1997
2081
|
const diagnostic = HtmlDiagnostics.implicitRoleRedundant(tag, implicitRole);
|
|
1998
2082
|
return [{
|
|
1999
2083
|
valid: false,
|
|
@@ -2004,8 +2088,8 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2004
2088
|
}];
|
|
2005
2089
|
}
|
|
2006
2090
|
static #checkStandaloneRegion({ tag, props, implicitRole }) {
|
|
2007
|
-
if (props.role !== "region") return
|
|
2008
|
-
if (!hasStandaloneRole(tag)) return
|
|
2091
|
+
if (props.role !== "region") return VALID_RESULT;
|
|
2092
|
+
if (!hasStandaloneRole(tag)) return VALID_RESULT;
|
|
2009
2093
|
const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
|
|
2010
2094
|
return [{
|
|
2011
2095
|
valid: false,
|
|
@@ -2016,7 +2100,7 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2016
2100
|
}];
|
|
2017
2101
|
}
|
|
2018
2102
|
static #checkInvalidAriaAttributes({ tag, props, effectiveRole }) {
|
|
2019
|
-
if (effectiveRole === "none" || effectiveRole === "presentation") return
|
|
2103
|
+
if (effectiveRole === "none" || effectiveRole === "presentation") return VALID_RESULT;
|
|
2020
2104
|
const results = [];
|
|
2021
2105
|
iterate.forEachEntry(props, (key) => {
|
|
2022
2106
|
if (!key.startsWith("aria-")) return;
|
|
@@ -2033,63 +2117,33 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2033
2117
|
});
|
|
2034
2118
|
return results;
|
|
2035
2119
|
}
|
|
2036
|
-
static #isValidAriaValue(value, type) {
|
|
2037
|
-
switch (type.kind) {
|
|
2038
|
-
case "boolean": return value === "true" || value === "false" || value === true || value === false;
|
|
2039
|
-
case "tristate": return value === "true" || value === "false" || value === "mixed" || value === true || value === false;
|
|
2040
|
-
case "number": return strictNumeric(value) !== void 0;
|
|
2041
|
-
case "integer": {
|
|
2042
|
-
const n = strictNumeric(value);
|
|
2043
|
-
if (n === void 0 || !Number.isInteger(n)) return false;
|
|
2044
|
-
if (type.min !== void 0 && n < type.min) return false;
|
|
2045
|
-
if (type.max !== void 0 && n > type.max) return false;
|
|
2046
|
-
return true;
|
|
2047
|
-
}
|
|
2048
|
-
case "enum": return typeof value === "string" && type.values.has(value);
|
|
2049
|
-
}
|
|
2050
|
-
}
|
|
2051
|
-
static #describeExpected(type) {
|
|
2052
|
-
switch (type.kind) {
|
|
2053
|
-
case "boolean": return "\"true\" or \"false\"";
|
|
2054
|
-
case "tristate": return "\"true\", \"false\", or \"mixed\"";
|
|
2055
|
-
case "number": return "a finite number";
|
|
2056
|
-
case "integer": {
|
|
2057
|
-
const parts = ["an integer"];
|
|
2058
|
-
if (type.min !== void 0 && type.max !== void 0) parts.push(`between ${type.min} and ${type.max}`);
|
|
2059
|
-
else if (type.min !== void 0) parts.push(`≥ ${type.min}`);
|
|
2060
|
-
else if (type.max !== void 0) parts.push(`≤ ${type.max}`);
|
|
2061
|
-
return parts.join(" ");
|
|
2062
|
-
}
|
|
2063
|
-
case "enum": return [...type.values].map((v) => `"${v}"`).join(", ");
|
|
2064
|
-
}
|
|
2065
|
-
}
|
|
2066
2120
|
static #checkAriaAttributeValues({ props, effectiveRole }) {
|
|
2067
|
-
if (effectiveRole === "none" || effectiveRole === "presentation") return
|
|
2121
|
+
if (effectiveRole === "none" || effectiveRole === "presentation") return VALID_RESULT;
|
|
2068
2122
|
const results = [];
|
|
2069
2123
|
iterate.forEachEntry(props, (key, value) => {
|
|
2070
2124
|
if (!key.startsWith("aria-")) return;
|
|
2071
2125
|
const type = ARIA_VALUE_TYPES.get(key);
|
|
2072
2126
|
if (!isNonNull(type)) return;
|
|
2073
|
-
if (
|
|
2127
|
+
if (isValidAriaValue(value, type)) return;
|
|
2074
2128
|
results.push({
|
|
2075
2129
|
valid: false,
|
|
2076
2130
|
fixable: true,
|
|
2077
2131
|
severity: "warning",
|
|
2078
2132
|
attribute: key,
|
|
2079
|
-
diagnostic: AriaDiagnostics.invalidAttributeValue(key, value,
|
|
2133
|
+
diagnostic: AriaDiagnostics.invalidAttributeValue(key, value, describeExpected(type)),
|
|
2080
2134
|
fix: AriaPolicyEngine.#makeRemoveAttributeFix(key)
|
|
2081
2135
|
});
|
|
2082
2136
|
});
|
|
2083
2137
|
return results;
|
|
2084
2138
|
}
|
|
2085
2139
|
static #checkRedundantAriaLevel({ tag, props, effectiveRole }) {
|
|
2086
|
-
if (effectiveRole === "none" || effectiveRole === "presentation") return
|
|
2140
|
+
if (effectiveRole === "none" || effectiveRole === "presentation") return VALID_RESULT;
|
|
2087
2141
|
const implicitLevel = HEADING_IMPLICIT_LEVELS.get(tag);
|
|
2088
|
-
if (!isNonNull(implicitLevel)) return
|
|
2142
|
+
if (!isNonNull(implicitLevel)) return VALID_RESULT;
|
|
2089
2143
|
const raw = props["aria-level"];
|
|
2090
|
-
if (!isNonNull(raw)) return
|
|
2144
|
+
if (!isNonNull(raw)) return VALID_RESULT;
|
|
2091
2145
|
const n = strictNumeric(raw);
|
|
2092
|
-
if (n === void 0 || n !== implicitLevel) return
|
|
2146
|
+
if (n === void 0 || n !== implicitLevel) return VALID_RESULT;
|
|
2093
2147
|
return [{
|
|
2094
2148
|
valid: false,
|
|
2095
2149
|
fixable: true,
|
|
@@ -2100,7 +2154,7 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2100
2154
|
}];
|
|
2101
2155
|
}
|
|
2102
2156
|
static #checkNameProhibitedRoles({ props, effectiveRole }) {
|
|
2103
|
-
if (!effectiveRole || effectiveRole === "none" || effectiveRole === "presentation" || !NAME_PROHIBITED_ROLES.has(effectiveRole)) return
|
|
2157
|
+
if (!effectiveRole || effectiveRole === "none" || effectiveRole === "presentation" || !NAME_PROHIBITED_ROLES.has(effectiveRole)) return VALID_RESULT;
|
|
2104
2158
|
const results = [];
|
|
2105
2159
|
for (const key of NAME_PROHIBITED_ATTRIBUTES) {
|
|
2106
2160
|
if (!(key in props)) continue;
|
|
@@ -2116,9 +2170,9 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2116
2170
|
return results;
|
|
2117
2171
|
}
|
|
2118
2172
|
static #checkNameRequiredRoles({ tag, props, effectiveRole }) {
|
|
2119
|
-
if (!effectiveRole || !NAME_REQUIRED_ROLES.has(effectiveRole)) return
|
|
2120
|
-
if ("aria-label" in props || "aria-labelledby" in props) return
|
|
2121
|
-
if (tag === "img" &&
|
|
2173
|
+
if (!effectiveRole || !NAME_REQUIRED_ROLES.has(effectiveRole)) return VALID_RESULT;
|
|
2174
|
+
if ("aria-label" in props || "aria-labelledby" in props) return VALID_RESULT;
|
|
2175
|
+
if (tag === "img" && isString(props.alt) && props.alt.length > 0) return VALID_RESULT;
|
|
2122
2176
|
return [{
|
|
2123
2177
|
valid: false,
|
|
2124
2178
|
fixable: false,
|
|
@@ -2131,12 +2185,12 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2131
2185
|
diagnosticFor: (attribute, role) => AriaDiagnostics.requiredProperty(attribute, role)
|
|
2132
2186
|
};
|
|
2133
2187
|
static #checkRequiredAriaProperties(context) {
|
|
2134
|
-
if (!isNonNull(context.props.role)) return
|
|
2188
|
+
if (!isNonNull(context.props.role)) return VALID_RESULT;
|
|
2135
2189
|
return checkRequiredAttributes(AriaPolicyEngine.#requiredAriaPropertiesRule, context);
|
|
2136
2190
|
}
|
|
2137
2191
|
static #checkAriaHiddenOnFocusable({ tag, props }) {
|
|
2138
|
-
if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return
|
|
2139
|
-
if (!isPotentiallyFocusable(tag, props)) return
|
|
2192
|
+
if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return VALID_RESULT;
|
|
2193
|
+
if (!isPotentiallyFocusable(tag, props)) return VALID_RESULT;
|
|
2140
2194
|
return [{
|
|
2141
2195
|
valid: false,
|
|
2142
2196
|
fixable: false,
|
|
@@ -2146,7 +2200,7 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2146
2200
|
}];
|
|
2147
2201
|
}
|
|
2148
2202
|
static #checkPresentationalAriaAttributes({ tag, props, effectiveRole }) {
|
|
2149
|
-
if (effectiveRole !== "none" && effectiveRole !== "presentation") return
|
|
2203
|
+
if (effectiveRole !== "none" && effectiveRole !== "presentation") return VALID_RESULT;
|
|
2150
2204
|
const results = [];
|
|
2151
2205
|
iterate.forEachEntry(props, (key) => {
|
|
2152
2206
|
if (!key.startsWith("aria-")) return;
|
|
@@ -2163,10 +2217,10 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2163
2217
|
return results;
|
|
2164
2218
|
}
|
|
2165
2219
|
static #checkMissingLiveRegion({ effectiveRole, props }) {
|
|
2166
|
-
if (!effectiveRole) return
|
|
2220
|
+
if (!effectiveRole) return VALID_RESULT;
|
|
2167
2221
|
const impliedLive = LIVE_REGION_ROLES.get(effectiveRole);
|
|
2168
|
-
if (!impliedLive) return
|
|
2169
|
-
if ("aria-live" in props) return
|
|
2222
|
+
if (!impliedLive) return VALID_RESULT;
|
|
2223
|
+
if ("aria-live" in props) return VALID_RESULT;
|
|
2170
2224
|
return [{
|
|
2171
2225
|
valid: false,
|
|
2172
2226
|
fixable: true,
|
|
@@ -2206,8 +2260,8 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2206
2260
|
};
|
|
2207
2261
|
static #checkInvalidAriaRelevant({ props }) {
|
|
2208
2262
|
const relevant = props["aria-relevant"];
|
|
2209
|
-
if (relevant
|
|
2210
|
-
if (
|
|
2263
|
+
if (isUndefined(relevant)) return VALID_RESULT;
|
|
2264
|
+
if (!isString(relevant)) return VALID_RESULT;
|
|
2211
2265
|
const tokens = relevant.trim().split(/\s+/);
|
|
2212
2266
|
const invalid = tokens.filter((t) => !VALID_RELEVANT_TOKENS.has(t));
|
|
2213
2267
|
if (invalid.length > 0) return [{
|
|
@@ -2226,7 +2280,7 @@ var AriaPolicyEngine = class AriaPolicyEngine extends InvariantBase {
|
|
|
2226
2280
|
diagnostic: AriaDiagnostics.relevantSuperseded(),
|
|
2227
2281
|
fix: AriaPolicyEngine.#normalizeRelevantAllFix
|
|
2228
2282
|
}];
|
|
2229
|
-
return
|
|
2283
|
+
return VALID_RESULT;
|
|
2230
2284
|
}
|
|
2231
2285
|
};
|
|
2232
2286
|
//#endregion
|
|
@@ -4648,11 +4702,11 @@ function buildRuntime(options) {
|
|
|
4648
4702
|
* there's no `HTMLElement` to check `instanceof` against there, so that part
|
|
4649
4703
|
* of the check is skipped when `HTMLElement` doesn't exist.
|
|
4650
4704
|
*
|
|
4651
|
-
* `WebContractComponent`'s third
|
|
4652
|
-
*
|
|
4653
|
-
* left at
|
|
4654
|
-
*
|
|
4655
|
-
*
|
|
4705
|
+
* `WebContractComponent`'s third and fourth parameters, `G` and `C` (the phantom `__generics`/
|
|
4706
|
+
* `__contract` markers `GenericsOf`/`ContractProps` read back — `./types/contract-props`), are
|
|
4707
|
+
* left at their defaults here: neither carries a runtime signal for this guard to check, and
|
|
4708
|
+
* `createContractComponent` supplies the real `G`/`C` itself when it casts this function's
|
|
4709
|
+
* narrowed result to its public return type.
|
|
4656
4710
|
*/
|
|
4657
4711
|
function isWebContractComponent(value) {
|
|
4658
4712
|
if (!isFunction(value)) return false;
|
|
@@ -4703,9 +4757,10 @@ function renderContractToString(component, props = {}, innerHTML = "") {
|
|
|
4703
4757
|
/** Web-specific addition on top of `FactoryOptions`. */
|
|
4704
4758
|
const WEB_FIELD_VALIDATORS = { filterProps: (v) => v === void 0 || isFunction(v) };
|
|
4705
4759
|
/**
|
|
4706
|
-
* Type guard narrowing
|
|
4707
|
-
* `
|
|
4708
|
-
* `
|
|
4760
|
+
* Type guard narrowing a concrete contract `C` down to `C & WebFactoryOptions` — the type
|
|
4761
|
+
* `buildRuntime` is declared against. Single-generic, matching `createContractComponent`'s own
|
|
4762
|
+
* `C extends WebFactoryOptions` (Phase 2 of the `defineContract` refactor — was 4 independent
|
|
4763
|
+
* generics). See `isFactoryOptionsLike` for what this does and doesn't validate.
|
|
4709
4764
|
*/
|
|
4710
4765
|
function isWebFactoryOptions(options) {
|
|
4711
4766
|
return isFactoryOptionsLike(options, WEB_FIELD_VALIDATORS);
|
|
@@ -4786,6 +4841,11 @@ function isWebFactoryOptions(options) {
|
|
|
4786
4841
|
* resolve to `options.tag` here, on both the client and SSR paths. Need different semantics for one
|
|
4787
4842
|
* instance? Register a second component with a different `tag`, or set `role` directly — both
|
|
4788
4843
|
* already work today, unaffected by this.
|
|
4844
|
+
*
|
|
4845
|
+
* `TDefault`/`TProps`/`TVariants`/`TPreset`/`TPlugin` are each `ContractXOf<C>`-derived *defaults*
|
|
4846
|
+
* on this function's own type parameter list, mirroring every other adapter's identical fix — see
|
|
4847
|
+
* `@praxis-kit/react`'s own doc comment for why (computed as function type-parameter defaults, not
|
|
4848
|
+
* inline body computations, which doesn't resolve for a still-abstract `C`).
|
|
4789
4849
|
*/
|
|
4790
4850
|
function createContractComponent(options) {
|
|
4791
4851
|
invariant(isWebFactoryOptions(options), "options is not a valid WebFactoryOptions object");
|
|
@@ -4865,4 +4925,4 @@ function createContractComponent(options) {
|
|
|
4865
4925
|
return assembleCompoundComponent(contractClass, options.subComponents);
|
|
4866
4926
|
}
|
|
4867
4927
|
//#endregion
|
|
4868
|
-
export { createContractComponent,
|
|
4928
|
+
export { createContractComponent, renderContractToString };
|