praxis-kit 4.1.3 → 6.0.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.
@@ -445,6 +445,19 @@ function makeResolveTag(defaultTag) {
445
445
  };
446
446
  }
447
447
 
448
+ // ../../lib/primitive/src/rule/rule-brand.ts
449
+ var RULE_BRAND = /* @__PURE__ */ Symbol("praxis-kit/dynamic-rule");
450
+
451
+ // ../../lib/primitive/src/rule/is-dynamic-rule.ts
452
+ function isDynamicRule(rule) {
453
+ return isObject(rule, true) && rule[RULE_BRAND] === true;
454
+ }
455
+
456
+ // ../../lib/primitive/src/rule/resolve-rule.ts
457
+ function resolveRule(rule, context) {
458
+ return isDynamicRule(rule) ? rule.resolve(context) : rule;
459
+ }
460
+
448
461
  // ../../lib/primitive/src/utils/assert-never.ts
449
462
  function assertNever(value) {
450
463
  throw new Error(`Unexpected value: ${String(value)}`);
@@ -1856,32 +1869,68 @@ var RuleValidator = class _RuleValidator extends InvariantBase {
1856
1869
  };
1857
1870
 
1858
1871
  // ../../lib/contract/src/children/children-evaluator.ts
1872
+ var isTextLike = (child) => isString(child) || isNumber(child);
1873
+ function checkPositionCardinalityInvariant(rules, context) {
1874
+ iterate.forEach(rules, (rule) => {
1875
+ const { name, position, cardinality } = rule;
1876
+ if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
1877
+ throw new RangeError(
1878
+ `ChildrenEvaluator [${context}]: rule "${name}" sets position="${position}" with an unbound or >1 max. position="first|last" implies max=1.`
1879
+ );
1880
+ }
1881
+ });
1882
+ }
1859
1883
  var ChildrenEvaluator = class extends InvariantBase {
1860
1884
  #context;
1861
- #rules;
1862
- #ruleNames;
1885
+ /** Rules with a static cardinality — normalized once, reused on every evaluate() call. */
1886
+ #staticRules;
1887
+ /** Rules whose cardinality is `dynamic(...)` — re-resolved against a ChildRuleContext per call. */
1888
+ #dynamicRuleInputs;
1863
1889
  #matcher;
1864
1890
  #ruleValidator;
1865
- constructor(rules, diagnostics, context = "Component") {
1891
+ #exclusiveChildren;
1892
+ #allowText;
1893
+ constructor(rules, diagnostics, context = "Component", options = {}) {
1866
1894
  super(diagnostics);
1867
1895
  this.#context = context;
1868
- this.#rules = rules.map((r) => normalizeChildRule(r));
1869
- this.#ruleNames = this.#rules.map((r) => r.name);
1870
- iterate.forEach(this.#rules, (rule) => {
1871
- const { name, position, cardinality } = rule;
1872
- if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
1873
- throw new RangeError(
1874
- `ChildrenEvaluator [${context}]: rule "${name}" sets position="${position}" with an unbound or >1 max. position="first|last" implies max=1.`
1875
- );
1876
- }
1896
+ this.#exclusiveChildren = options.exclusiveChildren ?? false;
1897
+ this.#allowText = options.allowText ?? true;
1898
+ const staticRuleInputs = [];
1899
+ const dynamicRuleInputs = [];
1900
+ iterate.forEach(rules, (rule) => {
1901
+ if (isDynamicRule(rule.cardinality)) dynamicRuleInputs.push(rule);
1902
+ else staticRuleInputs.push(rule);
1877
1903
  });
1878
- this.#matcher = new RuleMatcher(this.#rules);
1904
+ this.#dynamicRuleInputs = dynamicRuleInputs;
1905
+ this.#staticRules = staticRuleInputs.map(
1906
+ (r) => normalizeChildRule(r)
1907
+ );
1879
1908
  this.#ruleValidator = new RuleValidator(context, diagnostics);
1909
+ if (this.#dynamicRuleInputs.length === 0) {
1910
+ checkPositionCardinalityInvariant(this.#staticRules, context);
1911
+ this.#matcher = new RuleMatcher(this.#staticRules);
1912
+ } else {
1913
+ this.#matcher = void 0;
1914
+ }
1880
1915
  }
1881
- evaluate(children) {
1916
+ /**
1917
+ * @param context Required when any rule has a `dynamic(...)` cardinality —
1918
+ * supplies the resolved tag/props those rules are evaluated against.
1919
+ */
1920
+ evaluate(children, context) {
1882
1921
  if (!this.active) return;
1883
- const { matrix, unexpectedIndices, ambiguousIndices } = this.#matcher.match(children);
1884
- this.#ruleValidator.validate(this.#rules, matrix, children.length);
1922
+ const { rules, matcher } = this.#resolveRules(context);
1923
+ const {
1924
+ matrix,
1925
+ unexpectedIndices: rawUnexpectedIndices,
1926
+ ambiguousIndices
1927
+ } = matcher.match(children);
1928
+ this.#ruleValidator.validate(rules, matrix, children.length);
1929
+ const unexpectedIndices = new Set(
1930
+ [...rawUnexpectedIndices].filter(
1931
+ (ci) => isTextLike(children[ci]) ? this.#allowText === false : this.#exclusiveChildren
1932
+ )
1933
+ );
1885
1934
  if (unexpectedIndices.size === 0 && ambiguousIndices.size === 0) return;
1886
1935
  const violating = [...unexpectedIndices, ...ambiguousIndices].sort((a, b) => a - b);
1887
1936
  iterate.forEach(violating, (ci) => {
@@ -1890,11 +1939,28 @@ var ChildrenEvaluator = class extends InvariantBase {
1890
1939
  this.violate(ContractDiagnostics.unexpectedChild(typeName, ci, this.#context));
1891
1940
  } else {
1892
1941
  const matches = matrix.childToRules.forward.get(ci);
1893
- const names = [...matches].map((ri) => this.#ruleNames[ri] ?? `#${ri}`);
1942
+ const names = [...matches].map((ri) => rules[ri]?.name ?? `#${ri}`);
1894
1943
  this.violate(ContractDiagnostics.ambiguousChild(typeName, ci, names, this.#context));
1895
1944
  }
1896
1945
  });
1897
1946
  }
1947
+ #resolveRules(context) {
1948
+ if (this.#dynamicRuleInputs.length === 0) {
1949
+ return { rules: this.#staticRules, matcher: this.#matcher };
1950
+ }
1951
+ if (context === void 0) {
1952
+ throw new RangeError(
1953
+ `ChildrenEvaluator [${this.#context}]: rule(s) have a dynamic(...) cardinality \u2014 evaluate() requires a context argument to resolve them.`
1954
+ );
1955
+ }
1956
+ const resolvedDynamicRules = this.#dynamicRuleInputs.map((r) => {
1957
+ const cardinality = resolveRule(r.cardinality, context);
1958
+ return normalizeChildRule({ ...r, cardinality });
1959
+ });
1960
+ checkPositionCardinalityInvariant(resolvedDynamicRules, this.#context);
1961
+ const rules = [...this.#staticRules, ...resolvedDynamicRules];
1962
+ return { rules, matcher: new RuleMatcher(rules) };
1963
+ }
1898
1964
  };
1899
1965
 
1900
1966
  // ../../lib/contract/src/props/get-disabled-props.ts
@@ -2003,8 +2069,11 @@ function metadata(name = "metadata") {
2003
2069
  function firstOptional(name, tag) {
2004
2070
  return { name, match: isTag(tag), cardinality: { max: 1 }, position: "first" };
2005
2071
  }
2006
- function contract(children) {
2007
- return { diagnostics: warnDiagnostics, children };
2072
+ function contract(children, options) {
2073
+ return { diagnostics: warnDiagnostics, children, ...options };
2074
+ }
2075
+ function closedContract(children) {
2076
+ return contract(children, { exclusiveChildren: true });
2008
2077
  }
2009
2078
  function ariaContract(aria) {
2010
2079
  return { diagnostics: warnDiagnostics, aria };
@@ -2030,8 +2099,10 @@ var VOID_TAGS = [
2030
2099
  ];
2031
2100
  var TEXT_ONLY_TAGS = ["option", "script", "style", "textarea", "title"];
2032
2101
  var LANDMARK_TAGS = ["article", "aside", "footer", "header", "main", "nav"];
2033
- var listContract = contract([{ name: "list-item", match: isTag("li", ...METADATA_TAGS) }]);
2034
- var tableContract = contract([
2102
+ var listContract = closedContract([
2103
+ { name: "list-item", match: isTag("li", ...METADATA_TAGS) }
2104
+ ]);
2105
+ var tableContract = closedContract([
2035
2106
  firstOptional("caption", "caption"),
2036
2107
  { name: "colgroup", match: isTag("colgroup") },
2037
2108
  { name: "thead", match: isTag("thead"), cardinality: { max: 1 } },
@@ -2039,29 +2110,31 @@ var tableContract = contract([
2039
2110
  { name: "tfoot", match: isTag("tfoot"), cardinality: { max: 1 } },
2040
2111
  { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
2041
2112
  ]);
2042
- var tableBodyContract = contract([
2113
+ var tableBodyContract = closedContract([
2043
2114
  { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
2044
2115
  ]);
2045
- var tableRowContract = contract([
2116
+ var tableRowContract = closedContract([
2046
2117
  { name: "table-cell", match: isTag("td", "th", ...METADATA_TAGS) }
2047
2118
  ]);
2048
- var colgroupContract = contract([{ name: "column", match: isTag("col", "template") }]);
2049
- var dlContract = contract([
2119
+ var colgroupContract = closedContract([
2120
+ { name: "column", match: isTag("col", "template") }
2121
+ ]);
2122
+ var dlContract = closedContract([
2050
2123
  { name: "term", match: isTag("dt") },
2051
2124
  { name: "description", match: isTag("dd") },
2052
2125
  { name: "group", match: isTag("div") },
2053
2126
  metadata()
2054
2127
  ]);
2055
- var selectContract = contract([
2128
+ var selectContract = closedContract([
2056
2129
  { name: "option", match: isTag("option", "optgroup", "hr", ...METADATA_TAGS) }
2057
2130
  ]);
2058
- var optgroupContract = contract([
2131
+ var optgroupContract = closedContract([
2059
2132
  { name: "option", match: isTag("option", ...METADATA_TAGS) }
2060
2133
  ]);
2061
- var datalistContract = contract([
2134
+ var datalistContract = closedContract([
2062
2135
  { name: "option", match: isTag("option", ...METADATA_TAGS) }
2063
2136
  ]);
2064
- var pictureContract = contract([
2137
+ var pictureContract = closedContract([
2065
2138
  { name: "source", match: isTag("source", ...METADATA_TAGS) },
2066
2139
  { name: "image", match: isTag("img"), cardinality: { min: 1, max: 1 }, position: "last" }
2067
2140
  ]);
@@ -2077,23 +2150,18 @@ var mediaContract = contract([
2077
2150
  metadata(),
2078
2151
  { name: "content", match: isOpenContent("source", "track", ...METADATA_TAGS) }
2079
2152
  ]);
2080
- var headContract = contract([
2153
+ var headContract = closedContract([
2081
2154
  {
2082
2155
  name: "metadata",
2083
2156
  match: isTag("base", "link", "meta", "noscript", "script", "style", "template", "title")
2084
2157
  }
2085
2158
  ]);
2086
- var htmlContract = contract([
2159
+ var htmlContract = closedContract([
2087
2160
  { name: "head", match: isTag("head"), cardinality: { min: 1, max: 1 }, position: "first" },
2088
2161
  { name: "body", match: isTag("body"), cardinality: { min: 1, max: 1 } }
2089
2162
  ]);
2090
- var voidContract = contract([]);
2091
- var textOnlyContract = contract([
2092
- {
2093
- name: "text",
2094
- match: (child) => isString(child) || isNumber(child)
2095
- }
2096
- ]);
2163
+ var voidContract = contract([], { exclusiveChildren: true, allowText: false });
2164
+ var textOnlyContract = contract([], { exclusiveChildren: true });
2097
2165
  var landmarkContract = ariaContract([landmarkRoleRule, landmarkNameAdvisory]);
2098
2166
  var dialogContract = ariaContract([requireAccessibleName]);
2099
2167
  var menuContract = ariaContract([requireAccessibleName]);
@@ -2138,9 +2206,15 @@ var htmlContracts = {
2138
2206
  var htmlDiagnostics = warnDiagnostics2;
2139
2207
  function buildEvaluatorMap() {
2140
2208
  const map2 = /* @__PURE__ */ new Map();
2141
- iterate.forEachEntry(htmlContracts, (tag, { children }) => {
2142
- if (children?.length) {
2143
- map2.set(tag, new ChildrenEvaluator(children, htmlDiagnostics, `<${tag}>`));
2209
+ iterate.forEachEntry(htmlContracts, (tag, { children, exclusiveChildren, allowText }) => {
2210
+ if (children?.length || exclusiveChildren || allowText === false) {
2211
+ map2.set(
2212
+ tag,
2213
+ new ChildrenEvaluator(children ?? [], htmlDiagnostics, `<${tag}>`, {
2214
+ exclusiveChildren,
2215
+ allowText
2216
+ })
2217
+ );
2144
2218
  }
2145
2219
  });
2146
2220
  return map2;
@@ -2351,7 +2425,7 @@ function definePipeline(factory) {
2351
2425
  }
2352
2426
 
2353
2427
  // ../core/src/options/resolve-factory-options.ts
2354
- import { silentDiagnostics } from "../_shared/diagnostics.js";
2428
+ import { resolveDiagnostics, silentDiagnostics } from "../_shared/diagnostics.js";
2355
2429
  var EMPTY_VARIANT_KEYS = /* @__PURE__ */ new Set();
2356
2430
  function composeNormalizers(normalizers, fn) {
2357
2431
  if (!normalizers?.length) return fn;
@@ -2372,7 +2446,7 @@ function resolveFactoryOptions(options = {}) {
2372
2446
  const variantKeys = styling?.variants === void 0 ? EMPTY_VARIANT_KEYS : new Set(Object.keys(styling.variants));
2373
2447
  return Object.freeze({
2374
2448
  defaultTag: options.tag ?? "div",
2375
- diagnostics: enforcement?.diagnostics ?? silentDiagnostics,
2449
+ diagnostics: resolveDiagnostics(enforcement?.diagnostics, silentDiagnostics),
2376
2450
  variantKeys,
2377
2451
  ...whenDefined("displayName", options.name),
2378
2452
  ...whenDefined("defaultProps", options.defaults),
@@ -2385,6 +2459,8 @@ function resolveFactoryOptions(options = {}) {
2385
2459
  ...whenDefined("normalizeFn", composedNormalizeFn),
2386
2460
  ...whenDefined("ariaRules", enforcement?.aria),
2387
2461
  ...whenDefined("childRules", enforcement?.children),
2462
+ ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
2463
+ ...whenDefined("allowText", enforcement?.allowText),
2388
2464
  ...whenDefined("allowedAs", enforcement?.allowedAs),
2389
2465
  ...whenDefined("precomputedClasses", styling?.precomputedClasses)
2390
2466
  });
@@ -2569,16 +2645,22 @@ function buildCoreRuntime(normalized) {
2569
2645
  }
2570
2646
 
2571
2647
  // ../../lib/adapter-utils/src/runtime/build-engines.ts
2572
- function buildEngines(diagnostics, childRules, context) {
2573
- return childRules?.length ? { childrenEvaluator: new ChildrenEvaluator(childRules, diagnostics, context) } : {};
2648
+ function buildEngines(diagnostics, childRules, context, childrenOptions) {
2649
+ const { exclusiveChildren, allowText } = childrenOptions ?? {};
2650
+ return childRules?.length || exclusiveChildren || allowText === false ? {
2651
+ childrenEvaluator: new ChildrenEvaluator(childRules ?? [], diagnostics, context, {
2652
+ exclusiveChildren,
2653
+ allowText
2654
+ })
2655
+ } : {};
2574
2656
  }
2575
2657
 
2576
2658
  // ../../lib/adapter-utils/src/runtime/resolve-adapter-common-options.ts
2577
- import { throwDiagnostics as throwDiagnostics2, silentDiagnostics as silentDiagnostics3 } from "../_shared/diagnostics.js";
2659
+ import { throwDiagnostics as throwDiagnostics2, silentDiagnostics as silentDiagnostics3, resolveDiagnostics as resolveDiagnostics2 } from "../_shared/diagnostics.js";
2578
2660
  function resolveAdapterCommonOptions(options, defaultName = "PolymorphicComponent", defaultDiagnostics = throwDiagnostics2) {
2579
2661
  return {
2580
2662
  name: options.name ?? defaultName,
2581
- diagnostics: options.enforcement?.diagnostics ?? defaultDiagnostics
2663
+ diagnostics: resolveDiagnostics2(options.enforcement?.diagnostics, defaultDiagnostics)
2582
2664
  };
2583
2665
  }
2584
2666
 
@@ -2633,7 +2715,11 @@ function buildRuntime(options) {
2633
2715
  const { childrenEvaluator } = buildEngines(
2634
2716
  normalized.diagnostics,
2635
2717
  normalized.enforcement?.children,
2636
- normalized.name
2718
+ normalized.name,
2719
+ {
2720
+ exclusiveChildren: normalized.enforcement?.exclusiveChildren,
2721
+ allowText: normalized.enforcement?.allowText
2722
+ }
2637
2723
  );
2638
2724
  const filterProps = composeFilter(ownedKeys, normalized.filterProps);
2639
2725
  const slotValidator = new SlotValidator(normalized.name, normalized.diagnostics);
@@ -2646,9 +2732,10 @@ function buildRuntime(options) {
2646
2732
  }
2647
2733
 
2648
2734
  // ../../adapters/solid/src/render.tsx
2735
+ import { createComponent as _$createComponent } from "solid-js/web";
2736
+ import { mergeProps as _$mergeProps } from "solid-js/web";
2649
2737
  import { createEffect, createMemo, splitProps } from "solid-js";
2650
2738
  import { Dynamic } from "solid-js/web";
2651
- import { jsx } from "solid-js/jsx-runtime";
2652
2739
  var SPLIT_KEYS = ["as", "asChild", "children", "class", "recipe", "ref"];
2653
2740
  function toChildArray(children) {
2654
2741
  if (children === void 0 || children === null) return [];
@@ -2656,22 +2743,38 @@ function toChildArray(children) {
2656
2743
  return [children];
2657
2744
  }
2658
2745
  function buildElementProps(props, classStr, ref, children) {
2659
- const { role, ...rest } = props;
2746
+ const {
2747
+ role,
2748
+ ...rest
2749
+ } = props;
2660
2750
  return {
2661
2751
  ...rest,
2662
2752
  class: classStr,
2663
- ...ref !== void 0 && { ref },
2664
- ...children !== void 0 && { children },
2665
- ...isKnownAriaRole(role) && { role }
2753
+ ...ref !== void 0 && {
2754
+ ref
2755
+ },
2756
+ ...children !== void 0 && {
2757
+ children
2758
+ },
2759
+ ...isKnownAriaRole(role) && {
2760
+ role
2761
+ }
2666
2762
  };
2667
2763
  }
2668
2764
  function buildSlotProps(props, classStr, ref) {
2669
- const { role, ...rest } = props;
2765
+ const {
2766
+ role,
2767
+ ...rest
2768
+ } = props;
2670
2769
  return {
2671
2770
  ...rest,
2672
2771
  class: classStr,
2673
- ...ref !== void 0 && { ref },
2674
- ...isKnownAriaRole(role) && { role }
2772
+ ...ref !== void 0 && {
2773
+ ref
2774
+ },
2775
+ ...isKnownAriaRole(role) && {
2776
+ role
2777
+ }
2675
2778
  };
2676
2779
  }
2677
2780
  function resolveTag2(runtime, as) {
@@ -2688,9 +2791,7 @@ function tryRenderAsChild(known, filteredProps, resolvedClass, slotValidator) {
2688
2791
  }
2689
2792
  if (!slotValidator.assertRenderFn(known.children)) return null;
2690
2793
  const renderFn = known.children;
2691
- return createMemo(
2692
- () => renderFn(buildSlotProps(filteredProps(), resolvedClass(), known.ref))
2693
- );
2794
+ return createMemo(() => renderFn(buildSlotProps(filteredProps(), resolvedClass(), known.ref)));
2694
2795
  }
2695
2796
  function render({
2696
2797
  runtime,
@@ -2706,32 +2807,27 @@ function render({
2706
2807
  const normalizedProps = createMemo(() => {
2707
2808
  const base = runtime.options.normalizeFn ? runtime.options.normalizeFn(mergedProps()) : mergedProps();
2708
2809
  const htmlNormalizers = runtime.options.htmlPropNormalizersFn?.(tag());
2709
- return htmlNormalizers?.length ? htmlNormalizers.reduce((acc, fn) => ({ ...acc, ...fn(acc) }), base) : base;
2810
+ return htmlNormalizers?.length ? htmlNormalizers.reduce((acc, fn) => ({
2811
+ ...acc,
2812
+ ...fn(acc)
2813
+ }), base) : base;
2710
2814
  });
2711
- const resolvedClass = createMemo(
2712
- () => runtime.resolveClasses(
2713
- tag(),
2714
- normalizedProps(),
2715
- known.class,
2716
- known.recipe
2717
- )
2718
- );
2719
- const filteredProps = createMemo(
2720
- () => applyFilter(normalizedProps(), filterProps, runtime.options.variantKeys)
2721
- );
2722
- const { allowedAs } = runtime.options;
2815
+ const resolvedClass = createMemo(() => runtime.resolveClasses(tag(), normalizedProps(), known.class, known.recipe));
2816
+ const filteredProps = createMemo(() => applyFilter(normalizedProps(), filterProps, runtime.options.variantKeys));
2817
+ const {
2818
+ allowedAs
2819
+ } = runtime.options;
2723
2820
  if (allowedAs !== void 0) {
2724
- createEffect(
2725
- () => enforceAllowedAs(tag(), allowedAs, runtime.options.diagnostics, runtime.options.displayName)
2726
- );
2821
+ createEffect(() => enforceAllowedAs(tag(), allowedAs, runtime.options.diagnostics, runtime.options.displayName));
2727
2822
  }
2728
2823
  if (process.env.NODE_ENV !== "production" && childrenEvaluator) {
2729
- createEffect(() => childrenEvaluator.evaluate(toChildArray(known.children)));
2824
+ createEffect(() => childrenEvaluator.evaluate(toChildArray(known.children), {
2825
+ tag: tag(),
2826
+ props: normalizedProps()
2827
+ }));
2730
2828
  }
2731
2829
  if (process.env.NODE_ENV !== "production") {
2732
- createEffect(
2733
- () => runtime.options.htmlChildrenEvaluatorFn?.(tag())?.evaluate(toChildArray(known.children))
2734
- );
2830
+ createEffect(() => runtime.options.htmlChildrenEvaluatorFn?.(tag())?.evaluate(toChildArray(known.children)));
2735
2831
  }
2736
2832
  const slotResult = tryRenderAsChild(known, filteredProps, resolvedClass, slotValidator);
2737
2833
  if (slotResult !== null) return slotResult;
@@ -2739,7 +2835,11 @@ function render({
2739
2835
  const ep = buildElementProps(filteredProps(), resolvedClass(), known.ref, known.children);
2740
2836
  return resolveDomProps(tag(), ep, runtime);
2741
2837
  });
2742
- return /* @__PURE__ */ jsx(Dynamic, { component: tag(), ...domProps() });
2838
+ return _$createComponent(Dynamic, _$mergeProps({
2839
+ get component() {
2840
+ return tag();
2841
+ }
2842
+ }, () => domProps()));
2743
2843
  }
2744
2844
 
2745
2845
  // ../../adapters/solid/src/create-contract-component.ts
@@ -0,0 +1,146 @@
1
+ <script module lang="ts">
2
+ declare const process: { env: { NODE_ENV: string } }
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ import { enforceAllowedAs, isKnownAriaRole } from '@praxis-kit/core'
7
+ import type { ElementType, IntrinsicProps } from '@praxis-kit/core'
8
+ import { isObject, isString } from '@praxis-kit/primitive'
9
+ import { applyFilter } from '@praxis-kit/adapter-utils'
10
+ import type { Snippet } from 'svelte'
11
+ import type {
12
+ PolymorphicComponentProps,
13
+ ResolvedAttributes,
14
+ StyleObject,
15
+ UnknownProps,
16
+ } from './types'
17
+
18
+ let {
19
+ bundle,
20
+ as: asProp,
21
+ asChild,
22
+ class: cls,
23
+ recipe,
24
+ children,
25
+ ...rest
26
+ }: PolymorphicComponentProps = $props()
27
+ let hostEl: Element | undefined = $state()
28
+
29
+ // Svelte 5 event delegation requires lowercase handler names (onclick, onfocus…).
30
+ // Normalize React-style camelCase handlers so spreads on <svelte:element> work.
31
+ const EVENT_RE = /^on[A-Z]/
32
+ function normalizeEventKeys(props: UnknownProps): UnknownProps {
33
+ const out: ResolvedAttributes = {}
34
+ for (const k in props) {
35
+ out[EVENT_RE.test(k) ? k.toLowerCase() : k] = (props as ResolvedAttributes)[k]
36
+ }
37
+ return out as UnknownProps
38
+ }
39
+
40
+ function serializeStyle(style: StyleObject): string {
41
+ let result = ''
42
+ for (const key in style) {
43
+ const value = style[key]
44
+ if (value == null) continue
45
+ if (result) result += ';'
46
+ result += `${key.replace(/([A-Z])/g, '-$1').toLowerCase()}:${value}`
47
+ }
48
+ return result
49
+ }
50
+
51
+ function buildDomProps(
52
+ props: UnknownProps,
53
+ classStr: string,
54
+ tag: ElementType,
55
+ ): ResolvedAttributes {
56
+ const { role, style, ...r } = normalizeEventKeys(props)
57
+ const styleStr = isObject(style, true)
58
+ ? serializeStyle(style as StyleObject)
59
+ : (style as string | undefined)
60
+ const ep: IntrinsicProps = {
61
+ ...(r as IntrinsicProps),
62
+ class: classStr,
63
+ ...(styleStr !== undefined && { style: styleStr }),
64
+ }
65
+ if (isKnownAriaRole(role)) ep.role = role
66
+ if (!isString(tag)) return ep as ResolvedAttributes
67
+ return bundle.runtime.resolveAria(tag, ep).props as ResolvedAttributes
68
+ }
69
+
70
+ function buildSlotProps(props: UnknownProps, classStr: string): UnknownProps {
71
+ const { role, ...r } = props
72
+ return {
73
+ ...r,
74
+ class: classStr,
75
+ ...(isKnownAriaRole(role) && { role }),
76
+ }
77
+ }
78
+
79
+ const tag = $derived(bundle.runtime.resolveTag(asProp as ElementType | undefined))
80
+ const mergedProps = $derived(bundle.runtime.resolveProps(rest as UnknownProps))
81
+ const normalizedProps = $derived.by(() => {
82
+ const { runtime: { options } } = bundle
83
+ // Folded into this derived (rather than a standalone one) so it's guaranteed to run on
84
+ // both SSR and DOM — $derived is lazy in Svelte 5 and only evaluates when read; this one
85
+ // is read downstream by resolvedClass/filteredProps/domProps, which the template renders.
86
+ if (options.allowedAs !== undefined) {
87
+ enforceAllowedAs(
88
+ tag,
89
+ options.allowedAs,
90
+ options.diagnostics,
91
+ options.displayName,
92
+ )
93
+ }
94
+ const base =
95
+ typeof options.normalizeFn === 'function' ? options.normalizeFn(mergedProps) : mergedProps
96
+
97
+ const htmlNormalizers = options.htmlPropNormalizersFn?.(tag)
98
+
99
+ if (!htmlNormalizers?.length) {
100
+ return base
101
+ }
102
+
103
+ return htmlNormalizers.reduce((acc, fn) => ({ ...acc, ...fn(acc) }), base)
104
+ })
105
+ const resolvedClass = $derived(
106
+ bundle.runtime.resolveClasses(tag, normalizedProps, cls as string | undefined, recipe),
107
+ )
108
+ const filteredProps = $derived(
109
+ applyFilter(normalizedProps, bundle.filterProps, bundle.runtime.options.variantKeys),
110
+ )
111
+ const domProps = $derived(buildDomProps(filteredProps, resolvedClass, tag))
112
+
113
+ // Resolves whether to render as child slot; also enforces as+asChild mutual exclusion.
114
+ const useAsChild = $derived.by(() => {
115
+ if (!asChild) return false
116
+ if (asProp !== undefined) {
117
+ bundle.slotValidator.assertExclusive()
118
+ return false
119
+ }
120
+ return true
121
+ })
122
+
123
+ // DOM-only (like Lit skipping htmlChildrenEvaluatorFn for its SSR renderToString). Merely
124
+ // *registering* $effect during svelte/server's render() throws ("effect_orphan") — it's not
125
+ // enough to no-op inside the callback, the rune call itself must never happen during SSR.
126
+ // Reads real child nodes off the mounted host, same approach as Lit's Array.from(this.childNodes).
127
+ // Not reachable in the asChild branch — no host element there.
128
+ if (typeof document !== 'undefined') {
129
+ $effect(() => {
130
+ if (process.env.NODE_ENV === 'production' || !hostEl) return
131
+ const childArray = Array.from(hostEl.childNodes)
132
+ bundle.childrenEvaluator?.evaluate(childArray, { tag, props: normalizedProps })
133
+ bundle.runtime.options.htmlChildrenEvaluatorFn?.(tag)?.evaluate(childArray)
134
+ })
135
+ }
136
+ </script>
137
+
138
+ {#if useAsChild}
139
+ {#if children}
140
+ {@render (children as Snippet<[UnknownProps]>)(buildSlotProps(filteredProps, resolvedClass))}
141
+ {/if}
142
+ {:else}
143
+ <svelte:element this={tag as string} bind:this={hostEl} {...domProps}>
144
+ {@render (children as Snippet | undefined)?.()}
145
+ </svelte:element>
146
+ {/if}