praxis-kit 5.0.0 → 6.1.0

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/lit/index.js CHANGED
@@ -313,6 +313,19 @@ function makeResolveTag(defaultTag) {
313
313
  };
314
314
  }
315
315
 
316
+ // ../../lib/primitive/src/rule/rule-brand.ts
317
+ var RULE_BRAND = /* @__PURE__ */ Symbol("praxis-kit/dynamic-rule");
318
+
319
+ // ../../lib/primitive/src/rule/is-dynamic-rule.ts
320
+ function isDynamicRule(rule) {
321
+ return isObject(rule, true) && rule[RULE_BRAND] === true;
322
+ }
323
+
324
+ // ../../lib/primitive/src/rule/resolve-rule.ts
325
+ function resolveRule(rule, context) {
326
+ return isDynamicRule(rule) ? rule.resolve(context) : rule;
327
+ }
328
+
316
329
  // ../../lib/primitive/src/utils/assert-never.ts
317
330
  function assertNever(value) {
318
331
  throw new Error(`Unexpected value: ${String(value)}`);
@@ -494,6 +507,45 @@ var iterate = Object.freeze({
494
507
  values
495
508
  });
496
509
 
510
+ // ../../lib/primitive/src/utils/lru-cache.ts
511
+ var LRUCache = class {
512
+ #maxSize;
513
+ #store = /* @__PURE__ */ new Map();
514
+ constructor(maxSize) {
515
+ if (!Number.isInteger(maxSize) || maxSize < 1) {
516
+ throw new RangeError("LRUCache maxSize must be a positive integer.");
517
+ }
518
+ this.#maxSize = maxSize;
519
+ }
520
+ get(key) {
521
+ if (!this.#store.has(key)) return void 0;
522
+ const value = this.#store.get(key);
523
+ this.#store.delete(key);
524
+ this.#store.set(key, value);
525
+ return value;
526
+ }
527
+ set(key, value) {
528
+ this.#store.delete(key);
529
+ this.#store.set(key, value);
530
+ if (this.#store.size > this.#maxSize) {
531
+ const lru = this.#store.keys().next().value;
532
+ if (lru !== void 0) this.#store.delete(lru);
533
+ }
534
+ }
535
+ has(key) {
536
+ return this.#store.has(key);
537
+ }
538
+ delete(key) {
539
+ return this.#store.delete(key);
540
+ }
541
+ get size() {
542
+ return this.#store.size;
543
+ }
544
+ clear() {
545
+ this.#store.clear();
546
+ }
547
+ };
548
+
497
549
  // ../../lib/primitive/src/utils/merge-props.ts
498
550
  function mergeProps(defaultProps, props) {
499
551
  return {
@@ -538,28 +590,6 @@ function isTag(...args) {
538
590
  return tag !== void 0 && set2.has(tag);
539
591
  }
540
592
 
541
- // ../../lib/contract/src/strict/invariant-base.ts
542
- var InvariantBase = class {
543
- diagnostics;
544
- constructor(diagnostics) {
545
- this.diagnostics = diagnostics;
546
- }
547
- get active() {
548
- return this.diagnostics.active;
549
- }
550
- violate(input) {
551
- this.diagnostics.error(input);
552
- }
553
- // Always caps at warn — never throws. ARIA 'warning' violations route here
554
- // so they surface even in strict='throw' mode without aborting a render.
555
- warn(input) {
556
- this.diagnostics.warn(input);
557
- }
558
- invariant(condition, input) {
559
- if (!condition) this.violate(input);
560
- }
561
- };
562
-
563
593
  // ../../lib/contract/src/diagnostics/aria.ts
564
594
  import { DiagnosticCategory, DiagnosticCode } from "../_shared/diagnostics.js";
565
595
  var AriaDiagnostics = {
@@ -870,6 +900,28 @@ var HtmlDiagnostics = {
870
900
  }
871
901
  };
872
902
 
903
+ // ../../lib/contract/src/strict/invariant-base.ts
904
+ var InvariantBase = class {
905
+ diagnostics;
906
+ constructor(diagnostics) {
907
+ this.diagnostics = diagnostics;
908
+ }
909
+ get active() {
910
+ return this.diagnostics.active;
911
+ }
912
+ violate(input) {
913
+ this.diagnostics.error(input);
914
+ }
915
+ // Always caps at warn — never throws. ARIA 'warning' violations route here
916
+ // so they surface even in strict='throw' mode without aborting a render.
917
+ warn(input) {
918
+ this.diagnostics.warn(input);
919
+ }
920
+ invariant(condition, input) {
921
+ if (!condition) this.violate(input);
922
+ }
923
+ };
924
+
873
925
  // ../../lib/contract/src/aria/polymorphic-validator.ts
874
926
  var VALID = [{ valid: true }];
875
927
  function isIntrinsicTag(tag) {
@@ -881,8 +933,7 @@ function omitProp(obj, key) {
881
933
  }
882
934
  var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
883
935
  #extraRules;
884
- #planCache = /* @__PURE__ */ new Map();
885
- static #MAX_CACHE = 100;
936
+ #planCache = new LRUCache(100);
886
937
  // Memoized AriaFix objects keyed by attribute name — the ARIA attribute set is
887
938
  // finite so this Map is bounded and avoids recreating closures on every cache miss.
888
939
  static #removeAttributeFixCache = /* @__PURE__ */ new Map();
@@ -1038,8 +1089,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1038
1089
  if (!isNull(key)) {
1039
1090
  const cached = this.#planCache.get(key);
1040
1091
  if (cached !== void 0) {
1041
- this.#planCache.delete(key);
1042
- this.#planCache.set(key, cached);
1043
1092
  if (cached.violations.length > 0) this.report(cached.violations);
1044
1093
  return {
1045
1094
  props: _AriaPolicyEngine.#applyPlan(props, cached.removals, cached.updates),
@@ -1056,10 +1105,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1056
1105
  );
1057
1106
  const plan = { removals, updates, violations: result.violations };
1058
1107
  this.#planCache.set(key, plan);
1059
- if (this.#planCache.size > _AriaPolicyEngine.#MAX_CACHE) {
1060
- const lru = this.#planCache.keys().next().value;
1061
- if (lru !== void 0) this.#planCache.delete(lru);
1062
- }
1063
1108
  }
1064
1109
  return result;
1065
1110
  }
@@ -1715,10 +1760,22 @@ var RuleValidator = class _RuleValidator extends InvariantBase {
1715
1760
 
1716
1761
  // ../../lib/contract/src/children/children-evaluator.ts
1717
1762
  var isTextLike = (child) => isString(child) || isNumber(child);
1763
+ function checkPositionCardinalityInvariant(rules, context) {
1764
+ iterate.forEach(rules, (rule) => {
1765
+ const { name, position, cardinality } = rule;
1766
+ if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
1767
+ throw new RangeError(
1768
+ `ChildrenEvaluator [${context}]: rule "${name}" sets position="${position}" with an unbound or >1 max. position="first|last" implies max=1.`
1769
+ );
1770
+ }
1771
+ });
1772
+ }
1718
1773
  var ChildrenEvaluator = class extends InvariantBase {
1719
1774
  #context;
1720
- #rules;
1721
- #ruleNames;
1775
+ /** Rules with a static cardinality — normalized once, reused on every evaluate() call. */
1776
+ #staticRules;
1777
+ /** Rules whose cardinality is `dynamic(...)` — re-resolved against a ChildRuleContext per call. */
1778
+ #dynamicRuleInputs;
1722
1779
  #matcher;
1723
1780
  #ruleValidator;
1724
1781
  #exclusiveChildren;
@@ -1726,29 +1783,39 @@ var ChildrenEvaluator = class extends InvariantBase {
1726
1783
  constructor(rules, diagnostics, context = "Component", options = {}) {
1727
1784
  super(diagnostics);
1728
1785
  this.#context = context;
1729
- this.#rules = rules.map((r) => normalizeChildRule(r));
1730
- this.#ruleNames = this.#rules.map((r) => r.name);
1731
1786
  this.#exclusiveChildren = options.exclusiveChildren ?? false;
1732
1787
  this.#allowText = options.allowText ?? true;
1733
- iterate.forEach(this.#rules, (rule) => {
1734
- const { name, position, cardinality } = rule;
1735
- if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
1736
- throw new RangeError(
1737
- `ChildrenEvaluator [${context}]: rule "${name}" sets position="${position}" with an unbound or >1 max. position="first|last" implies max=1.`
1738
- );
1739
- }
1788
+ const staticRuleInputs = [];
1789
+ const dynamicRuleInputs = [];
1790
+ iterate.forEach(rules, (rule) => {
1791
+ if (isDynamicRule(rule.cardinality)) dynamicRuleInputs.push(rule);
1792
+ else staticRuleInputs.push(rule);
1740
1793
  });
1741
- this.#matcher = new RuleMatcher(this.#rules);
1794
+ this.#dynamicRuleInputs = dynamicRuleInputs;
1795
+ this.#staticRules = staticRuleInputs.map(
1796
+ (r) => normalizeChildRule(r)
1797
+ );
1742
1798
  this.#ruleValidator = new RuleValidator(context, diagnostics);
1799
+ if (this.#dynamicRuleInputs.length === 0) {
1800
+ checkPositionCardinalityInvariant(this.#staticRules, context);
1801
+ this.#matcher = new RuleMatcher(this.#staticRules);
1802
+ } else {
1803
+ this.#matcher = void 0;
1804
+ }
1743
1805
  }
1744
- evaluate(children) {
1806
+ /**
1807
+ * @param context Required when any rule has a `dynamic(...)` cardinality —
1808
+ * supplies the resolved tag/props those rules are evaluated against.
1809
+ */
1810
+ evaluate(children, context) {
1745
1811
  if (!this.active) return;
1812
+ const { rules, matcher } = this.#resolveRules(context);
1746
1813
  const {
1747
1814
  matrix,
1748
1815
  unexpectedIndices: rawUnexpectedIndices,
1749
1816
  ambiguousIndices
1750
- } = this.#matcher.match(children);
1751
- this.#ruleValidator.validate(this.#rules, matrix, children.length);
1817
+ } = matcher.match(children);
1818
+ this.#ruleValidator.validate(rules, matrix, children.length);
1752
1819
  const unexpectedIndices = new Set(
1753
1820
  [...rawUnexpectedIndices].filter(
1754
1821
  (ci) => isTextLike(children[ci]) ? this.#allowText === false : this.#exclusiveChildren
@@ -1762,11 +1829,28 @@ var ChildrenEvaluator = class extends InvariantBase {
1762
1829
  this.violate(ContractDiagnostics.unexpectedChild(typeName, ci, this.#context));
1763
1830
  } else {
1764
1831
  const matches = matrix.childToRules.forward.get(ci);
1765
- const names = [...matches].map((ri) => this.#ruleNames[ri] ?? `#${ri}`);
1832
+ const names = [...matches].map((ri) => rules[ri]?.name ?? `#${ri}`);
1766
1833
  this.violate(ContractDiagnostics.ambiguousChild(typeName, ci, names, this.#context));
1767
1834
  }
1768
1835
  });
1769
1836
  }
1837
+ #resolveRules(context) {
1838
+ if (this.#dynamicRuleInputs.length === 0) {
1839
+ return { rules: this.#staticRules, matcher: this.#matcher };
1840
+ }
1841
+ if (context === void 0) {
1842
+ throw new RangeError(
1843
+ `ChildrenEvaluator [${this.#context}]: rule(s) have a dynamic(...) cardinality \u2014 evaluate() requires a context argument to resolve them.`
1844
+ );
1845
+ }
1846
+ const resolvedDynamicRules = this.#dynamicRuleInputs.map((r) => {
1847
+ const cardinality = resolveRule(r.cardinality, context);
1848
+ return normalizeChildRule({ ...r, cardinality });
1849
+ });
1850
+ checkPositionCardinalityInvariant(resolvedDynamicRules, this.#context);
1851
+ const rules = [...this.#staticRules, ...resolvedDynamicRules];
1852
+ return { rules, matcher: new RuleMatcher(rules) };
1853
+ }
1770
1854
  };
1771
1855
 
1772
1856
  // ../../lib/contract/src/props/get-disabled-props.ts
@@ -2095,7 +2179,7 @@ function cva2(base, config) {
2095
2179
  // ../../lib/styling/src/static-class-resolver.ts
2096
2180
  var StaticClassResolver = class {
2097
2181
  #baseClass;
2098
- #cache = /* @__PURE__ */ new Map();
2182
+ #cache = new LRUCache(200);
2099
2183
  #resolveTag;
2100
2184
  constructor(baseClass, tagMap) {
2101
2185
  this.#baseClass = Array.isArray(baseClass) ? baseClass.join(" ") : baseClass;
@@ -2109,17 +2193,9 @@ var StaticClassResolver = class {
2109
2193
  resolve(tag, skipTagMap = false) {
2110
2194
  if (typeof tag !== "string" || skipTagMap) return this.#baseClass;
2111
2195
  const cached = this.#cache.get(tag);
2112
- if (cached !== void 0) {
2113
- this.#cache.delete(tag);
2114
- this.#cache.set(tag, cached);
2115
- return cached;
2116
- }
2196
+ if (cached !== void 0) return cached;
2117
2197
  const result = this.#resolveTag(tag);
2118
2198
  this.#cache.set(tag, result);
2119
- if (this.#cache.size > 200) {
2120
- const lru = this.#cache.keys().next().value;
2121
- if (lru !== void 0) this.#cache.delete(lru);
2122
- }
2123
2199
  return result;
2124
2200
  }
2125
2201
  };
@@ -2130,7 +2206,7 @@ var VariantClassResolver = class _VariantClassResolver {
2130
2206
  #recipeMap;
2131
2207
  #variantKeys;
2132
2208
  #precomputedClasses;
2133
- #cache = /* @__PURE__ */ new Map();
2209
+ #cache = new LRUCache(1e3);
2134
2210
  constructor(cvaFn, recipeMap, variantKeys, precomputedClasses) {
2135
2211
  this.#cvaFn = cvaFn ?? null;
2136
2212
  this.#recipeMap = Object.freeze(recipeMap ?? {});
@@ -2145,17 +2221,9 @@ var VariantClassResolver = class _VariantClassResolver {
2145
2221
  if (precomputed !== void 0) return precomputed;
2146
2222
  }
2147
2223
  const cached = this.#cache.get(cacheKey);
2148
- if (cached !== void 0) {
2149
- this.#cache.delete(cacheKey);
2150
- this.#cache.set(cacheKey, cached);
2151
- return cached;
2152
- }
2224
+ if (cached !== void 0) return cached;
2153
2225
  const result = this.#compute(props, recipe);
2154
2226
  this.#cache.set(cacheKey, result);
2155
- if (this.#cache.size > 1e3) {
2156
- const lru = this.#cache.keys().next().value;
2157
- if (lru !== void 0) this.#cache.delete(lru);
2158
- }
2159
2227
  return result;
2160
2228
  }
2161
2229
  #compute(props, recipe) {
@@ -2558,13 +2626,10 @@ function toLooseBundle(bundle) {
2558
2626
  }
2559
2627
  return bundle;
2560
2628
  }
2561
- function resolveHostState(bundle, props) {
2562
- const { as, className, recipe, ...rest } = props;
2563
- const { options, resolveAria, resolveClasses, resolveProps, resolveTag: resolveTag2 } = bundle.runtime;
2629
+ function resolveTagAndNormalizedProps(bundle, props) {
2630
+ const { as, className: _className, recipe: _recipe, ...rest } = props;
2631
+ const { options, resolveProps, resolveTag: resolveTag2 } = bundle.runtime;
2564
2632
  const tag = resolveTag2(as);
2565
- if (options.allowedAs !== void 0) {
2566
- enforceAllowedAs(tag, options.allowedAs, options.diagnostics, options.displayName);
2567
- }
2568
2633
  const mergedProps = resolveProps(rest);
2569
2634
  const normalizedProps = typeof options.normalizeFn === "function" ? options.normalizeFn(mergedProps) : mergedProps;
2570
2635
  const htmlNormalizers = options.htmlPropNormalizersFn?.(tag);
@@ -2574,6 +2639,15 @@ function resolveHostState(bundle, props) {
2574
2639
  Object.assign(finalProps, normalize(finalProps));
2575
2640
  }
2576
2641
  }
2642
+ return { tag, normalizedProps: finalProps };
2643
+ }
2644
+ function resolveHostState(bundle, props) {
2645
+ const { className, recipe } = props;
2646
+ const { options, resolveAria, resolveClasses } = bundle.runtime;
2647
+ const { tag, normalizedProps: finalProps } = resolveTagAndNormalizedProps(bundle, props);
2648
+ if (options.allowedAs !== void 0) {
2649
+ enforceAllowedAs(tag, options.allowedAs, options.diagnostics, options.displayName);
2650
+ }
2577
2651
  const resolvedClass = resolveClasses(
2578
2652
  tag,
2579
2653
  finalProps,
@@ -2582,7 +2656,7 @@ function resolveHostState(bundle, props) {
2582
2656
  );
2583
2657
  const ariaResult = resolveAria(tag, finalProps);
2584
2658
  const attributes = applyFilter(ariaResult.props, bundle.filterProps, options.variantKeys);
2585
- return { className: resolvedClass, attributes };
2659
+ return { className: resolvedClass, attributes, tag, normalizedProps: finalProps };
2586
2660
  }
2587
2661
  function diffAndApplyAttributes(host, state, prevPipelineAttrs, incomingProps) {
2588
2662
  const hasOwn2 = Object.hasOwn;
@@ -2721,7 +2795,11 @@ function createContractComponent(options) {
2721
2795
  get _self() {
2722
2796
  return this;
2723
2797
  }
2724
- _applyPraxis() {
2798
+ // Start with all current DOM attributes so the ARIA engine sees role,
2799
+ // aria-*, and any other pass-through attributes. Shared by render() and
2800
+ // _applyPraxis(), which run at different points in the same update cycle
2801
+ // and can't share a local variable.
2802
+ _buildProps() {
2725
2803
  const self = this._self;
2726
2804
  const props = {};
2727
2805
  iterate.forEach(iterate.items(this.attributes), (attr) => {
@@ -2735,14 +2813,18 @@ function createContractComponent(options) {
2735
2813
  const val = self[key];
2736
2814
  if (val != null) props[key] = val;
2737
2815
  });
2816
+ return props;
2817
+ }
2818
+ _applyPraxis() {
2819
+ const props = this._buildProps();
2738
2820
  diffAndApplyAttributes(this, resolveHostState(looseBundle, props), this._pipelineAttrs, props);
2739
2821
  }
2740
2822
  render() {
2741
2823
  const children = Array.from(this.childNodes);
2824
+ const { tag, normalizedProps } = resolveTagAndNormalizedProps(looseBundle, this._buildProps());
2742
2825
  if (bundle.childrenEvaluator) {
2743
- bundle.childrenEvaluator.evaluate(children);
2826
+ bundle.childrenEvaluator.evaluate(children, { tag, props: normalizedProps });
2744
2827
  }
2745
- const tag = bundle.runtime.resolveTag(this._self.as);
2746
2828
  bundle.runtime.options.htmlChildrenEvaluatorFn?.(tag)?.evaluate(children);
2747
2829
  return html`<slot></slot>`;
2748
2830
  }
@@ -30,6 +30,26 @@ type MinMax = {
30
30
  };
31
31
  type CardinalityInput = Partial<MinMax>;
32
32
 
33
+ /**
34
+ * Resolved per-instance state available to a dynamic (`dynamic(...)`) child
35
+ * rule field — the same tag/props every adapter already computes before
36
+ * evaluating children, exposed so a rule can vary by them (e.g. cardinality
37
+ * that depends on the resolved `as` tag).
38
+ */
39
+ type ChildRuleContext = {
40
+ readonly tag: unknown;
41
+ readonly props: Readonly<AnyRecord>;
42
+ };
43
+
44
+ declare const RULE_BRAND: unique symbol;
45
+
46
+ type DynamicRule<T, C = unknown> = {
47
+ readonly [RULE_BRAND]: true;
48
+ resolve(context: C): T;
49
+ };
50
+
51
+ type Rule<T, C = unknown> = T | DynamicRule<T, C>;
52
+
33
53
  type ChildRuleMatch<T, U extends T = T> = (child: T) => child is U;
34
54
 
35
55
  type ChildRulePosition = 'first' | 'last' | 'any';
@@ -37,7 +57,13 @@ type ChildRulePosition = 'first' | 'last' | 'any';
37
57
  type ChildRuleInput<T = unknown, U extends T = T> = {
38
58
  name: string;
39
59
  match: ChildRuleMatch<T, U>;
40
- cardinality?: CardinalityInput;
60
+ /**
61
+ * Either a static cardinality, or `dynamic((ctx) => ...)` to derive it from
62
+ * the resolved tag/props (e.g. a different max depending on `as`). `match`
63
+ * stays static-only — it's already a function, so a dynamic wrapper would
64
+ * be indistinguishable from the predicate itself without one.
65
+ */
66
+ cardinality?: Rule<CardinalityInput, ChildRuleContext>;
41
67
  position?: ChildRulePosition;
42
68
  /**
43
69
  * Optional component-type reference for O(1) dispatch index.