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