@bridge-ds/core 0.5.1 → 0.7.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.
@@ -2,7 +2,7 @@ import { execFile } from "node:child_process";
2
2
  import { mkdtemp, rm } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join, resolve, relative, dirname, sep, posix } from "node:path";
5
- import { a as assertSafeBranch, c as collectHistory, p as packageDirCandidates, b as parseChangelog, m as mergeReleases, r as releasesFromTags, d as buildReport, e as prNumberFromSubject, l as loadConfig, h as hashSpec, C as COMPONENT_SPECS_VERSION, f as CONFIG_FILE, g as layoutOptions, v as variantKeyOf, i as buildTokenSet, t as tokenCssVar, j as iconComponentName, k as COLOR_FIELDS } from "../component-spec-cvrSvIZj.js";
5
+ import { a as assertSafeBranch, c as collectHistory, p as packageDirCandidates, b as parseChangelog, m as mergeReleases, r as releasesFromTags, d as buildReport, e as prNumberFromSubject, l as loadConfig, s as specHash, v as validateComponentSpec, h as hashSpec, i as iconComponentName, f as variantKeyOf, C as CONFIG_FILE, g as layoutOptions, j as buildTokenSet, k as defaultContext, t as tokenCssVar, n as resolve$1, o as COMPONENT_SPECS_VERSION, q as COLOR_FIELDS } from "../component-spec-1z2ih4E3.js";
6
6
  import { s as selectTokenFiles, g as globToRegExp, L as LayeredTokenSet } from "../layered-BnbmToGs.js";
7
7
  import { statSync, readFileSync, existsSync, writeFileSync, readdirSync } from "node:fs";
8
8
  import { v as isAuditable, Q as planCssFixes, O as parseAllowlist, c as DesignSystemIndex, k as buildAuditReport, n as compareWithBaseline, a as AUDIT_IGNORE_DIRS } from "../report-iPSnsjYi.js";
@@ -495,13 +495,26 @@ function siblingsBefore(el) {
495
495
  const idx = sibs.indexOf(el);
496
496
  return idx < 0 ? [] : sibs.slice(0, idx);
497
497
  }
498
+ function checkedAttr(name, checked) {
499
+ if (checked === void 0) return void 0;
500
+ if (name === "aria-checked") return checked;
501
+ if (name === "data-checked" || name === "data-selected" || name === "data-active") return checked === "true" ? "" : void 0;
502
+ if (name === "data-unchecked") return checked === "false" ? "" : void 0;
503
+ if (name === "data-indeterminate") return checked === "mixed" ? "" : void 0;
504
+ return void 0;
505
+ }
498
506
  function attrMatches(el, s, state) {
499
507
  switch (s.name) {
500
508
  case "data-disabled":
501
509
  case "disabled":
502
- return state.disabled && (el.isRoot || el.inputLike);
503
- default:
504
- return false;
510
+ return state.disabled && (el.isRoot || el.inputLike || !!el.baseUi);
511
+ default: {
512
+ const own = el.checked === "state" || el.inputLike && el.checked === void 0 ? state.checked : el.checked;
513
+ const fromState = el.baseUi || el.inputLike ? checkedAttr(s.name, own) : void 0;
514
+ const v = fromState ?? el.attrs?.get(s.name);
515
+ if (v === void 0) return false;
516
+ return s.value === void 0 || s.value === v;
517
+ }
505
518
  }
506
519
  }
507
520
  function pseudoMatches(el, s, state, module) {
@@ -516,6 +529,10 @@ function pseudoMatches(el, s, state, module) {
516
529
  return !state.disabled;
517
530
  case "placeholder-shown":
518
531
  return el.inputLike;
532
+ case "checked":
533
+ return (el.inputLike || !!el.baseUi) && (el.checked === "state" || el.checked === void 0 ? state.checked : el.checked) === "true";
534
+ case "indeterminate":
535
+ return (el.inputLike || !!el.baseUi) && (el.checked === "state" || el.checked === void 0 ? state.checked : el.checked) === "mixed";
519
536
  case "not":
520
537
  return !(s.args ?? []).some((a) => matchComplex(a, el, state, module));
521
538
  case "is":
@@ -666,6 +683,65 @@ function expandDecl(d) {
666
683
  { prop: `border-${s}-color`, value: color }
667
684
  ]);
668
685
  }
686
+ case "border-inline-start":
687
+ case "border-inline-end":
688
+ case "border-block-start":
689
+ case "border-block-end": {
690
+ const side = { "border-inline-start": "left", "border-inline-end": "right", "border-block-start": "top", "border-block-end": "bottom" }[d.prop];
691
+ return expandDecl({ ...d, prop: `border-${side}` });
692
+ }
693
+ case "inset-block-start":
694
+ return [{ prop: "top", value: d.value }];
695
+ case "inset-block-end":
696
+ return [{ prop: "bottom", value: d.value }];
697
+ case "inset-inline-start":
698
+ return [{ prop: "left", value: d.value }];
699
+ case "inset-inline-end":
700
+ return [{ prop: "right", value: d.value }];
701
+ case "inset": {
702
+ const v = boxValues(parts);
703
+ return v ? SIDES.map((side, i) => ({ prop: side, value: v[i] })) : [d];
704
+ }
705
+ case "inset-block": {
706
+ const [a, b = a] = parts;
707
+ return [
708
+ { prop: "top", value: a },
709
+ { prop: "bottom", value: b }
710
+ ];
711
+ }
712
+ case "inset-inline": {
713
+ const [a, b = a] = parts;
714
+ return [
715
+ { prop: "left", value: a },
716
+ { prop: "right", value: b }
717
+ ];
718
+ }
719
+ case "place-items": {
720
+ const [align, justify = align] = parts;
721
+ return [
722
+ { prop: "align-items", value: align },
723
+ { prop: "justify-items", value: justify }
724
+ ];
725
+ }
726
+ case "text-decoration": {
727
+ const line = parts.filter((x) => /^(none|underline|overline|line-through)$/.test(x));
728
+ return line.length ? [{ prop: "text-decoration-line", value: line.join(" ") }] : [d];
729
+ }
730
+ case "gap": {
731
+ const [row, col = row] = parts;
732
+ return [
733
+ { prop: "row-gap", value: row },
734
+ { prop: "column-gap", value: col }
735
+ ];
736
+ }
737
+ case "flex-flow": {
738
+ const out = [];
739
+ for (const x of parts) {
740
+ if (/^(row|column)(-reverse)?$/.test(x)) out.push({ prop: "flex-direction", value: x });
741
+ else if (/wrap/.test(x)) out.push({ prop: "flex-wrap", value: x });
742
+ }
743
+ return out.length ? out : [d];
744
+ }
669
745
  case "font":
670
746
  return d.value === "inherit" ? ["font-family", "font-size", "font-weight", "line-height"].map((prop) => ({ prop, value: "inherit" })) : [d];
671
747
  default:
@@ -683,7 +759,7 @@ function splitBorder(parts) {
683
759
  const width = parts.find((p) => /width|^\d/.test(p)) ?? "medium";
684
760
  return { width, color: parts.filter((p) => p !== width).join(" ") || "currentcolor" };
685
761
  }
686
- const INHERITED = /* @__PURE__ */ new Set(["color", "font-family", "font-size", "font-weight", "line-height", "letter-spacing", "cursor", "white-space", "visibility"]);
762
+ const INHERITED = /* @__PURE__ */ new Set(["color", "font-family", "font-size", "font-weight", "line-height", "letter-spacing", "cursor", "white-space", "visibility", "text-decoration-line", "text-decoration-color"]);
687
763
  function declaredFor(rules, el, state, pseudoElement) {
688
764
  const matched = rules.filter((r) => matchComplex(r.selector, el, state, r.module, pseudoElement));
689
765
  matched.sort((a, b) => cmpSpec(a.specificity, b.specificity) || a.order - b.order);
@@ -697,15 +773,247 @@ function computeValues(own, parent) {
697
773
  for (const [k, v] of parent) if (k.startsWith("--") || INHERITED.has(k)) out.set(k, v);
698
774
  }
699
775
  for (const [k, d] of own) {
700
- if (d.value === "inherit") {
776
+ if (d.value === "inherit" || k === "color" && d.value.trim().toLowerCase() === "currentcolor") {
701
777
  const pv = parent?.get(k);
702
778
  if (pv !== void 0) out.set(k, pv);
703
779
  else out.delete(k);
780
+ } else if (k.startsWith("--") && d.value === "initial") {
781
+ out.set(k, "initial");
704
782
  } else if (d.value === "initial" || d.value === "unset") out.delete(k);
705
783
  else out.set(k, d.value);
706
784
  }
707
785
  return out;
708
786
  }
787
+ const KEYWORDS = {
788
+ ease: [0.25, 0.1, 0.25, 1],
789
+ "ease-in": [0.42, 0, 1, 1],
790
+ "ease-out": [0, 0, 0.58, 1],
791
+ "ease-in-out": [0.42, 0, 0.58, 1]
792
+ };
793
+ const SILENT = /^(cursor|pointer-events|user-select|-webkit-.*|transition.*|animation.*|will-change|outline.*|content|touch-action)$/;
794
+ function substituteVars(raw, computed, tokens, depth = 0) {
795
+ if (depth > 12) return raw;
796
+ return raw.replace(/var\(\s*(--[\w-]+)\s*(?:,\s*([^()]*(?:\([^()]*\)[^()]*)*))?\)/g, (m, name, fallback) => {
797
+ if (tokens.has(name)) return m;
798
+ const v = computed.get(name);
799
+ if (v !== void 0 && v !== "initial") return substituteVars(v, computed, tokens, depth + 1);
800
+ return fallback !== void 0 ? substituteVars(fallback.trim(), computed, tokens, depth + 1) : m;
801
+ });
802
+ }
803
+ const isTime = (t, tokens) => /^-?[\d.]+m?s$/i.test(t) || tokens.get(varName(t) ?? "")?.type === "duration";
804
+ const varName = (t) => /^var\(\s*(--[\w-]+)/.exec(t)?.[1];
805
+ function parseTransitions(computed, tokens) {
806
+ const sub = (k) => {
807
+ const v = computed.get(k);
808
+ return v === void 0 ? void 0 : substituteVars(v, computed, tokens);
809
+ };
810
+ const out = [];
811
+ const short = sub("transition");
812
+ if (short && short !== "none") {
813
+ for (const item of splitTop(short, ",")) {
814
+ const words = splitTop(item.trim(), /\s/).filter(Boolean);
815
+ const e = { property: "all", duration: "0s", easing: "ease", delay: "0s" };
816
+ let times = 0;
817
+ for (const w of words) {
818
+ if (isTime(w, tokens)) {
819
+ if (times++ === 0) e.duration = w;
820
+ else e.delay = w;
821
+ } else if (KEYWORDS[w] || w === "linear" || /^(cubic-bezier|steps|linear)\(/.test(w) || /^step-(start|end)$/.test(w) || tokens.get(varName(w) ?? "")?.type === "cubicBezier") e.easing = w;
822
+ else e.property = w;
823
+ }
824
+ out.push(e);
825
+ }
826
+ }
827
+ const props = sub("transition-property");
828
+ if (props === void 0) return out;
829
+ const list = (k, def) => {
830
+ const v = sub(k);
831
+ return v === void 0 ? [def] : splitTop(v, ",").map((x) => x.trim());
832
+ };
833
+ const durations = list("transition-duration", "0s");
834
+ const easings = list("transition-timing-function", "ease");
835
+ const delays = list("transition-delay", "0s");
836
+ return splitTop(props, ",").map((p) => p.trim()).filter((p) => p && p !== "none").map((property, i) => ({ property, duration: durations[i % durations.length], easing: easings[i % easings.length], delay: delays[i % delays.length] }));
837
+ }
838
+ function resolveDuration(raw, tokens) {
839
+ const v = raw.trim();
840
+ const lit = /^(-?[\d.]+)(m?s)$/i.exec(v);
841
+ if (lit) return { ms: Number(lit[1]) * (lit[2].toLowerCase() === "s" ? 1e3 : 1) };
842
+ const t = tokens.get(varName(v) ?? "");
843
+ if (!t || t.type !== "duration") return void 0;
844
+ const value = t.value;
845
+ if (typeof value === "string") {
846
+ const r = resolveDuration(value, /* @__PURE__ */ new Map());
847
+ return r ? { ms: r.ms, token: t.path } : void 0;
848
+ }
849
+ if (value && typeof value.value === "number") return { ms: value.value * (value.unit === "s" ? 1e3 : 1), token: t.path };
850
+ return void 0;
851
+ }
852
+ function resolveEasing(raw, tokens) {
853
+ const v = raw.trim().toLowerCase();
854
+ const curve = (b) => b[0] === 0 && b[1] === 0 && b[2] === 1 && b[3] === 1 ? { type: "LINEAR" } : { type: "CUSTOM_CUBIC_BEZIER", bezier: [b[0], b[1], b[2], b[3]] };
855
+ if (v === "linear") return { easing: { type: "LINEAR" } };
856
+ if (KEYWORDS[v]) return { easing: curve(KEYWORDS[v]) };
857
+ const cb = /^cubic-bezier\(([^)]*)\)$/.exec(v);
858
+ if (cb) {
859
+ const n = cb[1].split(",").map((x) => Number(x.trim()));
860
+ return n.length === 4 && n.every((x) => Number.isFinite(x)) ? { easing: curve(n) } : void 0;
861
+ }
862
+ if (/^(steps\(|step-(start|end)$)/.test(v)) return { easing: { type: "LINEAR" }, approximated: `${raw.trim()} moves in steps, Figma has no stepped easing: linear` };
863
+ if (/^linear\(/.test(v)) return { easing: { type: "LINEAR" }, approximated: "a linear() curve has no Figma easing: linear" };
864
+ const t = tokens.get(varName(raw.trim()) ?? "");
865
+ if (!t || t.type !== "cubicBezier") return void 0;
866
+ const value = t.value;
867
+ if (Array.isArray(value) && value.length === 4 && value.every((x) => typeof x === "number")) return { easing: curve(value), token: t.path };
868
+ if (typeof value === "string") {
869
+ const r = resolveEasing(value, /* @__PURE__ */ new Map());
870
+ return r ? { ...r, token: t.path } : void 0;
871
+ }
872
+ return void 0;
873
+ }
874
+ const shorthandOf = (prop) => prop.replace(/-(top|bottom)-(left|right)(?=-|$)/, "").replace(/-(top|right|bottom|left)(?=-|$)/, "").replace(/-(block|inline)(-(start|end))?(?=-|$)/, "");
875
+ function entryFor(prop, entries) {
876
+ const general = shorthandOf(prop);
877
+ for (let i = entries.length - 1; i >= 0; i--) {
878
+ const p = entries[i].property;
879
+ if (p === "all" || p === prop || p === general || prop.startsWith(`${p}-`) || general.startsWith(`${p}-`)) return entries[i];
880
+ }
881
+ return void 0;
882
+ }
883
+ function changedProperties(from, to, tokens) {
884
+ const a = from ?? /* @__PURE__ */ new Map();
885
+ const b = to ?? /* @__PURE__ */ new Map();
886
+ const out = [];
887
+ for (const k of /* @__PURE__ */ new Set([...a.keys(), ...b.keys()])) {
888
+ if (k.startsWith("--") || SILENT.test(k)) continue;
889
+ const x = a.get(k);
890
+ const y = b.get(k);
891
+ if ((x === void 0 ? void 0 : substituteVars(x, a, tokens)) !== (y === void 0 ? void 0 : substituteVars(y, b, tokens))) out.push(k);
892
+ }
893
+ return out.sort();
894
+ }
895
+ const fmtMs = (ms) => `${Math.round(ms)} ms`;
896
+ function transitionBetween(from, to, tokens) {
897
+ const animated = [];
898
+ for (const id of /* @__PURE__ */ new Set([...from.keys(), ...to.keys()])) {
899
+ const changed = changedProperties(from.get(id), to.get(id), tokens);
900
+ if (!changed.length) continue;
901
+ const entries = parseTransitions(to.get(id) ?? /* @__PURE__ */ new Map(), tokens);
902
+ for (const prop of changed) {
903
+ const e = entryFor(prop, entries);
904
+ if (!e) continue;
905
+ const d = resolveDuration(e.duration, tokens);
906
+ const ease = resolveEasing(e.easing, tokens);
907
+ if (!d || d.ms <= 0 || !ease) continue;
908
+ const delay = resolveDuration(e.delay, tokens)?.ms ?? 0;
909
+ animated.push({ prop, ms: d.ms, ...d.token ? { durationToken: d.token } : {}, easing: ease.easing, ...ease.token ? { easingToken: ease.token } : {}, ...ease.approximated ? { approximated: ease.approximated } : {}, delay });
910
+ }
911
+ }
912
+ if (!animated.length) return null;
913
+ const main = animated.reduce((m, x) => x.ms > m.ms ? x : m, animated[0]);
914
+ const notes = [];
915
+ const speeds = new Set(animated.map((x) => `${x.ms}|${JSON.stringify(x.easing)}`));
916
+ if (speeds.size > 1) {
917
+ const others = [...new Set(animated.filter((x) => x.ms !== main.ms).map((x) => fmtMs(x.ms)))];
918
+ notes.push(others.length ? `parts move at ${[fmtMs(main.ms), ...others].join(" and ")} in code, Figma plays one speed: ${fmtMs(main.ms)}` : "parts use different easings in code, Figma plays one");
919
+ }
920
+ if (animated.some((x) => x.delay > 0)) notes.push("a transition delay in code is not played");
921
+ for (const x of animated) if (x.approximated && !notes.includes(x.approximated)) notes.push(x.approximated);
922
+ return {
923
+ duration: main.ms,
924
+ ...main.durationToken ? { durationToken: main.durationToken } : {},
925
+ easing: main.easing,
926
+ ...main.easingToken ? { easingToken: main.easingToken } : {},
927
+ properties: [...new Set(animated.map((x) => shorthandOf(x.prop)))].sort(),
928
+ ...notes.length ? { approximated: notes.join("; ") } : {}
929
+ };
930
+ }
931
+ function targetClasses(rule) {
932
+ const last = rule.selector.parts[rule.selector.parts.length - 1];
933
+ return (last?.compound ?? []).filter((s) => s.t === "class").map((s) => `${rule.module}|${s.name}`);
934
+ }
935
+ const OPEN_ATTR = /\[data-(starting-style|ending-style|open|closed|popup-open|panel-open|expanded)\]/;
936
+ function motionNotes(rules, classes, tokens, view, elements = [...classes].map((c) => [c])) {
937
+ const out = [];
938
+ const add = (n) => {
939
+ if (!out.some((x) => x.what === n.what)) out.push(n);
940
+ };
941
+ const styled = (r) => targetClasses(r).some((c) => classes.has(c));
942
+ const opened = /* @__PURE__ */ new Set();
943
+ for (const r of rules) {
944
+ if (!styled(r)) continue;
945
+ const values = new Map(r.decls.map((d) => [d.prop, d.value]));
946
+ const anim = values.get("animation") ?? values.get("animation-name");
947
+ if (anim && !/^none\b/.test(anim.trim())) {
948
+ const words = splitTop(anim.trim(), /\s/).filter(Boolean);
949
+ const name = values.get("animation-name") ?? words.find((w) => !isTime(w, tokens) && !resolveEasing(w, tokens) && !/^(infinite|\d+|alternate|reverse|normal|forwards|backwards|both|running|paused)$/.test(w));
950
+ const dur = words.map((w) => resolveDuration(w, tokens)).find(Boolean) ?? (values.get("animation-duration") ? resolveDuration(values.get("animation-duration"), tokens) : void 0);
951
+ const loops = /\binfinite\b/.test(anim) || values.get("animation-iteration-count") === "infinite";
952
+ add({
953
+ what: `${loops ? "Looping animation" : "Keyframe animation"} ${name ?? ""}${dur ? `, ${fmtMs(dur.ms)}` : ""}`.replace(/\s+,/, ",").trim(),
954
+ reason: loops ? "a Figma component cannot loop an animation on its own" : "keyframes have no Figma equivalent between variants",
955
+ ...dur?.token ? { durationToken: dur.token } : {}
956
+ });
957
+ }
958
+ if (/:focus(-visible|-within)?\b/.test(r.text)) add({ what: "Keyboard focus", reason: "the focus ring shows when a keyboard moves to the component; Figma prototypes have no focus state" });
959
+ if (OPEN_ATTR.test(r.text)) {
960
+ for (const c of targetClasses(r)) if (classes.has(c)) opened.add(c);
961
+ add({
962
+ what: view === "popup" ? "Opening and closing the popup" : "Opening and closing",
963
+ reason: view === "popup" ? "the popup is its own set; a Figma component cannot open another one from its trigger" : "the open state is not drawn as a variant"
964
+ });
965
+ }
966
+ }
967
+ const note = out.find((n) => n.what.startsWith("Opening and closing"));
968
+ if (note) {
969
+ const { enter, exit } = openingTransitions(rules, elements.filter((el) => el.some((c) => opened.has(c))), tokens);
970
+ if (enter) note.enter = enter;
971
+ if (exit) note.exit = exit;
972
+ }
973
+ return out;
974
+ }
975
+ const ENDING_ATTR = /\[data-(ending-style|closed)\]/;
976
+ const STATE_PSEUDO = /:(hover|active|focus|focus-visible|focus-within|disabled|checked)\b/;
977
+ function openingTransitions(rules, elements, tokens) {
978
+ const best = {};
979
+ for (const el of elements) {
980
+ const mine = rules.filter((r) => targetClasses(r).some((c) => el.includes(c))).sort((a, b) => a.order - b.order);
981
+ const base = mine.filter((r) => !/\[data-/.test(r.text) && !STATE_PSEUDO.test(r.text));
982
+ const states = mine.filter((r) => OPEN_ATTR.test(r.text));
983
+ const opens = states.filter((r) => !/\[data-(starting-style|ending-style|closed)\]/.test(r.text));
984
+ const endings = states.filter((r) => ENDING_ATTR.test(r.text));
985
+ const changed = new Set(states.flatMap((r) => r.decls.map((d) => d.prop)).filter((p) => !p.startsWith("--") && !SILENT.test(p)));
986
+ if (!changed.size) continue;
987
+ const settle = (list) => {
988
+ const computed = /* @__PURE__ */ new Map();
989
+ for (const r of [...list].sort((a, b) => a.order - b.order)) for (const d of r.decls) computed.set(d.prop, d.value);
990
+ return computed;
991
+ };
992
+ const find = (computed) => {
993
+ const entries = parseTransitions(computed, tokens);
994
+ let main;
995
+ const props = /* @__PURE__ */ new Set();
996
+ for (const prop of changed) {
997
+ const e = entryFor(prop, entries);
998
+ if (!e) continue;
999
+ const d = resolveDuration(e.duration, tokens);
1000
+ const ease = resolveEasing(e.easing, tokens);
1001
+ if (!d || d.ms <= 0 || !ease) continue;
1002
+ props.add(shorthandOf(prop));
1003
+ if (!main || d.ms > main.ms) main = { ms: d.ms, ...d.token ? { durationToken: d.token } : {}, easing: ease.easing, ...ease.token ? { easingToken: ease.token } : {}, props };
1004
+ }
1005
+ return main;
1006
+ };
1007
+ for (const [dir, found] of [["enter", find(settle([...base, ...opens]))], ["exit", find(settle([...base, ...endings]))]]) {
1008
+ if (!found) continue;
1009
+ const cur = best[dir];
1010
+ if (!cur || found.ms > cur.ms) best[dir] = { ...found, props: /* @__PURE__ */ new Set([...cur?.props ?? [], ...found.props]) };
1011
+ else for (const p of found.props) cur.props.add(p);
1012
+ }
1013
+ }
1014
+ const out = (f) => f ? { duration: f.ms, ...f.durationToken ? { durationToken: f.durationToken } : {}, easing: f.easing, ...f.easingToken ? { easingToken: f.easingToken } : {}, properties: [...f.props].sort() } : null;
1015
+ return { enter: out(best.enter), exit: out(best.exit) };
1016
+ }
709
1017
  const STATES = [
710
1018
  { name: "Default", state: DEFAULT_STATE },
711
1019
  { name: "Hover", state: { hover: true, active: false, disabled: false } },
@@ -717,7 +1025,6 @@ const REPORTED = {
717
1025
  "box-shadow": "shadows are code-only tokens (no Figma variable)",
718
1026
  outline: "focus rings are not drawn",
719
1027
  transform: "transforms are not drawn",
720
- opacity: "opacity is not mapped yet",
721
1028
  filter: "filters are not drawn",
722
1029
  "margin-left": "margins have no auto layout equivalent",
723
1030
  "margin-right": "margins have no auto layout equivalent",
@@ -744,8 +1051,25 @@ class Reader {
744
1051
  dirOf(name) {
745
1052
  return `${this.opts.componentsDir}/${name}`;
746
1053
  }
1054
+ /** Export name -> component folder, for components a file exports besides its own (Radio in RadioGroup). */
1055
+ exportIndex;
1056
+ /** The folder a component comes from: its own, or the folder whose file exports it. */
1057
+ folderOf(name) {
1058
+ if (this.opts.read(`${this.dirOf(name)}/${name}.tsx`) !== void 0) return name;
1059
+ if (!this.exportIndex) {
1060
+ this.exportIndex = /* @__PURE__ */ new Map();
1061
+ for (const folder of this.opts.components ?? []) {
1062
+ const text = this.opts.read(`${this.dirOf(folder)}/${folder}.tsx`) ?? "";
1063
+ for (const m of text.matchAll(/forwardRef[^(]*\(\s*function\s+([A-Z]\w*)|export function ([A-Z]\w*)/g)) {
1064
+ const n = m[1] ?? m[2];
1065
+ if (n !== folder && !this.exportIndex.has(n)) this.exportIndex.set(n, folder);
1066
+ }
1067
+ }
1068
+ }
1069
+ return this.exportIndex.get(name);
1070
+ }
747
1071
  exists(name) {
748
- return this.opts.read(`${this.dirOf(name)}/${name}.tsx`) !== void 0;
1072
+ return this.folderOf(name) !== void 0;
749
1073
  }
750
1074
  /** `export type X = ...` of a component file and of the component files it imports from. */
751
1075
  aliases(file, sf, depth = 0) {
@@ -769,19 +1093,56 @@ class Reader {
769
1093
  }
770
1094
  load(name) {
771
1095
  if (this.cache.has(name)) return this.cache.get(name);
772
- const file = `${this.dirOf(name)}/${name}.tsx`;
773
- const text = this.opts.read(file);
1096
+ const folder = this.folderOf(name);
1097
+ const file = `${this.dirOf(folder ?? name)}/${folder ?? name}.tsx`;
1098
+ const text = folder ? this.opts.read(file) : void 0;
774
1099
  if (text === void 0) {
775
1100
  this.cache.set(name, null);
776
1101
  return null;
777
1102
  }
778
1103
  const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
779
1104
  const styles = /* @__PURE__ */ new Map();
1105
+ const baseUi = /* @__PURE__ */ new Set();
1106
+ const helpers = /* @__PURE__ */ new Map();
1107
+ const lucide = /* @__PURE__ */ new Map();
1108
+ const consts = /* @__PURE__ */ new Map();
1109
+ const baseUiModules = /* @__PURE__ */ new Set();
1110
+ const fnConsts = /* @__PURE__ */ new Map();
1111
+ const imported = /* @__PURE__ */ new Map();
780
1112
  for (const st of sf.statements) {
781
- if (!ts.isImportDeclaration(st) || !st.importClause?.name) continue;
1113
+ if (ts.isVariableStatement(st))
1114
+ for (const d of st.declarationList.declarations) {
1115
+ if (!ts.isIdentifier(d.name) || !d.initializer) continue;
1116
+ const init = skipCasts(d.initializer);
1117
+ if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) fnConsts.set(d.name.text, init);
1118
+ else if (!/forwardRef|^\(/.test(d.initializer.getText(sf).slice(0, 12))) consts.set(d.name.text, d.initializer);
1119
+ }
1120
+ if (ts.isFunctionDeclaration(st) && st.name && /^[a-z]/.test(st.name.text) && st.body) helpers.set(st.name.text, st);
1121
+ if (!ts.isImportDeclaration(st) || !st.importClause) continue;
782
1122
  const spec = unquote(st.moduleSpecifier.getText(sf));
783
- if (!spec.endsWith(this.opts.styleSuffix ?? ".module.css")) continue;
784
- styles.set(st.importClause.name.text, posix.normalize(posix.join(this.dirOf(name), spec)));
1123
+ if (spec.startsWith(".") && !/\.css$/.test(spec) && !/^\.\.\/\w+$/.test(spec)) {
1124
+ const b = st.importClause.namedBindings;
1125
+ const mod = b && ts.isNamedImports(b) ? this.plainModule(posix.normalize(posix.join(this.dirOf(folder), spec))) : void 0;
1126
+ if (mod && b && ts.isNamedImports(b))
1127
+ for (const e of b.elements) {
1128
+ const f = mod[(e.propertyName ?? e.name).text];
1129
+ if (typeof f === "function") imported.set(e.name.text, f);
1130
+ }
1131
+ }
1132
+ if (spec === "lucide-react") {
1133
+ const b = st.importClause.namedBindings;
1134
+ if (b && ts.isNamedImports(b)) for (const e of b.elements) lucide.set(e.name.text, (e.propertyName ?? e.name).text);
1135
+ continue;
1136
+ }
1137
+ if (spec.startsWith("@base-ui/react")) {
1138
+ baseUiModules.add(spec.replace(/^@base-ui\/react\/?/, ""));
1139
+ const b = st.importClause.namedBindings;
1140
+ if (st.importClause.name) baseUi.add(st.importClause.name.text);
1141
+ if (b && ts.isNamedImports(b)) for (const e of b.elements) baseUi.add(e.name.text);
1142
+ continue;
1143
+ }
1144
+ if (!st.importClause.name || !spec.endsWith(this.opts.styleSuffix ?? ".module.css")) continue;
1145
+ styles.set(st.importClause.name.text, posix.normalize(posix.join(this.dirOf(folder), spec)));
785
1146
  }
786
1147
  const aliases = this.aliases(file, sf);
787
1148
  const props = /* @__PURE__ */ new Map();
@@ -801,10 +1162,40 @@ class Reader {
801
1162
  const fns = componentFunctions(sf);
802
1163
  const fn = fns.get(name);
803
1164
  const subcomponents = [...fns].filter(([n]) => n !== name && n.startsWith(name)).map(([n, f]) => ({ name: n, fn: f }));
804
- const src = { name, file, sf, styles, props, native, fn, subcomponents };
1165
+ const src = { name, folder, file, sf, styles, props, native, fn, subcomponents, exported: fns, baseUi, helpers, lucide, consts, baseUiModules, fnConsts, imported };
805
1166
  this.cache.set(name, src);
806
1167
  return src;
807
1168
  }
1169
+ modules = /* @__PURE__ */ new Map();
1170
+ /**
1171
+ * Exports of a plain module of the package (`utils/color.ts`): no imports, no JSX, so it runs on its own. Undefined
1172
+ * for anything else (a module that imports React or another file).
1173
+ */
1174
+ plainModule(path) {
1175
+ if (this.modules.has(path)) return this.modules.get(path);
1176
+ let out;
1177
+ const text = this.opts.read(`${path}.ts`) ?? this.opts.read(`${path}/index.ts`);
1178
+ if (text !== void 0) {
1179
+ const sf = ts.createSourceFile(path, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
1180
+ const plain = !sf.statements.some((s) => ts.isImportDeclaration(s) || ts.isExportDeclaration(s) && !!s.moduleSpecifier);
1181
+ if (plain) {
1182
+ try {
1183
+ const js = ts.transpileModule(text, { compilerOptions: { target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.CommonJS } }).outputText;
1184
+ const exports = {};
1185
+ new Function("exports", "module", js)(exports, { exports });
1186
+ out = exports;
1187
+ } catch {
1188
+ out = void 0;
1189
+ }
1190
+ }
1191
+ }
1192
+ this.modules.set(path, out);
1193
+ return out;
1194
+ }
1195
+ }
1196
+ function skipCasts(e) {
1197
+ while (ts.isParenthesizedExpression(e) || ts.isAsExpression(e) || ts.isSatisfiesExpression(e) || ts.isNonNullExpression(e)) e = e.expression;
1198
+ return e;
808
1199
  }
809
1200
  function classifyType(text, aliases, depth = 0) {
810
1201
  const t = text.trim();
@@ -833,7 +1224,10 @@ function componentFunctions(sf) {
833
1224
  }
834
1225
  const CHILDREN = /* @__PURE__ */ Symbol("children");
835
1226
  const OPAQUE = /* @__PURE__ */ Symbol("opaque");
1227
+ const isFnRef = (v) => !!v && typeof v === "object" && "fnRef" in v;
836
1228
  const isClass = (v) => !!v && typeof v === "object" && "cls" in v;
1229
+ const isTextRef = (v) => !!v && typeof v === "object" && "textProp" in v;
1230
+ const isLucide = (v) => !!v && typeof v === "object" && "lucide" in v;
837
1231
  const isStyles = (v) => !!v && typeof v === "object" && "module" in v;
838
1232
  const isJsx = (v) => !!v && typeof v === "object" && "jsx" in v;
839
1233
  function lookup(scope, name) {
@@ -847,7 +1241,12 @@ function lookup(scope, name) {
847
1241
  }
848
1242
  const style = scope.src.styles.get(name);
849
1243
  if (style) return { module: style };
850
- if (name === "undefined") return void 0;
1244
+ const icon = scope.src.lucide.get(name);
1245
+ if (icon) return { lucide: icon };
1246
+ const top = scope.src.consts.get(name);
1247
+ if (top) return evaluate(top, { ...scope, vars: /* @__PURE__ */ new Map(), locals: /* @__PURE__ */ new Map() });
1248
+ const fnConst = scope.src.fnConsts.get(name);
1249
+ if (fnConst) return { fnRef: fnConst, scope: { ...scope, vars: /* @__PURE__ */ new Map(), locals: /* @__PURE__ */ new Map(), toggles: /* @__PURE__ */ new Map() } };
851
1250
  return void 0;
852
1251
  }
853
1252
  function classList(v, out = []) {
@@ -872,6 +1271,8 @@ function evaluate(e, scope) {
872
1271
  if (ts.isPropertyAccessExpression(e)) {
873
1272
  const obj = evaluate(e.expression, scope);
874
1273
  if (isStyles(obj)) return { cls: `${obj.module}|${e.name.text}` };
1274
+ if ((Array.isArray(obj) || typeof obj === "string") && e.name.text === "length") return obj.length;
1275
+ if (obj === OPAQUE) return OPAQUE;
875
1276
  if (obj && typeof obj === "object" && !Array.isArray(obj) && !isClass(obj) && !isJsx(obj)) return obj[e.name.text];
876
1277
  return void 0;
877
1278
  }
@@ -879,9 +1280,20 @@ function evaluate(e, scope) {
879
1280
  const obj = evaluate(e.expression, scope);
880
1281
  const key = evaluate(e.argumentExpression, scope);
881
1282
  if (isStyles(obj) && typeof key === "string") return { cls: `${obj.module}|${key}` };
1283
+ if (Array.isArray(obj) && typeof key === "number") return obj[key];
1284
+ if (obj && typeof obj === "object" && !Array.isArray(obj) && (typeof key === "string" || typeof key === "number") && !isClass(obj) && !isJsx(obj)) return obj[key];
882
1285
  return void 0;
883
1286
  }
884
1287
  if (ts.isPrefixUnaryExpression(e) && e.operator === ts.SyntaxKind.ExclamationToken) return !truthy(evaluate(e.operand, scope));
1288
+ if (ts.isPrefixUnaryExpression(e) && e.operator === ts.SyntaxKind.MinusToken) {
1289
+ const v = evaluate(e.operand, scope);
1290
+ return typeof v === "number" ? -v : OPAQUE;
1291
+ }
1292
+ if (ts.isTypeOfExpression(e)) {
1293
+ const v = evaluate(e.expression, scope);
1294
+ if (v === OPAQUE || isJsx(v)) return OPAQUE;
1295
+ return v === null ? "object" : Array.isArray(v) ? "object" : typeof v === "symbol" ? OPAQUE : typeof v;
1296
+ }
885
1297
  if (ts.isConditionalExpression(e)) return truthy(evaluate(e.condition, scope)) ? evaluate(e.whenTrue, scope) : evaluate(e.whenFalse, scope);
886
1298
  if (ts.isBinaryExpression(e)) {
887
1299
  const op = e.operatorToken.kind;
@@ -911,17 +1323,273 @@ function evaluate(e, scope) {
911
1323
  return l != r;
912
1324
  // eslint-disable-line eqeqeq
913
1325
  default:
914
- return OPAQUE;
1326
+ return arithmetic(op, l, r);
915
1327
  }
916
1328
  }
917
1329
  if (ts.isCallExpression(e)) {
918
1330
  const callee = e.expression.getText(sf);
919
1331
  if (callee === "cx" || callee === "clsx" || callee === "classNames") return e.arguments.map((a) => evaluate(a, scope)).filter(truthy);
1332
+ if (callee === "mergeProps") {
1333
+ const out = {};
1334
+ for (const a of e.arguments) {
1335
+ const v = evaluate(a, scope);
1336
+ if (v && typeof v === "object" && !Array.isArray(v) && !isJsx(v) && !isClass(v)) Object.assign(out, v);
1337
+ }
1338
+ return out;
1339
+ }
1340
+ const math = /^Math\.(max|min|floor|ceil|round|abs)$/.exec(callee);
1341
+ if (math) {
1342
+ const args = e.arguments.map((a) => evaluate(a, scope));
1343
+ return args.every((a) => typeof a === "number") ? Math[math[1]](...args) : OPAQUE;
1344
+ }
1345
+ const objFn = /^Object\.(entries|keys|values)$/.exec(callee);
1346
+ if (objFn && e.arguments[0]) {
1347
+ const o = evaluate(e.arguments[0], scope);
1348
+ if (o === void 0 || o === null) return [];
1349
+ if (o && typeof o === "object" && !Array.isArray(o) && !isJsx(o) && !isClass(o)) {
1350
+ const rec = o;
1351
+ return objFn[1] === "keys" ? Object.keys(rec) : objFn[1] === "values" ? Object.values(rec) : Object.entries(rec).map(([k, v]) => [k, v]);
1352
+ }
1353
+ return OPAQUE;
1354
+ }
1355
+ if ((callee === "createElement" || callee === "React.createElement") && e.arguments[0]) {
1356
+ const tag = evaluate(e.arguments[0], scope);
1357
+ const props = e.arguments[1] ? evaluate(e.arguments[1], scope) : void 0;
1358
+ if (typeof tag !== "string") return OPAQUE;
1359
+ return { element: tag, props: props && typeof props === "object" && !Array.isArray(props) ? props : {}, kids: e.arguments.slice(2).map((a) => evaluate(a, scope)) };
1360
+ }
1361
+ if ((callee === "createContext" || callee === "React.createContext") && e.arguments[0]) return evaluate(e.arguments[0], scope);
1362
+ if ((callee === "useContext" || callee === "React.useContext") && e.arguments[0]) {
1363
+ const key = e.arguments[0].getText(sf);
1364
+ return scope.contexts?.has(key) ? scope.contexts.get(key) : evaluate(e.arguments[0], scope);
1365
+ }
1366
+ if (callee === "useRender" && e.arguments[0]) {
1367
+ const o = evaluate(e.arguments[0], scope);
1368
+ const props = o?.props && typeof o.props === "object" && !Array.isArray(o.props) ? o.props : {};
1369
+ return { element: typeof o?.defaultTagName === "string" ? o.defaultTagName : "div", props, kids: [] };
1370
+ }
1371
+ if ((callee === "useMemo" || callee === "React.useMemo") && e.arguments[0]) return callArrow(e.arguments[0], [], scope);
1372
+ if (callee === "Array.isArray" && e.arguments[0]) {
1373
+ const v = evaluate(e.arguments[0], scope);
1374
+ return v === OPAQUE || isFnRef(v) ? OPAQUE : Array.isArray(v);
1375
+ }
1376
+ if (callee === "Array.from" && e.arguments[0]) {
1377
+ const v = evaluate(e.arguments[0], scope);
1378
+ const length = v && typeof v === "object" && !Array.isArray(v) ? v.length : void 0;
1379
+ const list = Array.isArray(v) ? v : typeof length === "number" && length >= 0 && length < 1e3 ? Array.from({ length }, () => void 0) : void 0;
1380
+ if (!list) return OPAQUE;
1381
+ const fn = e.arguments[1];
1382
+ if (!fn) return list;
1383
+ const results = list.map((x, i) => callArrow(fn, [x, i], scope));
1384
+ return results.some((r) => r === OPAQUE) ? OPAQUE : results;
1385
+ }
1386
+ if (ts.isPropertyAccessExpression(e.expression) && /^(map|flatMap|filter|some|every|find)$/.test(e.expression.name.text)) {
1387
+ const list = evaluate(e.expression.expression, scope);
1388
+ if (Array.isArray(list)) return listMethod(list, e.expression.name.text, e.arguments, scope);
1389
+ }
1390
+ if (ts.isPropertyAccessExpression(e.expression)) {
1391
+ const obj = evaluate(e.expression.expression, scope);
1392
+ const r = valueMethod(obj, e.expression.name.text, e.arguments.map((a) => evaluate(a, scope)));
1393
+ if (r !== void 0) return r;
1394
+ }
1395
+ if (ts.isIdentifier(e.expression)) {
1396
+ const fnExpr = scope.inits?.get(e.expression.text) ?? scope.locals.get(e.expression.text);
1397
+ if (fnExpr && (ts.isArrowFunction(fnExpr) || ts.isFunctionExpression(fnExpr)))
1398
+ return callArrow(
1399
+ fnExpr,
1400
+ e.arguments.map((a) => evaluate(a, scope)),
1401
+ scope
1402
+ );
1403
+ }
1404
+ const helper = ts.isIdentifier(e.expression) ? scope.src.helpers.get(e.expression.text) : void 0;
1405
+ if (helper) {
1406
+ const args = e.arguments.map((a) => evaluate(a, scope));
1407
+ if (containsJsx(helper)) return callArrow(helper, args, { ...scope, vars: /* @__PURE__ */ new Map(), locals: /* @__PURE__ */ new Map(), toggles: /* @__PURE__ */ new Map(), inits: /* @__PURE__ */ new Map() });
1408
+ return callHelper(helper, args, scope.src);
1409
+ }
1410
+ const imported = ts.isIdentifier(e.expression) ? scope.src.imported.get(e.expression.text) : void 0;
1411
+ if (imported) return callData(imported, e.arguments.map((a) => evaluate(a, scope)));
1412
+ if (ts.isIdentifier(e.expression)) {
1413
+ const v = evaluate(e.expression, scope);
1414
+ if (isFnRef(v)) return callArrow(v.fnRef, e.arguments.map((a) => evaluate(a, scope)), v.scope);
1415
+ }
920
1416
  return OPAQUE;
921
1417
  }
1418
+ if (ts.isArrowFunction(e) || ts.isFunctionExpression(e)) return { fnRef: e, scope };
922
1419
  if (ts.isJsxElement(e) || ts.isJsxSelfClosingElement(e) || ts.isJsxFragment(e)) return { jsx: e, scope };
1420
+ if (ts.isObjectLiteralExpression(e)) {
1421
+ const out = {};
1422
+ for (const p of e.properties) {
1423
+ if (ts.isPropertyAssignment(p)) out[unquote(p.name.getText(sf))] = evaluate(p.initializer, scope);
1424
+ else if (ts.isShorthandPropertyAssignment(p)) out[p.name.text] = lookup(scope, p.name.text);
1425
+ else if (ts.isSpreadAssignment(p)) {
1426
+ const v = evaluate(p.expression, scope);
1427
+ if (v && typeof v === "object" && !Array.isArray(v) && !isJsx(v) && !isClass(v)) Object.assign(out, v);
1428
+ } else return OPAQUE;
1429
+ }
1430
+ return out;
1431
+ }
1432
+ if (ts.isArrayLiteralExpression(e)) return e.elements.map((x) => evaluate(x, scope));
923
1433
  return OPAQUE;
924
1434
  }
1435
+ const isData = (v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean" || v === null || v === void 0 || Array.isArray(v) && v.every(isData) || !!v && typeof v === "object" && !isJsx(v) && !isClass(v) && !isStyles(v) && !isTextRef(v) && !isFnRef(v) && Object.values(v).every(isData);
1436
+ function callData(fn, args) {
1437
+ const data = args.map((a) => isTextRef(a) ? a.text : a);
1438
+ if (!data.every(isData)) return OPAQUE;
1439
+ try {
1440
+ const out = fn(...structuredClone(data));
1441
+ const v = jsonValue(JSON.parse(JSON.stringify(out ?? null)));
1442
+ return isData(v) ? v : OPAQUE;
1443
+ } catch {
1444
+ return OPAQUE;
1445
+ }
1446
+ }
1447
+ function callHelper(fn, args, src) {
1448
+ try {
1449
+ const js = ts.transpileModule(fn.getText(src.sf).replace(/^export\s+(default\s+)?/, ""), { compilerOptions: { target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.None } }).outputText;
1450
+ const run = new Function(`${js}; return ${fn.name.text}.apply(null, arguments);`);
1451
+ return callData(run, args);
1452
+ } catch {
1453
+ return OPAQUE;
1454
+ }
1455
+ }
1456
+ function containsJsx(fn) {
1457
+ let found = false;
1458
+ const visit = (n) => {
1459
+ if (found) return;
1460
+ if (ts.isJsxElement(n) || ts.isJsxSelfClosingElement(n) || ts.isJsxFragment(n)) found = true;
1461
+ else ts.forEachChild(n, visit);
1462
+ };
1463
+ visit(fn);
1464
+ return found;
1465
+ }
1466
+ function valueMethod(obj, method, args) {
1467
+ const str = (i) => typeof args[i] === "string" ? args[i] : void 0;
1468
+ const int = (i) => typeof args[i] === "number" ? args[i] : args[i] === void 0 ? void 0 : NaN;
1469
+ if (typeof obj === "string") {
1470
+ switch (method) {
1471
+ case "split":
1472
+ return str(0) !== void 0 ? obj.split(str(0)) : void 0;
1473
+ case "trim":
1474
+ return obj.trim();
1475
+ case "toLowerCase":
1476
+ return obj.toLowerCase();
1477
+ case "toUpperCase":
1478
+ return obj.toUpperCase();
1479
+ case "startsWith":
1480
+ return str(0) !== void 0 ? obj.startsWith(str(0)) : void 0;
1481
+ case "endsWith":
1482
+ return str(0) !== void 0 ? obj.endsWith(str(0)) : void 0;
1483
+ case "includes":
1484
+ return str(0) !== void 0 ? obj.includes(str(0)) : void 0;
1485
+ case "slice":
1486
+ return Number.isNaN(int(0)) || Number.isNaN(int(1)) ? void 0 : obj.slice(int(0), int(1));
1487
+ default:
1488
+ return void 0;
1489
+ }
1490
+ }
1491
+ if (Array.isArray(obj)) {
1492
+ switch (method) {
1493
+ case "join":
1494
+ return obj.every((x) => typeof x === "string" || typeof x === "number") ? obj.join(str(0) ?? ",") : void 0;
1495
+ case "slice":
1496
+ return Number.isNaN(int(0)) || Number.isNaN(int(1)) ? void 0 : obj.slice(int(0), int(1));
1497
+ case "includes":
1498
+ return obj.includes(args[0]);
1499
+ default:
1500
+ return void 0;
1501
+ }
1502
+ }
1503
+ return void 0;
1504
+ }
1505
+ function callArrow(fn, args, scope) {
1506
+ if (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn) && !ts.isFunctionDeclaration(fn)) return OPAQUE;
1507
+ if (!fn.body) return OPAQUE;
1508
+ const vars = new Map(scope.vars);
1509
+ fn.parameters.forEach((p, i) => {
1510
+ const v = args[i];
1511
+ if (ts.isIdentifier(p.name)) vars.set(p.name.text, v);
1512
+ else if (ts.isObjectBindingPattern(p.name) && v && typeof v === "object") {
1513
+ for (const el of p.name.elements) if (ts.isIdentifier(el.name)) vars.set(el.name.text, v[(el.propertyName ?? el.name).getText(scope.src.sf)]);
1514
+ }
1515
+ const pattern = p.name;
1516
+ if (ts.isArrayBindingPattern(pattern) && Array.isArray(v))
1517
+ pattern.elements.forEach((el, j) => {
1518
+ if (ts.isBindingElement(el) && ts.isIdentifier(el.name)) vars.set(el.name.text, v[j]);
1519
+ });
1520
+ });
1521
+ const inner = { ...scope, vars, locals: new Map(scope.locals) };
1522
+ if (!ts.isBlock(fn.body)) return evaluate(fn.body, inner);
1523
+ const run = (stmts) => {
1524
+ for (const st of stmts) {
1525
+ if (ts.isVariableStatement(st)) {
1526
+ for (const d of st.declarationList.declarations) if (ts.isIdentifier(d.name) && d.initializer) inner.locals.set(d.name.text, d.initializer);
1527
+ }
1528
+ if (ts.isIfStatement(st)) {
1529
+ const c = evaluate(st.expression, inner);
1530
+ if (c === OPAQUE) return { value: OPAQUE };
1531
+ const branch = truthy(c) ? st.thenStatement : st.elseStatement;
1532
+ const r = branch ? run(ts.isBlock(branch) ? branch.statements : [branch]) : void 0;
1533
+ if (r) return r;
1534
+ }
1535
+ if (ts.isForOfStatement(st) || ts.isForStatement(st) || ts.isWhileStatement(st)) return { value: OPAQUE };
1536
+ if (ts.isReturnStatement(st)) return { value: st.expression ? evaluate(st.expression, inner) : void 0 };
1537
+ }
1538
+ return void 0;
1539
+ };
1540
+ return run(fn.body.statements)?.value;
1541
+ }
1542
+ function listMethod(list, method, args, scope) {
1543
+ let fn = args[0];
1544
+ if (fn && ts.isIdentifier(fn)) fn = scope.inits?.get(fn.text) ?? scope.locals.get(fn.text) ?? fn;
1545
+ if (!fn) return OPAQUE;
1546
+ const results = list.map((x, i) => callArrow(fn, [x, i], scope));
1547
+ if (results.some((r) => r === OPAQUE)) return OPAQUE;
1548
+ switch (method) {
1549
+ case "map":
1550
+ return results;
1551
+ case "flatMap":
1552
+ return results.flatMap((r) => Array.isArray(r) ? r : [r]);
1553
+ case "filter":
1554
+ return list.filter((_, i) => truthy(results[i]));
1555
+ case "some":
1556
+ return results.some(truthy);
1557
+ case "every":
1558
+ return results.every(truthy);
1559
+ case "find":
1560
+ return list.find((_, i) => truthy(results[i]));
1561
+ default:
1562
+ return OPAQUE;
1563
+ }
1564
+ }
1565
+ function arithmetic(op, l, r) {
1566
+ if (l === OPAQUE || r === OPAQUE) return OPAQUE;
1567
+ const K = ts.SyntaxKind;
1568
+ const compare = op === K.LessThanToken || op === K.GreaterThanToken || op === K.LessThanEqualsToken || op === K.GreaterThanEqualsToken;
1569
+ if (compare && (l === void 0 || l === null || r === void 0 || r === null)) return false;
1570
+ if (op === K.PlusToken && (typeof l === "string" || typeof r === "string") && (typeof l === "string" || typeof l === "number") && (typeof r === "string" || typeof r === "number")) return `${l}${r}`;
1571
+ if (typeof l !== "number" || typeof r !== "number") return OPAQUE;
1572
+ switch (op) {
1573
+ case K.PlusToken:
1574
+ return l + r;
1575
+ case K.MinusToken:
1576
+ return l - r;
1577
+ case K.AsteriskToken:
1578
+ return l * r;
1579
+ case K.SlashToken:
1580
+ return l / r;
1581
+ case K.LessThanToken:
1582
+ return l < r;
1583
+ case K.GreaterThanToken:
1584
+ return l > r;
1585
+ case K.LessThanEqualsToken:
1586
+ return l <= r;
1587
+ case K.GreaterThanEqualsToken:
1588
+ return l >= r;
1589
+ default:
1590
+ return OPAQUE;
1591
+ }
1592
+ }
925
1593
  function truthy(v) {
926
1594
  if (v === OPAQUE) return true;
927
1595
  if (Array.isArray(v)) return true;
@@ -931,14 +1599,19 @@ function bindProps(fn, scope, given) {
931
1599
  const names = /* @__PURE__ */ new Map();
932
1600
  const param = fn.parameters[0];
933
1601
  if (!param || !ts.isObjectBindingPattern(param.name)) return names;
1602
+ const taken = /* @__PURE__ */ new Set();
934
1603
  for (const el of param.name.elements) {
935
1604
  if (el.dotDotDotToken || !ts.isIdentifier(el.name)) continue;
936
1605
  const prop = el.propertyName ? el.propertyName.getText(scope.src.sf) : el.name.text;
937
1606
  names.set(el.name.text, prop);
1607
+ taken.add(prop);
938
1608
  let v = given[prop];
939
1609
  if (v === void 0 && el.initializer) v = evaluate(el.initializer, scope);
940
1610
  scope.vars.set(el.name.text, v);
941
1611
  }
1612
+ const rest = param.name.elements.find((el) => el.dotDotDotToken && ts.isIdentifier(el.name));
1613
+ const keepChildren = !scope.main && !!scope.preview?.some((c) => typeof c !== "string");
1614
+ if (rest && ts.isIdentifier(rest.name)) scope.vars.set(rest.name.text, Object.fromEntries(Object.entries(given).filter(([k, v]) => !taken.has(k) && v !== void 0 && (v !== CHILDREN || keepChildren))));
942
1615
  return names;
943
1616
  }
944
1617
  function renderedJsx(fn, scope) {
@@ -951,7 +1624,31 @@ function renderedJsx(fn, scope) {
951
1624
  const run = (stmts) => {
952
1625
  for (const st of stmts) {
953
1626
  if (ts.isVariableStatement(st)) {
954
- for (const d of st.declarationList.declarations) if (ts.isIdentifier(d.name) && d.initializer) scope.locals.set(d.name.text, d.initializer);
1627
+ for (const d of st.declarationList.declarations) {
1628
+ if (ts.isArrayBindingPattern(d.name) && d.initializer && ts.isCallExpression(d.initializer) && /^(React\.)?useState$/.test(d.initializer.expression.getText(scope.src.sf))) {
1629
+ const first = d.name.elements[0];
1630
+ const init = d.initializer.arguments[0];
1631
+ if (first && ts.isBindingElement(first) && ts.isIdentifier(first.name) && init) {
1632
+ scope.locals.set(first.name.text, init);
1633
+ scope.inits?.set(first.name.text, init);
1634
+ }
1635
+ continue;
1636
+ }
1637
+ if (ts.isObjectBindingPattern(d.name) && d.initializer) {
1638
+ const obj = evaluate(d.initializer, scope);
1639
+ for (const el of d.name.elements) {
1640
+ if (!ts.isIdentifier(el.name) || el.dotDotDotToken) continue;
1641
+ const key = (el.propertyName ?? el.name).getText(scope.src.sf);
1642
+ let v = obj && typeof obj === "object" && !Array.isArray(obj) ? obj[key] : void 0;
1643
+ if (v === void 0 && el.initializer) v = evaluate(el.initializer, scope);
1644
+ scope.vars.set(el.name.text, v);
1645
+ }
1646
+ continue;
1647
+ }
1648
+ if (!ts.isIdentifier(d.name) || !d.initializer) continue;
1649
+ scope.locals.set(d.name.text, d.initializer);
1650
+ scope.inits?.set(d.name.text, d.initializer);
1651
+ }
955
1652
  } else if (ts.isIfStatement(st)) {
956
1653
  const branch = truthy(evaluate(st.expression, scope)) ? st.thenStatement : st.elseStatement;
957
1654
  if (!branch) continue;
@@ -968,10 +1665,17 @@ function renderedJsx(fn, scope) {
968
1665
  return run(body.statements) ?? void 0;
969
1666
  }
970
1667
  const INPUT_TAGS = /* @__PURE__ */ new Set(["input", "textarea", "BaseInput"]);
1668
+ const SVG_SHAPES = /^(circle|path|rect|line|polyline|polygon|ellipse)$/;
1669
+ const isInputTag = (tag) => INPUT_TAGS.has(tag) || /\.Input$/.test(tag);
971
1670
  const sentenceCase = (prop) => {
972
1671
  const words = prop.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase();
973
1672
  return words.charAt(0).toUpperCase() + words.slice(1);
974
1673
  };
1674
+ function attrString(v) {
1675
+ if (v === void 0 || v === null || v === OPAQUE || typeof v === "symbol") return void 0;
1676
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") return String(v);
1677
+ return void 0;
1678
+ }
975
1679
  function renderJsx(ref, ctx, visible) {
976
1680
  const { jsx, scope } = ref;
977
1681
  const sf = scope.src.sf;
@@ -980,11 +1684,29 @@ function renderJsx(ref, ctx, visible) {
980
1684
  if (!ts.isJsxElement(jsx) && !ts.isJsxSelfClosingElement(jsx)) return [];
981
1685
  const open = ts.isJsxElement(jsx) ? jsx.openingElement : jsx;
982
1686
  const tag = open.tagName.getText(sf);
1687
+ if (tag === "wbr") return [];
1688
+ if (/\.Provider$/.test(tag) && ts.isJsxElement(jsx)) {
1689
+ const valueAttr = open.attributes.properties.find((a) => ts.isJsxAttribute(a) && a.name.getText(sf) === "value");
1690
+ const value = valueAttr?.initializer && ts.isJsxExpression(valueAttr.initializer) && valueAttr.initializer.expression ? evaluate(valueAttr.initializer.expression, scope) : void 0;
1691
+ const contexts = new Map(ctx.contexts ?? []);
1692
+ contexts.set(tag.replace(/\.Provider$/, ""), value);
1693
+ const inner = { ...scope, contexts };
1694
+ return renderChildren(jsx.children, inner, { ...ctx, contexts });
1695
+ }
1696
+ if (/^(React\.)?Fragment$|\.Provider$/.test(tag)) return ts.isJsxElement(jsx) ? renderChildren(jsx.children, scope, ctx) : [];
983
1697
  const attrs = {};
984
1698
  let spreads = false;
1699
+ let spreadChildren;
985
1700
  for (const a of open.attributes.properties) {
986
1701
  if (ts.isJsxSpreadAttribute(a)) {
987
1702
  spreads = true;
1703
+ const v = evaluate(a.expression, scope);
1704
+ if (v && typeof v === "object" && !Array.isArray(v) && !isJsx(v) && !isClass(v) && !isElementValue(v) && !isFnRef(v)) {
1705
+ for (const [k, x] of Object.entries(v)) {
1706
+ if (k === "children") spreadChildren = x;
1707
+ else if (/^(data|aria)-/.test(k) || SPREAD_PROPS.has(k)) attrs[k] = x;
1708
+ }
1709
+ }
988
1710
  continue;
989
1711
  }
990
1712
  const name = a.name.getText(sf);
@@ -993,36 +1715,137 @@ function renderJsx(ref, ctx, visible) {
993
1715
  else if (ts.isJsxExpression(a.initializer) && a.initializer.expression) attrs[name] = evaluate(a.initializer.expression, scope);
994
1716
  }
995
1717
  const children = ts.isJsxElement(jsx) ? jsx.children : void 0;
1718
+ if (scope.src.baseUi.has(tag.split(".")[0]) && (/\.Empty$/.test(tag) && ctx.items?.length || /\.Clear$/.test(tag) && ctx.selected === void 0 || /\.Arrow$/.test(tag) || /\.Error$/.test(tag) && attrs.match === void 0)) return [];
1719
+ if (scope.src.baseUi.has(tag.split(".")[0]) && attrs.render !== void 0 && !children?.some((c) => !ts.isJsxText(c) || c.text.trim())) {
1720
+ const r = attrs.render;
1721
+ const extra = classList(attrs.className);
1722
+ if (isJsx(r)) {
1723
+ const nodes = renderJsx(r, ctx, visible);
1724
+ if (nodes[0]) nodes[0].classes = [...nodes[0].classes, ...extra];
1725
+ return nodes;
1726
+ }
1727
+ const init = open.attributes.properties.find((a) => ts.isJsxAttribute(a) && a.name.getText(sf) === "render")?.initializer;
1728
+ if (r === CHILDREN && init && ts.isJsxExpression(init) && init.expression) return renderExpression(init.expression, scope, ctx, visible);
1729
+ }
996
1730
  if (/^[A-Z]/.test(tag) && tag !== ctx.component && ctx.depth < 4 && ctx.reader.exists(tag)) {
997
- const nested = renderComponent(ctx.reader, tag, attrs, { ...ctx, depth: ctx.depth + 1 });
1731
+ const nested = renderComponent(ctx.reader, tag, withFormError(tag, children?.length ? { ...attrs, children: childrenValue(children, scope) } : attrs, ctx), { ...ctx, depth: ctx.depth + 1 });
998
1732
  if (nested) {
999
- if (tag === "Icon") nested.kind = "icon";
1733
+ if (tag === ICON_COMPONENT) {
1734
+ nested.kind = "icon";
1735
+ const iconAttr = open.attributes.properties.find((a) => ts.isJsxAttribute(a) && a.name.getText(sf) === "icon");
1736
+ const expr = iconAttr?.initializer && ts.isJsxExpression(iconAttr.initializer) ? iconAttr.initializer.expression : void 0;
1737
+ const lucideName = expr && ts.isIdentifier(expr) ? scope.src.lucide.get(expr.text) : void 0;
1738
+ if (lucideName) nested.icon = lucideName;
1739
+ else if (isLucide(attrs.icon)) nested.icon = attrs.icon.lucide;
1740
+ else if (typeof attrs.icon === "string" && /^[A-Z]\w*$/.test(attrs.icon)) nested.icon = attrs.icon;
1741
+ }
1000
1742
  if (visible) nested.visible = visible;
1001
1743
  return [nested];
1002
1744
  }
1003
1745
  }
1746
+ const dataAttrs = /* @__PURE__ */ new Map();
1747
+ for (const [k, v] of Object.entries(attrs)) {
1748
+ if (!/^(data|aria)-/.test(k)) continue;
1749
+ const str = attrString(v);
1750
+ if (str !== void 0) dataAttrs.set(k, str);
1751
+ }
1752
+ const isBaseUi = scope.src.baseUi.has(tag.split(".")[0]);
1753
+ const orientation = isBaseUi && typeof attrs.orientation === "string" ? attrs.orientation : ctx.orientation;
1754
+ if (isBaseUi && orientation && !dataAttrs.has("data-orientation")) dataAttrs.set("data-orientation", orientation);
1004
1755
  const node = {
1005
1756
  key: "",
1006
1757
  tag,
1007
1758
  classes: classList(attrs.className),
1008
1759
  children: [],
1009
- kind: INPUT_TAGS.has(tag) ? "text" : "frame",
1010
- ...visible ? { visible } : {}
1760
+ kind: "frame",
1761
+ ...visible ? { visible } : {},
1762
+ ...scope.src.baseUi.has(tag.split(".")[0]) ? ctx.selected !== void 0 && typeof attrs.value === "string" ? { baseUi: true, checked: attrs.value === ctx.selected ? "true" : "false", selectedBy: true } : { baseUi: true, checked: scope.main ? "state" : scope.checked ?? "false" } : {},
1763
+ ...dataAttrs.size ? { attrs: dataAttrs } : {},
1764
+ .../(^img$|\.Image$)/.test(tag) ? { media: true } : {},
1765
+ // Base UI indicators are only mounted while their item is checked, unless keepMounted.
1766
+ .../\.(Item)?Indicator$/.test(tag) && scope.src.baseUi.has(tag.split(".")[0]) && attrs.keepMounted !== true && ctx.fraction === void 0 ? { whenChecked: true } : {}
1011
1767
  };
1012
- if (INPUT_TAGS.has(tag)) {
1768
+ if (isBaseUi && /\.Panel$/.test(tag) && node.selectedBy && node.checked === "false") node.inactive = true;
1769
+ if (isBaseUi && /\.Thumb$/.test(tag) && ctx.fraction !== void 0) node.at = ctx.fraction;
1770
+ const bg = attrs.style && typeof attrs.style === "object" && !Array.isArray(attrs.style) ? attrs.style.backgroundColor : void 0;
1771
+ if (typeof bg === "string") node.inlineFill = bg;
1772
+ const style = attrs.style;
1773
+ if (style && typeof style === "object" && !Array.isArray(style) && !isJsx(style)) {
1774
+ const vars = /* @__PURE__ */ new Map();
1775
+ for (const [k, v] of Object.entries(style)) if (k.startsWith("--") && (typeof v === "number" || typeof v === "string")) vars.set(k, String(v));
1776
+ if (vars.size) node.styleVars = vars;
1777
+ }
1778
+ if (tag === "svg") {
1779
+ const lit = {};
1780
+ for (const [k, v] of Object.entries(attrs)) if ((typeof v === "string" || typeof v === "number") && k !== "className") lit[k] = String(v);
1781
+ node.svgAttrs = lit;
1782
+ node.shapes = children ? renderChildren(children, scope, ctx).filter((c) => SVG_SHAPES.test(c.tag)) : [];
1783
+ return [node];
1784
+ }
1785
+ if (SVG_SHAPES.test(tag)) {
1786
+ const lit = {};
1787
+ for (const [k, v] of Object.entries(attrs)) if ((typeof v === "string" || typeof v === "number") && k !== "className") lit[k] = String(v);
1788
+ node.svgAttrs = lit;
1789
+ return [node];
1790
+ }
1791
+ if (isInputTag(tag)) {
1013
1792
  node.inputLike = true;
1014
- node.placeholder = true;
1015
- node.text = { property: "Placeholder", default: typeof attrs.placeholder === "string" ? attrs.placeholder : "Placeholder" };
1793
+ const typed = typeof attrs.value === "string" && attrs.value ? attrs.value : typeof attrs.defaultValue === "string" && attrs.defaultValue ? attrs.defaultValue : void 0;
1794
+ if (typed !== void 0 && !scope.main) {
1795
+ node.children = [textNode(typed)];
1796
+ return [node];
1797
+ }
1798
+ const value = typeof attrs.placeholder === "string" ? attrs.placeholder : "Placeholder";
1799
+ node.children = [{ key: "", tag: "#placeholder", classes: [], children: [], kind: "text", placeholder: true, text: { property: "Placeholder", default: value } }];
1016
1800
  return [node];
1017
1801
  }
1018
- if (children) node.children = renderChildren(children, scope, ctx);
1802
+ if (node.baseUi && /\.Indicator$/.test(tag) && ctx.fraction !== void 0) node.fraction = ctx.fraction;
1803
+ let childCtx = Array.isArray(attrs.items) && node.baseUi ? { ...ctx, items: attrs.items } : ctx;
1804
+ if (isBaseUi && orientation) childCtx = { ...childCtx, orientation };
1805
+ if (isBaseUi && attrs.errors && typeof attrs.errors === "object" && !Array.isArray(attrs.errors) && !isFnRef(attrs.errors)) childCtx = { ...childCtx, formErrors: attrs.errors };
1806
+ const numeric = typeof attrs.value === "number" ? attrs.value : attrs.value === void 0 && typeof attrs.defaultValue === "number" ? attrs.defaultValue : void 0;
1807
+ if (node.baseUi && /\.Root$/.test(tag) && numeric !== void 0) {
1808
+ const min = typeof attrs.min === "number" ? attrs.min : 0;
1809
+ const max = typeof attrs.max === "number" ? attrs.max : 100;
1810
+ const local = tag.split(".")[0].toLowerCase();
1811
+ const module = [...scope.src.baseUiModules].find((m) => m && local.includes(m.split("/")[0])) ?? "";
1812
+ childCtx = { ...childCtx, fraction: Math.min(1, Math.max(0, (numeric - min) / (max - min || 1))), numeric: { value: numeric, module } };
1813
+ }
1814
+ const formatted = ctx.fraction !== void 0 && ctx.numeric ? ctx.numeric.module === "slider" ? new Intl.NumberFormat("en-US").format(ctx.numeric.value) : `${Math.round(ctx.fraction * 100)}%` : void 0;
1815
+ if (node.baseUi && /\.Value$/.test(tag) && formatted !== void 0 && ctx.numeric) childCtx = { ...childCtx, valueArgs: ctx.numeric.module === "slider" ? [[formatted], [ctx.numeric.value]] : [formatted, ctx.numeric.value] };
1816
+ if (children) node.children = renderChildren(children, scope, childCtx);
1817
+ else if (spreadChildren === CHILDREN && scope.preview?.some((c) => typeof c !== "string")) node.children = renderPreview(scope.preview, childCtx);
1818
+ else if (spreadChildren !== void 0 && spreadChildren !== CHILDREN) node.children = renderValues([spreadChildren], ctx);
1019
1819
  else if (spreads) node.takesChildren = true;
1820
+ if (!node.children.length && node.baseUi && /\.Value$/.test(tag)) {
1821
+ const placeholder = formatted ?? (ctx.fraction !== void 0 ? `${Math.round(ctx.fraction * 100)}%` : typeof attrs.placeholder === "string" ? attrs.placeholder : "Value");
1822
+ node.children = [{ key: "", tag: "#text", classes: [], children: [], kind: "text", text: { property: "Value", default: placeholder } }];
1823
+ }
1020
1824
  return [node];
1021
1825
  }
1826
+ const SPREAD_PROPS = /* @__PURE__ */ new Set(["className", "value", "defaultValue", "min", "max", "orientation", "errors"]);
1827
+ function withFormError(tag, props, ctx) {
1828
+ const name = props.name;
1829
+ if (!ctx.formErrors || typeof name !== "string" || props.error !== void 0 || !ctx.reader.load(tag)?.props.has("error")) return props;
1830
+ const message = ctx.formErrors[name];
1831
+ const text = Array.isArray(message) ? message[0] : message;
1832
+ return typeof text === "string" ? { ...props, error: text } : props;
1833
+ }
1834
+ const isChildrenRef = (v) => !!v && typeof v === "object" && "children" in v && "scope" in v;
1835
+ function childrenValue(children, scope) {
1836
+ const texts = children.filter((c) => !ts.isJsxText(c) || c.text.trim());
1837
+ if (texts.every((c) => ts.isJsxText(c))) return texts.map((c) => c.text.trim()).join(" ");
1838
+ return { children, scope };
1839
+ }
1022
1840
  function renderChildren(children, scope, ctx) {
1023
1841
  const out = [];
1024
1842
  for (const c of children) {
1025
- if (ts.isJsxText(c)) continue;
1843
+ if (ts.isJsxText(c)) {
1844
+ const raw = jsxText(c.text);
1845
+ if (raw.trim()) out.push(textNode(raw.replace(/\s+/g, " ").trim(), raw));
1846
+ else if (raw) out.push(spaceNode(raw));
1847
+ continue;
1848
+ }
1026
1849
  if (ts.isJsxExpression(c)) {
1027
1850
  if (c.expression) out.push(...renderExpression(c.expression, scope, ctx));
1028
1851
  continue;
@@ -1031,66 +1854,394 @@ function renderChildren(children, scope, ctx) {
1031
1854
  }
1032
1855
  return out;
1033
1856
  }
1034
- function toggleOf(e, scope) {
1035
- const inner = ts.isParenthesizedExpression(e) ? e.expression : e;
1036
- if (!ts.isIdentifier(inner)) return void 0;
1037
- const prop = scope.toggles.get(inner.text);
1038
- return prop ? { property: sentenceCase(prop), prop } : void 0;
1857
+ const spaceNode = (raw) => ({ key: "", tag: "#text", classes: [], children: [], kind: "text", chars: "", raw, space: true });
1858
+ const textNode = (chars, raw) => ({ key: "", tag: "#text", classes: [], children: [], kind: "text", chars, ...raw !== void 0 && raw !== chars ? { raw } : {} });
1859
+ function jsxText(text) {
1860
+ const lines = text.split(/\r\n|\n|\r/);
1861
+ let out = "";
1862
+ lines.forEach((line, i) => {
1863
+ let l = line.replace(/\t/g, " ");
1864
+ if (i !== 0) l = l.replace(/^ +/, "");
1865
+ if (i !== lines.length - 1) l = l.replace(/ +$/, "");
1866
+ if (!l) return;
1867
+ if (out && i !== 0) out += " ";
1868
+ out += l;
1869
+ });
1870
+ return out;
1871
+ }
1872
+ function identifiersIn(e, scope, seen = /* @__PURE__ */ new Set()) {
1873
+ const visit = (n) => {
1874
+ if (ts.isIdentifier(n) && !seen.has(n.text)) {
1875
+ seen.add(n.text);
1876
+ const init = scope.inits?.get(n.text);
1877
+ if (init) identifiersIn(init, scope, seen);
1878
+ }
1879
+ if (ts.isPropertyAccessExpression(n)) return visit(n.expression);
1880
+ ts.forEachChild(n, visit);
1881
+ };
1882
+ visit(e);
1883
+ return seen;
1884
+ }
1885
+ function toggledScope(scope, local) {
1886
+ const vars = new Map(scope.vars);
1887
+ for (const k of scope.inits?.keys() ?? []) vars.delete(k);
1888
+ vars.set(local, scope.toggleValues?.get(local) ?? true);
1889
+ return { ...scope, vars, locals: new Map(scope.inits ?? []) };
1890
+ }
1891
+ function toggleFor(cond, scope) {
1892
+ if (!scope.toggles.size) return void 0;
1893
+ const ids = identifiersIn(cond, scope);
1894
+ const local = [...scope.toggles.keys()].find((k) => ids.has(k));
1895
+ if (!local) return void 0;
1896
+ const prop = scope.toggles.get(local);
1897
+ const inner = toggledScope(scope, local);
1898
+ if (!truthy(evaluate(cond, inner))) return void 0;
1899
+ const off = { ...scope, vars: new Map(scope.vars), locals: new Map(scope.inits ?? []) };
1900
+ for (const k of scope.inits?.keys() ?? []) off.vars.delete(k);
1901
+ if (truthy(evaluate(cond, off))) return void 0;
1902
+ const kind = scope.src.props.get(prop)?.kind;
1903
+ const property = kind === "icon" ? sentenceCase(prop) : `Show ${sentenceCase(prop).toLowerCase()}`;
1904
+ return { visible: { property, prop, ...scope.toggleOn?.has(prop) ? { on: true } : {} }, scope: inner };
1905
+ }
1906
+ function nameToggle(visible, nodes, scope) {
1907
+ const cls = nodes[0]?.classes[0]?.split("|")[1];
1908
+ if (scope.src.props.get(visible.prop)?.kind === "string" && cls) visible.property = `Show ${sentenceCase(cls).toLowerCase()}`;
1909
+ return nodes;
1039
1910
  }
1040
1911
  function renderExpression(e, scope, ctx, visible) {
1041
1912
  if (ts.isParenthesizedExpression(e)) return renderExpression(e.expression, scope, ctx, visible);
1042
1913
  if (ts.isBinaryExpression(e) && e.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
1043
- const t = toggleOf(e.left, scope);
1044
- if (t) return renderExpression(e.right, scope, ctx, visible ?? t);
1914
+ const t = visible ? void 0 : toggleFor(e.left, scope);
1915
+ if (t) return nameToggle(t.visible, renderExpression(e.right, t.scope, ctx, t.visible), scope);
1045
1916
  return truthy(evaluate(e.left, scope)) ? renderExpression(e.right, scope, ctx, visible) : [];
1046
1917
  }
1047
1918
  if (ts.isConditionalExpression(e)) {
1048
- const t = toggleOf(e.condition, scope);
1049
- if (t) return renderExpression(e.whenTrue, scope, ctx, visible ?? t);
1919
+ const t = visible || truthy(evaluate(e.whenFalse, scope)) ? void 0 : toggleFor(e.condition, scope);
1920
+ if (t) return nameToggle(t.visible, renderExpression(e.whenTrue, t.scope, ctx, t.visible), scope);
1050
1921
  return renderExpression(truthy(evaluate(e.condition, scope)) ? e.whenTrue : e.whenFalse, scope, ctx, visible);
1051
1922
  }
1052
1923
  if (ts.isJsxElement(e) || ts.isJsxSelfClosingElement(e) || ts.isJsxFragment(e)) return renderJsx({ jsx: e, scope }, ctx, visible);
1053
1924
  const v = evaluate(e, scope);
1054
- if (v === CHILDREN) return [{ key: "", tag: "#text", classes: [], children: [], kind: "text", text: { property: "Label", default: ctx.component, prop: "children" } }];
1925
+ if (v === CHILDREN) {
1926
+ if (scope.preview?.some((c) => typeof c !== "string")) return renderPreview(scope.preview, ctx);
1927
+ return [{ key: "", tag: "#text", classes: [], children: [], kind: "text", text: { property: "Label", default: ctx.component, prop: "children" } }];
1928
+ }
1929
+ if (isTextRef(v)) {
1930
+ const property = v.textProp === "children" ? "Label" : sentenceCase(v.textProp);
1931
+ return [{ key: "", tag: "#text", classes: [], children: [], kind: "text", text: { property, default: v.text, prop: v.textProp }, ...visible ? { visible } : {} }];
1932
+ }
1933
+ if (isChildrenRef(v)) return renderChildren(v.children, v.scope, ctx);
1934
+ if (typeof v === "string" && v.trim()) return [{ ...textNode(v.trim(), v), ...visible ? { visible } : {} }];
1935
+ if (typeof v === "string" && v) return [spaceNode(v)];
1936
+ if (typeof v === "number") return [textNode(String(v))];
1055
1937
  if (isJsx(v)) return renderJsx(v, ctx, visible);
1938
+ if (Array.isArray(v) || isElementValue(v)) return renderValues([v], ctx);
1939
+ if (v === OPAQUE || isFnRef(v)) {
1940
+ const mapped = renderMap(e, scope, ctx);
1941
+ if (mapped) return mapped;
1942
+ const fn = functionOf(e, scope);
1943
+ if (fn && ctx.items) return renderValues(ctx.items.map((item) => callArrow(fn, [item], scope)), ctx);
1944
+ if (fn && ctx.valueArgs) {
1945
+ const out = callArrow(fn, ctx.valueArgs, scope);
1946
+ if (out !== OPAQUE) return renderValues([out], ctx);
1947
+ }
1948
+ ctx.blockers?.push("shows content computed while the page runs");
1949
+ }
1056
1950
  return [];
1057
1951
  }
1058
- function renderComponent(reader, name, props, ctx, fnOverride) {
1952
+ function functionOf(e, scope) {
1953
+ if (ts.isParenthesizedExpression(e)) return functionOf(e.expression, scope);
1954
+ if (ts.isArrowFunction(e) || ts.isFunctionExpression(e)) return e;
1955
+ if (ts.isIdentifier(e)) {
1956
+ const init = scope.inits?.get(e.text) ?? scope.locals.get(e.text);
1957
+ return init ? functionOf(init, scope) : void 0;
1958
+ }
1959
+ if (ts.isConditionalExpression(e)) {
1960
+ const c = evaluate(e.condition, scope);
1961
+ return c === OPAQUE ? void 0 : functionOf(truthy(c) ? e.whenTrue : e.whenFalse, scope);
1962
+ }
1963
+ return void 0;
1964
+ }
1965
+ const isElementValue = (v) => !!v && typeof v === "object" && "element" in v && "kids" in v;
1966
+ function elementNode(el, ctx) {
1967
+ const node = { key: "", tag: el.element, classes: classList(el.props.className), children: [], kind: "frame" };
1968
+ const kids = el.kids.length ? el.kids : el.props.children !== void 0 ? [el.props.children] : [];
1969
+ for (const k of kids) {
1970
+ if (k === CHILDREN) node.children.push({ key: "", tag: "#text", classes: [], children: [], kind: "text", text: { property: "Label", default: ctx.component, prop: "children" } });
1971
+ else if (isTextRef(k)) node.children.push({ key: "", tag: "#text", classes: [], children: [], kind: "text", text: { property: k.textProp === "children" ? "Label" : sentenceCase(k.textProp), default: k.text, prop: k.textProp } });
1972
+ else node.children.push(...renderValues([k], ctx));
1973
+ }
1974
+ return node;
1975
+ }
1976
+ function renderValues(list, ctx) {
1977
+ const out = [];
1978
+ for (const v of list) {
1979
+ if (isElementValue(v)) out.push(elementNode(v, ctx));
1980
+ else if (isChildrenRef(v)) out.push(...renderChildren(v.children, v.scope, ctx));
1981
+ else if (isTextRef(v)) out.push({ key: "", tag: "#text", classes: [], children: [], kind: "text", text: { property: v.textProp === "children" ? "Label" : sentenceCase(v.textProp), default: v.text, prop: v.textProp } });
1982
+ else if (isJsx(v)) out.push(...renderJsx(v, ctx));
1983
+ else if (Array.isArray(v)) out.push(...renderValues(v, ctx));
1984
+ else if (typeof v === "string" && v.trim() || typeof v === "number") out.push(textNode(String(v).trim(), String(v)));
1985
+ else if (typeof v === "string" && v) out.push(spaceNode(v));
1986
+ else if (v === OPAQUE) ctx.blockers?.push("shows content computed while the page runs");
1987
+ }
1988
+ return out;
1989
+ }
1990
+ function renderMap(e, scope, ctx) {
1991
+ if (!ts.isCallExpression(e) || !ts.isPropertyAccessExpression(e.expression) || e.expression.name.text !== "map") return void 0;
1992
+ const list = evaluate(e.expression.expression, scope);
1993
+ const fn = e.arguments[0];
1994
+ if (!Array.isArray(list) || !fn || !(ts.isArrowFunction(fn) || ts.isFunctionExpression(fn))) return void 0;
1995
+ const param = fn.parameters[0];
1996
+ const out = [];
1997
+ list.forEach((item, i) => {
1998
+ const vars = new Map(scope.vars);
1999
+ if (param && ts.isIdentifier(param.name)) vars.set(param.name.text, item);
2000
+ else if (param && ts.isObjectBindingPattern(param.name) && item && typeof item === "object") {
2001
+ for (const el of param.name.elements) if (ts.isIdentifier(el.name)) vars.set(el.name.text, item[(el.propertyName ?? el.name).getText(scope.src.sf)]);
2002
+ }
2003
+ const index = fn.parameters[1];
2004
+ if (index && ts.isIdentifier(index.name)) vars.set(index.name.text, i);
2005
+ const inner = { ...scope, vars, locals: new Map(scope.locals), inits: new Map(scope.inits ?? []) };
2006
+ const body = fn.body;
2007
+ if (ts.isBlock(body)) {
2008
+ for (const st of body.statements) {
2009
+ if (ts.isVariableStatement(st))
2010
+ for (const d of st.declarationList.declarations) {
2011
+ if (!ts.isIdentifier(d.name) || !d.initializer) continue;
2012
+ inner.locals.set(d.name.text, d.initializer);
2013
+ inner.inits.set(d.name.text, d.initializer);
2014
+ }
2015
+ if (ts.isReturnStatement(st) && st.expression) {
2016
+ out.push(...renderExpression(st.expression, inner, ctx));
2017
+ break;
2018
+ }
2019
+ }
2020
+ } else out.push(...renderExpression(body, inner, ctx));
2021
+ });
2022
+ return out;
2023
+ }
2024
+ function previewElement(v, name) {
2025
+ if (Array.isArray(v)) {
2026
+ for (const x of v) {
2027
+ const hit = previewElement(x, name);
2028
+ if (hit) return hit;
2029
+ }
2030
+ return void 0;
2031
+ }
2032
+ if (!v || typeof v !== "object") return void 0;
2033
+ const n = v;
2034
+ if (n.c === name) return n;
2035
+ return previewElement(n.children, name);
2036
+ }
2037
+ function storyElement(sf, name) {
2038
+ if (!sf) return void 0;
2039
+ const lucide = /* @__PURE__ */ new Set();
2040
+ for (const st of sf.statements) {
2041
+ if (!ts.isImportDeclaration(st) || unquote(st.moduleSpecifier.getText(sf)) !== "lucide-react") continue;
2042
+ const b = st.importClause?.namedBindings;
2043
+ if (b && ts.isNamedImports(b)) for (const e of b.elements) lucide.add(e.name.text);
2044
+ }
2045
+ const convert = (n) => {
2046
+ const open = ts.isJsxElement(n) ? n.openingElement : n;
2047
+ const p = {};
2048
+ for (const a of open.attributes.properties) {
2049
+ if (!ts.isJsxAttribute(a)) continue;
2050
+ const key = a.name.getText(sf);
2051
+ const init = a.initializer;
2052
+ if (!init) p[key] = true;
2053
+ else if (ts.isStringLiteral(init)) p[key] = init.text;
2054
+ else if (ts.isJsxExpression(init) && init.expression) {
2055
+ const e = init.expression;
2056
+ if (ts.isStringLiteral(e) || ts.isNoSubstitutionTemplateLiteral(e)) p[key] = e.text;
2057
+ else if (ts.isNumericLiteral(e)) p[key] = Number(e.text);
2058
+ else if (e.kind === ts.SyntaxKind.TrueKeyword || e.kind === ts.SyntaxKind.FalseKeyword) p[key] = e.kind === ts.SyntaxKind.TrueKeyword;
2059
+ else if (ts.isIdentifier(e) && lucide.has(e.text)) p[key] = e.text;
2060
+ }
2061
+ }
2062
+ const children = [];
2063
+ if (ts.isJsxElement(n))
2064
+ for (const c of n.children) {
2065
+ if (ts.isJsxText(c)) {
2066
+ const t = c.text.replace(/\s+/g, " ").trim();
2067
+ if (t) children.push(t);
2068
+ } else if (ts.isJsxElement(c) || ts.isJsxSelfClosingElement(c)) children.push(convert(c));
2069
+ else if (ts.isJsxExpression(c) && c.expression && (ts.isStringLiteral(c.expression) || ts.isNoSubstitutionTemplateLiteral(c.expression))) children.push(c.expression.text);
2070
+ }
2071
+ return { c: open.tagName.getText(sf), ...Object.keys(p).length ? { p } : {}, ...children.length ? { children } : {} };
2072
+ };
2073
+ let found;
2074
+ const visit = (n) => {
2075
+ if (found) return;
2076
+ if ((ts.isJsxElement(n) || ts.isJsxSelfClosingElement(n)) && (ts.isJsxElement(n) ? n.openingElement : n).tagName.getText(sf) === name) {
2077
+ const el = convert(n);
2078
+ if (el.children?.some((c) => typeof c !== "string")) found = el;
2079
+ }
2080
+ ts.forEachChild(n, visit);
2081
+ };
2082
+ visit(sf);
2083
+ return found;
2084
+ }
2085
+ function previewProps(el) {
2086
+ const out = {};
2087
+ for (const [k, v] of Object.entries(el.p ?? {})) if (v !== null && v !== void 0) out[k] = jsonValue(v);
2088
+ return out;
2089
+ }
2090
+ function jsonValue(v) {
2091
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean" || v === null) return v;
2092
+ if (Array.isArray(v)) return v.map(jsonValue);
2093
+ if (v && typeof v === "object") return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, jsonValue(x)]));
2094
+ return void 0;
2095
+ }
2096
+ function renderPreview(children, ctx) {
2097
+ const out = [];
2098
+ for (const c of children) {
2099
+ if (typeof c === "string") {
2100
+ if (c.trim()) out.push(textNode(c.trim()));
2101
+ continue;
2102
+ }
2103
+ if (!ctx.reader.exists(c.c) || ctx.depth >= 4) continue;
2104
+ const kids = c.children ?? [];
2105
+ let props = previewProps(c);
2106
+ if (kids.length && kids.every((k) => typeof k === "string")) props.children = kids.join(" ");
2107
+ else if (kids.length) props.children = CHILDREN;
2108
+ props = withFormError(c.c, props, ctx);
2109
+ const n = renderComponent(ctx.reader, c.c, props, { ...ctx, depth: ctx.depth + 1 }, void 0, kids.some((k) => typeof k !== "string") ? kids : void 0);
2110
+ if (n) out.push(n);
2111
+ else ctx.blockers?.push(`cannot draw its ${c.c} parts yet`);
2112
+ }
2113
+ return out;
2114
+ }
2115
+ function useRenderElement(fn, scope, ctx) {
2116
+ if (!fn.body || !ts.isBlock(fn.body)) return void 0;
2117
+ const ret = fn.body.statements.find(ts.isReturnStatement)?.expression;
2118
+ if (!ret || !ts.isCallExpression(ret) || ret.expression.getText(scope.src.sf) !== "useRender" || !ret.arguments[0]) return void 0;
2119
+ const opts = evaluate(ret.arguments[0], scope);
2120
+ const tag = typeof opts?.defaultTagName === "string" ? opts.defaultTagName : "div";
2121
+ const props = opts?.props && typeof opts.props === "object" ? opts.props : {};
2122
+ return [elementNode({ element: tag, props, kids: [] }, ctx)];
2123
+ }
2124
+ function renderComponent(reader, name, props, ctx, fnOverride, preview) {
1059
2125
  const src = reader.load(name);
1060
2126
  const fn = fnOverride ?? src?.fn;
1061
2127
  if (!src || !fn) return void 0;
1062
- const scope = { src, vars: /* @__PURE__ */ new Map(), locals: /* @__PURE__ */ new Map(), toggles: /* @__PURE__ */ new Map(), main: ctx.depth === 0 };
1063
- const names = bindProps(fn, scope, props);
2128
+ const scope = { src, vars: /* @__PURE__ */ new Map(), locals: /* @__PURE__ */ new Map(), toggles: /* @__PURE__ */ new Map(), main: ctx.depth === 0, inits: /* @__PURE__ */ new Map(), ...preview ? { preview } : {}, ...ctx.contexts ? { contexts: ctx.contexts } : {} };
2129
+ const given = { ...props };
2130
+ if (!scope.main) {
2131
+ const on = (k) => props[k] === true;
2132
+ scope.checked = on("indeterminate") ? "mixed" : on("checked") || on("defaultChecked") ? "true" : "false";
2133
+ }
1064
2134
  if (scope.main) {
1065
- for (const [local, prop] of names) if (src.props.get(prop)?.kind === "icon" && src.props.get(prop)?.optional) scope.toggles.set(local, prop);
2135
+ for (const p of src.props.values()) {
2136
+ if (given[p.name] !== void 0 || p.name === "children") continue;
2137
+ if (p.kind === "node" && !p.optional) given[p.name] = { textProp: p.name, text: sentenceCase(p.name) };
2138
+ if (p.kind === "string" && !p.optional) given[p.name] = sentenceCase(p.name);
2139
+ }
2140
+ }
2141
+ const names = bindProps(fn, scope, given);
2142
+ if (scope.main) {
2143
+ scope.toggleValues = /* @__PURE__ */ new Map();
2144
+ for (const [local, prop] of names) {
2145
+ const info = src.props.get(prop);
2146
+ if (!info?.optional || given[prop] !== void 0) continue;
2147
+ if (info.kind === "icon") scope.toggles.set(local, prop);
2148
+ else if (info.kind === "node" && prop !== "children") {
2149
+ scope.toggles.set(local, prop);
2150
+ scope.toggleValues.set(local, { textProp: prop, text: sentenceCase(prop) });
2151
+ } else if (info.kind === "string") {
2152
+ scope.toggles.set(local, prop);
2153
+ const example = ctx.examples?.[prop];
2154
+ scope.toggleValues.set(local, typeof example === "string" ? example : sentenceCase(prop));
2155
+ if (typeof example === "string") (scope.toggleOn ??= /* @__PURE__ */ new Set()).add(prop);
2156
+ }
2157
+ }
1066
2158
  }
1067
2159
  const jsx = renderedJsx(fn, scope);
1068
- if (!jsx) return void 0;
1069
- const nodes = renderJsx(jsx, ctx);
2160
+ const nodes = jsx ? renderJsx(jsx, ctx) : useRenderElement(fn, scope, ctx);
2161
+ if (!nodes) return void 0;
1070
2162
  return nodes.length === 1 ? nodes[0] : nodes.length ? { key: "", tag: "div", classes: [], children: nodes, kind: "frame" } : void 0;
1071
2163
  }
1072
- function collapseText(n, root = true) {
1073
- n.children = n.children.map((c) => collapseText(c, false));
1074
- if (!root && n.tag === "span" && !n.name && n.kind === "frame" && n.children.length === 1 && n.children[0].kind === "text" && !n.children[0].classes.length && !n.children[0].inputLike) {
2164
+ function collapseText(n, root = true, boxed = () => false, unseen = () => false) {
2165
+ n.children = n.children.map((c) => collapseText(c, false, boxed, unseen));
2166
+ const seen = n.children.filter((c) => !unseen(c));
2167
+ if (seen.length < n.children.length && seen.length && /^(span|p|label|small|strong|em|code)$/.test(n.tag)) n.children = seen;
2168
+ const run = (c) => c.kind === "text" && c.chars !== void 0 && !c.visible ? c.raw ?? c.chars : c.tag === "span" && c.kind === "frame" && !c.visible && c.children.length === 1 ? run(c.children[0]) : void 0;
2169
+ if (n.kind === "frame" && n.children.filter((c) => !c.space).length > 1 && /^(span|p|label|small|strong|em|code)$/.test(n.tag) && n.children.every((c) => run(c) !== void 0)) {
2170
+ let chars = "";
2171
+ n.children.forEach((c, i) => {
2172
+ const text = run(c);
2173
+ const prev = n.children[i - 1];
2174
+ if (prev) {
2175
+ const before = run(prev);
2176
+ chars += /\s$/.test(before) || /^\s/.test(text) || prev.tag === "#text" && c.tag === "#text" ? "" : " ";
2177
+ }
2178
+ chars += text;
2179
+ });
2180
+ n.children = [{ ...textNode(chars.replace(/\s+/g, " ").trim()) }];
2181
+ }
2182
+ n.children = n.children.filter((c) => !c.space);
2183
+ if (!root && n.tag === "span" && !n.name && n.kind === "frame" && n.children.length === 1 && n.children[0].kind === "text" && !n.children[0].classes.length && !n.children[0].inputLike && !boxed(n)) {
1075
2184
  const t = n.children[0];
1076
2185
  return { ...t, tag: n.tag, classes: n.classes, ...n.visible ? { visible: n.visible } : {} };
1077
2186
  }
1078
2187
  return n;
1079
2188
  }
1080
- function build(reader, src, props) {
1081
- const root = renderComponent(reader, src.name, { ...props, children: CHILDREN }, { reader, depth: 0, component: src.name });
2189
+ function applyView(root, view) {
2190
+ if (!view) return root;
2191
+ const isPortal = (n) => /\.Portal$/.test(n.tag);
2192
+ if (view === "without-popup") {
2193
+ const strip2 = (n) => ({ ...n, children: n.children.filter((c) => !isPortal(c)).map(strip2) });
2194
+ return isPortal(root) ? void 0 : strip2(root);
2195
+ }
2196
+ let found;
2197
+ const find = (n, inPortal) => {
2198
+ if (found) return;
2199
+ if (inPortal && /\.Popup$/.test(n.tag)) found = n;
2200
+ for (const c of n.children) find(c, inPortal || isPortal(n));
2201
+ };
2202
+ find(root, false);
2203
+ const strip = (n) => ({ ...n, children: n.children.filter((c) => !isPortal(c)).map(strip) });
2204
+ return found ? strip({ ...found, visible: void 0 }) : void 0;
2205
+ }
2206
+ function build(reader, src, props, preview, view, rules = [], tokens, examples) {
2207
+ const blockers = [];
2208
+ const selected = typeof props.defaultValue === "string" ? props.defaultValue : void 0;
2209
+ const rendered = renderComponent(reader, src.name, { ...props, children: CHILDREN }, { reader, depth: 0, component: src.name, blockers, ...selected !== void 0 ? { selected } : {}, ...examples ? { examples } : {} }, void 0, preview);
2210
+ let root = rendered && applyView(rendered, view);
1082
2211
  if (!root) return void 0;
2212
+ const overlay = (n) => POPUP_PART.test(n.tag) || /\.Portal$/.test(n.tag);
2213
+ while (root.children.length === 1 && !root.classes.length && !root.attrs && root.kind === "frame" && !overlay(root) && root.children[0].kind === "frame" && !root.children[0].visible && !overlay(root.children[0]))
2214
+ root = root.children[0];
1083
2215
  if (root.takesChildren && !root.children.length && src.subcomponents.length) {
2216
+ root.fromExports = true;
1084
2217
  for (const sub of src.subcomponents) {
1085
2218
  const label = sub.name.slice(src.name.length);
1086
- const n = renderComponent(reader, src.name, {}, { reader, depth: 1, component: src.name }, sub.fn);
2219
+ const n = renderComponent(reader, src.name, {}, { reader, depth: 1, component: src.name, blockers }, sub.fn);
1087
2220
  if (!n) continue;
1088
2221
  n.name = label;
1089
2222
  if (n.takesChildren && !n.children.length) n.children = [{ key: "", tag: "#text", classes: [], children: [], kind: "text", text: { property: label, default: label } }];
1090
2223
  root.children.push(n);
1091
2224
  }
1092
2225
  }
1093
- const tree = collapseText(root);
2226
+ const follow = (n, inherited) => {
2227
+ if (n.baseUi && n.selectedBy) inherited = n.checked;
2228
+ else if (n.baseUi && inherited) n.checked = inherited;
2229
+ for (const c of n.children) follow(c, inherited);
2230
+ };
2231
+ follow(root, void 0);
2232
+ decorate(root, rules, tokens);
2233
+ const boxed = (n) => {
2234
+ const el = { tag: n.tag, classes: new Set(n.classes), children: [], present: true, isRoot: false, inputLike: false };
2235
+ const d = declaredFor(rules, el, DEFAULT_STATE);
2236
+ const set = (k) => d.has(k) && !/^(0|0px|none|transparent)$/.test(d.get(k).value.trim());
2237
+ return ["background-color", "border-top-width", "padding-left", "padding-top"].some(set);
2238
+ };
2239
+ const unseen = (n) => {
2240
+ if (n.kind !== "frame" && n.kind !== "text") return false;
2241
+ const el = { tag: n.tag, classes: new Set(n.classes), children: [], present: true, isRoot: false, inputLike: false };
2242
+ return visuallyHidden(new Map([...declaredFor(rules, el, DEFAULT_STATE)].map(([k, d]) => [k, d.value])));
2243
+ };
2244
+ const tree = collapseText(root, true, boxed, unseen);
1094
2245
  const parts = /* @__PURE__ */ new Map();
1095
2246
  const parents = /* @__PURE__ */ new Map();
1096
2247
  const used = /* @__PURE__ */ new Set();
@@ -1102,7 +2253,7 @@ function build(reader, src, props) {
1102
2253
  else if (n.text && n.kind === "text" && !n.placeholder) base = n.text.prop === "children" ? "text" : `${slug(n.text.property)}-text`;
1103
2254
  else if (n.placeholder) base = "placeholder";
1104
2255
  else if (n.name) base = slug(n.name);
1105
- else base = slug(n.classes[0]?.split("|")[1] ?? n.tag) || "part";
2256
+ else base = slug(n.classes[0]?.split("|")[1] ?? (n.chars !== void 0 ? "text" : n.tag)) || "part";
1106
2257
  let id = base;
1107
2258
  for (let i = 2; used.has(id); i++) id = `${base}-${i}`;
1108
2259
  used.add(id);
@@ -1112,14 +2263,94 @@ function build(reader, src, props) {
1112
2263
  for (const c of n.children) assign(c, id);
1113
2264
  };
1114
2265
  assign(tree, void 0);
1115
- return { root: tree, parts, parents };
2266
+ return { root: tree, parts, parents, blockers };
2267
+ }
2268
+ function cssElements(root) {
2269
+ const out = /* @__PURE__ */ new Map();
2270
+ const make = (n, parent) => {
2271
+ const el = {
2272
+ tag: n.tag,
2273
+ classes: new Set(n.classes),
2274
+ parent,
2275
+ children: [],
2276
+ present: (!n.visible || !!n.visible.on) && !n.pseudo,
2277
+ isRoot: !parent,
2278
+ inputLike: !!n.inputLike,
2279
+ ...n.baseUi ? { baseUi: true, checked: n.checked } : {},
2280
+ ...n.attrs ? { attrs: n.attrs } : {}
2281
+ };
2282
+ out.set(n, el);
2283
+ el.children = n.children.map((c) => make(c, el));
2284
+ return el;
2285
+ };
2286
+ make(root, void 0);
2287
+ return out;
2288
+ }
2289
+ const declaredVarCache = /* @__PURE__ */ new WeakMap();
2290
+ function declaredVars(rules) {
2291
+ let s = declaredVarCache.get(rules);
2292
+ if (!s) {
2293
+ s = new Set(rules.flatMap((r) => r.decls.filter((d) => d.prop.startsWith("--")).map((d) => d.prop)));
2294
+ declaredVarCache.set(rules, s);
2295
+ }
2296
+ return s;
2297
+ }
2298
+ function runtimeVar(raw, rules, tokens) {
2299
+ const m = raw ? /^var\(\s*(--[\w-]+)\s*\)$/.exec(raw.trim()) : null;
2300
+ return !!m && !declaredVars(rules).has(m[1]) && !tokens?.has(m[1]);
2301
+ }
2302
+ const PSEUDO_PAINTS = ["background-color", "border-top-width", "border-bottom-width", "border-left-width", "border-right-width"];
2303
+ function decorate(root, rules, tokens) {
2304
+ if (!rules.length) return;
2305
+ const els = cssElements(root);
2306
+ const visit = (n, parent) => {
2307
+ for (const c of [...n.children]) visit(c, n);
2308
+ if (n.kind !== "frame" || n.svgAttrs || n.pseudo) return;
2309
+ const el = els.get(n);
2310
+ if (n.baseUi && n.whenChecked && /\.Indicator$/.test(n.tag) && parent) {
2311
+ const d = declaredFor(rules, el, DEFAULT_STATE);
2312
+ if (["left", "top", "width", "height"].some((k) => runtimeVar(d.get(k)?.value, rules, tokens))) {
2313
+ const item = parent.children.find((s) => s !== n && s.baseUi && s.selectedBy && s.checked === "true");
2314
+ if (item) {
2315
+ parent.children.splice(parent.children.indexOf(n), 1);
2316
+ item.children.unshift(n);
2317
+ n.follows = true;
2318
+ delete n.whenChecked;
2319
+ }
2320
+ }
2321
+ }
2322
+ for (const pseudo of ["before", "after"]) {
2323
+ const d = declaredFor(rules, el, DEFAULT_STATE, pseudo);
2324
+ if (!/^(""|'')$/.test(d.get("content")?.value.trim() ?? "")) continue;
2325
+ if (!PSEUDO_PAINTS.some((k) => d.has(k) && !/^(0|none|transparent)$/.test(d.get(k).value.trim()))) continue;
2326
+ const deco = { key: "", tag: `::${pseudo}`, classes: [], children: [], kind: "frame", pseudo, name: "Decoration" };
2327
+ if (pseudo === "before" || /^-/.test(d.get("z-index")?.value.trim() ?? "")) n.children.unshift(deco);
2328
+ else n.children.push(deco);
2329
+ }
2330
+ };
2331
+ visit(root, void 0);
1116
2332
  }
1117
2333
  function toCssTree(b) {
1118
2334
  const els = /* @__PURE__ */ new Map();
1119
2335
  const make = (n, parent) => {
1120
- const el = { tag: n.tag, classes: new Set(n.classes), parent, children: [], present: !n.visible, isRoot: !parent, inputLike: !!n.inputLike };
2336
+ const el = {
2337
+ tag: n.tag,
2338
+ classes: new Set(n.classes),
2339
+ parent,
2340
+ children: [],
2341
+ present: (!n.visible || !!n.visible.on) && !n.pseudo,
2342
+ isRoot: !parent,
2343
+ inputLike: !!n.inputLike,
2344
+ ...n.baseUi ? { baseUi: true, checked: n.checked } : {},
2345
+ ...n.attrs ? { attrs: n.attrs } : {}
2346
+ };
1121
2347
  els.set(n.key, el);
1122
2348
  el.children = n.children.map((c) => make(c, el));
2349
+ for (const [i, shape] of (n.shapes ?? []).entries()) {
2350
+ const sel = { tag: shape.tag, classes: new Set(shape.classes), parent: el, children: [], present: true, isRoot: false, inputLike: false };
2351
+ els.set(`${n.key}#shape-${i}`, sel);
2352
+ el.children.push(sel);
2353
+ }
1123
2354
  return el;
1124
2355
  };
1125
2356
  make(b.root, void 0);
@@ -1145,6 +2376,14 @@ function resolveValue(raw, values, tokens, depth = 0, parent, forColor = false)
1145
2376
  return { unmapped: `${name} is not a token` };
1146
2377
  }
1147
2378
  if (/^calc\(/i.test(v)) return calcValue(v, values, tokens, depth, parent);
2379
+ const mm = /^min\(([\s\S]+)\)$/i.exec(v);
2380
+ if (mm) {
2381
+ for (const arg of splitTop(mm[1], ",")) {
2382
+ const r = resolveValue(arg, values, tokens, depth + 1, parent, forColor);
2383
+ if (r && r.unmapped === void 0 && typeof r.value === "string") return r;
2384
+ }
2385
+ return { unmapped: "min() of no token" };
2386
+ }
1148
2387
  if (v === "100%") return { unmapped: "fills its container: Figma draws it at a fixed width" };
1149
2388
  if (/%$/.test(v)) return { unmapped: "relative size: the Figma part hugs its content" };
1150
2389
  return { unmapped: "literal value, not a token" };
@@ -1158,7 +2397,7 @@ function calcValue(v, values, tokens, depth, parent) {
1158
2397
  failed ??= r?.unmapped ?? `${m} has no value`;
1159
2398
  return "0";
1160
2399
  }
1161
- if (r.value === 0) return "0";
2400
+ if (typeof r.value === "number") return String(r.value);
1162
2401
  return r.value.startsWith("calc(") ? `(${r.value.slice(5, -1)})` : r.value;
1163
2402
  });
1164
2403
  if (failed) return { unmapped: `calc(): ${failed}` };
@@ -1194,6 +2433,7 @@ const TEXT_FIELDS = [
1194
2433
  ["line-height", "lineHeight"],
1195
2434
  ["letter-spacing", "letterSpacing"]
1196
2435
  ];
2436
+ const NUMBER_FIELDS = /* @__PURE__ */ new Set(["strokeWeight", "opacity"]);
1197
2437
  const SIDE_FIELDS = { top: "strokeTopWeight", right: "strokeRightWeight", bottom: "strokeBottomWeight", left: "strokeLeftWeight" };
1198
2438
  const typeCache = /* @__PURE__ */ new WeakMap();
1199
2439
  function typesOf(tokens) {
@@ -1205,14 +2445,30 @@ function typesOf(tokens) {
1205
2445
  return m;
1206
2446
  }
1207
2447
  function expectedTypes(field) {
2448
+ if (field === "textDecoration") return ["keyword"];
1208
2449
  if (COLOR_FIELDS.has(field)) return ["color"];
1209
2450
  if (field === "fontFamily") return ["fontFamily"];
1210
2451
  if (field === "fontWeight") return ["fontWeight", "number"];
2452
+ if (field === "opacity") return ["number"];
1211
2453
  return ["dimension", "number"];
1212
2454
  }
1213
2455
  function partValues(node, computed, own, tokens, parent) {
1214
2456
  const out = { values: {}, unmapped: [] };
1215
2457
  const set = (field, prop, raw, skipZero = false) => {
2458
+ if (raw !== void 0 && NUMBER_FIELDS.has(field) && /^-?(\d+\.?\d*|\.\d+)$/.test(raw.trim())) {
2459
+ out.values[field] = Number(raw.trim());
2460
+ return;
2461
+ }
2462
+ const em = raw !== void 0 ? /^(\d*\.?\d+)em$/.exec(raw.trim()) : null;
2463
+ if (em) {
2464
+ const base = prop === "font-size" ? parent?.get("font-size") : computed.get("font-size");
2465
+ const b = resolveValue(base, prop === "font-size" ? parent ?? /* @__PURE__ */ new Map() : computed, tokens, 0, parent);
2466
+ if (b && typeof b.value === "string" && !b.value.startsWith("calc(")) {
2467
+ const n = Number(em[1]);
2468
+ out.values[field] = n === 1 ? b.value : `calc(${b.value} * ${n})`;
2469
+ return;
2470
+ }
2471
+ }
1216
2472
  const r = resolveValue(raw, computed, tokens, 0, parent, prop === "color");
1217
2473
  if (!r) return;
1218
2474
  if (r.unmapped) {
@@ -1230,8 +2486,17 @@ function partValues(node, computed, own, tokens, parent) {
1230
2486
  }
1231
2487
  out.values[field] = r.value;
1232
2488
  };
2489
+ const opacity = own.get("opacity");
2490
+ const setOpacity = () => {
2491
+ if (opacity !== void 0 && !/^(0|1|1\.0+)$/.test(opacity.trim())) set("opacity", "opacity", opacity);
2492
+ };
1233
2493
  if (node.kind === "text") {
1234
2494
  for (const [prop, field] of TEXT_FIELDS) set(field, prop, computed.get(prop));
2495
+ const line = computed.get("text-decoration-line") ?? "";
2496
+ const lineColor = (computed.get("text-decoration-color") ?? "").trim().toLowerCase();
2497
+ const keyword = /underline/.test(line) ? "UNDERLINE" : /line-through/.test(line) ? "STRIKETHROUGH" : void 0;
2498
+ if (keyword && lineColor !== "transparent") out.values.textDecoration = keyword;
2499
+ setOpacity();
1235
2500
  return out;
1236
2501
  }
1237
2502
  if (node.kind === "icon") {
@@ -1239,9 +2504,14 @@ function partValues(node, computed, own, tokens, parent) {
1239
2504
  set("height", "height", computed.get("height"));
1240
2505
  set("stroke", "color", computed.get("color"));
1241
2506
  set("strokeWeight", "stroke-width", computed.get("stroke-width"));
2507
+ setOpacity();
1242
2508
  return out;
1243
2509
  }
1244
- for (const [prop, field] of FRAME_FIELDS) set(field, prop, computed.get(prop), field === "minWidth" || field === "minHeight");
2510
+ setOpacity();
2511
+ const layout = layoutOf(computed);
2512
+ const gap = computed.get(layout.direction === "HORIZONTAL" ? "column-gap" : "row-gap");
2513
+ for (const [prop, field] of FRAME_FIELDS) set(field, prop, field === "itemSpacing" ? gap : computed.get(prop), field === "minWidth" || field === "minHeight");
2514
+ if (layout.wrap) set("counterAxisSpacing", "row-gap", computed.get("row-gap"));
1245
2515
  const widths = ["top", "right", "bottom", "left"].map((s) => computed.get(`border-${s}-width`));
1246
2516
  const colors = ["top", "right", "bottom", "left"].map((s) => computed.get(`border-${s}-color`));
1247
2517
  const present = widths.map((w) => w !== void 0 && !isZero(w));
@@ -1259,7 +2529,23 @@ function partValues(node, computed, own, tokens, parent) {
1259
2529
  }
1260
2530
  return out;
1261
2531
  }
2532
+ const singleCellGrid = (c) => /grid/.test(c.get("display") ?? "") && ["grid-template-columns", "grid-template-rows", "grid-template-areas", "grid-template", "grid-auto-flow", "grid-auto-columns"].every((k) => !c.get(k) || c.get(k) === "none");
2533
+ const TABLE_DISPLAY = { table: "table", thead: "table-row-group", tbody: "table-row-group", tfoot: "table-row-group", tr: "table-row", td: "table-cell", th: "table-cell", caption: "table-caption" };
2534
+ const gridCounter = (v) => /center/.test(v ?? "") ? "CENTER" : /end/.test(v ?? "") ? "MAX" : /start/.test(v ?? "") ? "MIN" : "STRETCH";
1262
2535
  function layoutOf(computed) {
2536
+ const shown = computed.get("display");
2537
+ if (shown === "table-row") return { direction: "HORIZONTAL", primary: "MIN", counter: "STRETCH" };
2538
+ if (shown === "table-cell") {
2539
+ const va = computed.get("vertical-align") ?? "middle";
2540
+ const ta = computed.get("text-align") ?? "start";
2541
+ return { direction: "VERTICAL", primary: /middle/.test(va) ? "CENTER" : /bottom/.test(va) ? "MAX" : "MIN", counter: /center/.test(ta) ? "CENTER" : /(end|right)/.test(ta) ? "MAX" : "MIN" };
2542
+ }
2543
+ if (singleCellGrid(computed)) {
2544
+ const align2 = computed.get("align-items") ?? "stretch";
2545
+ const justify2 = computed.get("justify-items") ?? "stretch";
2546
+ const at = (v) => /center/.test(v) ? "CENTER" : /end/.test(v) ? "MAX" : "MIN";
2547
+ return { direction: "VERTICAL", primary: at(align2), counter: /stretch|normal/.test(justify2) ? "STRETCH" : at(justify2) };
2548
+ }
1263
2549
  const dir = computed.get("flex-direction") ?? "row";
1264
2550
  const display = computed.get("display") ?? "block";
1265
2551
  const flex = /flex/.test(display);
@@ -1267,48 +2553,267 @@ function layoutOf(computed) {
1267
2553
  const align = computed.get("align-items") ?? "normal";
1268
2554
  const primary = /center/.test(justify) ? "CENTER" : /space-between/.test(justify) ? "SPACE_BETWEEN" : /end/.test(justify) ? "MAX" : "MIN";
1269
2555
  const counter = /center/.test(align) ? "CENTER" : /end/.test(align) ? "MAX" : /stretch|normal/.test(align) && flex ? "STRETCH" : "MIN";
1270
- return { direction: flex && !/column/.test(dir) ? "HORIZONTAL" : "VERTICAL", primary, counter: flex ? counter : "STRETCH" };
2556
+ return {
2557
+ direction: flex && !/column/.test(dir) ? "HORIZONTAL" : "VERTICAL",
2558
+ primary,
2559
+ counter: flex ? counter : "STRETCH",
2560
+ ...flex && /reverse/.test(dir) ? { reverse: true } : {},
2561
+ ...flex && !/column/.test(dir) && /^wrap/.test(computed.get("flex-wrap") ?? "") ? { wrap: true } : {}
2562
+ };
2563
+ }
2564
+ function grows(c) {
2565
+ const flex = c?.get("flex")?.trim();
2566
+ const grow = c?.get("flex-grow") ?? (flex && /^\d/.test(flex) ? flex.split(/\s+/)[0] : flex === "auto" ? "1" : void 0);
2567
+ return grow !== void 0 && Number(grow) >= 1;
2568
+ }
2569
+ function shrinksToFit(n, c, parent) {
2570
+ return n.kind === "frame" && parent?.direction === "HORIZONTAL" && !parent.wrap && /^0(px)?$/.test(c?.get("min-width")?.trim() ?? "") && c?.get("flex-shrink")?.trim() !== "0" && !/^(none|0 0)/.test(c?.get("flex")?.trim() ?? "");
1271
2571
  }
1272
2572
  const clips = (c) => /^(hidden|clip)\b/.test(c?.get("overflow") ?? c?.get("overflow-x") ?? "");
1273
- function fillsContainer(c) {
2573
+ const INLINE_TAGS = /* @__PURE__ */ new Set(["a", "span", "label", "kbd", "code", "em", "strong", "b", "i", "small", "abbr", "button", "input", "textarea", "select", "img"]);
2574
+ function fillsContainer(c, tag = "div") {
1274
2575
  if (!c) return false;
1275
2576
  if (c.get("width") === "100%") return true;
1276
- const display = c.get("display") ?? "block";
2577
+ const display = c.get("display") ?? (INLINE_TAGS.has(tag) ? "inline" : "block");
1277
2578
  return /^(block|flex|grid|flow-root)$/.test(display);
1278
2579
  }
1279
- function evaluateVariant(reader, src, rules, props, state, tokens) {
1280
- const built = build(reader, src, props);
2580
+ class UnsupportedComponentError extends Error {
2581
+ constructor(component, reason) {
2582
+ super(`${component}: ${reason}`);
2583
+ this.component = component;
2584
+ this.reason = reason;
2585
+ }
2586
+ component;
2587
+ reason;
2588
+ }
2589
+ function svgVector(node, id, els, rules, state, computed, tokens) {
2590
+ const esc = (v) => v.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
2591
+ const num = (raw, values) => {
2592
+ if (raw === void 0) return void 0;
2593
+ const t = raw.trim();
2594
+ if (/^-?\d*\.?\d+$/.test(t)) return Number(t);
2595
+ const sub = t.replace(/var\(\s*(--[\w-]+)\s*(?:,\s*([^()]+))?\)/g, (_m, name, fb) => values.get(name) ?? fb ?? "NaN");
2596
+ if (/^calc\(/.test(sub) && /^[\d\s.+\-*/()calc]+$/.test(sub)) {
2597
+ try {
2598
+ const n = Number(new Function(`return (${sub.replace(/calc/g, "")});`)());
2599
+ return Number.isFinite(n) ? n : void 0;
2600
+ } catch {
2601
+ return void 0;
2602
+ }
2603
+ }
2604
+ return void 0;
2605
+ };
2606
+ const strokes = [];
2607
+ const shapes = [];
2608
+ for (const [i, shape] of (node.shapes ?? []).entries()) {
2609
+ const el = els.get(`${id}#shape-${i}`);
2610
+ const own = new Map([...declaredFor(rules, el, state)].map(([k, d]) => [k, d.value]));
2611
+ const c = computeValues(declaredFor(rules, el, state), computed);
2612
+ const attrs = { ...shape.svgAttrs };
2613
+ const fill = own.get("fill") ?? attrs.fill;
2614
+ if (fill && !/^(none|transparent)$/.test(fill)) return { blocker: "draws SVG shapes with fills" };
2615
+ const strokeRaw = own.get("stroke") ?? attrs.stroke ?? node.svgAttrs?.stroke ?? "currentcolor";
2616
+ const color = resolveValue(strokeRaw, c, tokens, 0, computed, false);
2617
+ if (!color || color.unmapped !== void 0 || color.value === void 0) return { blocker: "draws SVG shapes in colors that are not tokens" };
2618
+ strokes.push(color.value);
2619
+ const out = { ...attrs, fill: "none", stroke: "#000000" };
2620
+ const width = num(own.get("stroke-width") ?? attrs["stroke-width"] ?? attrs.strokeWidth, c);
2621
+ if (width !== void 0) out["stroke-width"] = String(width);
2622
+ const opacity = num(own.get("opacity"), c);
2623
+ if (opacity !== void 0 && opacity !== 1) out.opacity = String(opacity);
2624
+ const cap = own.get("stroke-linecap");
2625
+ if (cap) out["stroke-linecap"] = cap;
2626
+ const pathLength = num(attrs.pathLength, c);
2627
+ const r = num(attrs.r, c);
2628
+ const scale = pathLength && r !== void 0 ? 2 * Math.PI * r / pathLength : 1;
2629
+ const dash = own.get("stroke-dasharray");
2630
+ if (dash) {
2631
+ const parts = splitTop(dash, /\s|,/).map((x) => num(x, c));
2632
+ if (parts.some((x) => x === void 0)) return { blocker: "draws SVG shapes with dashes it cannot read" };
2633
+ out["stroke-dasharray"] = parts.map((x) => +(x * scale).toFixed(3)).join(" ");
2634
+ }
2635
+ const offset = own.get("stroke-dashoffset");
2636
+ if (offset) {
2637
+ const o = num(offset, c);
2638
+ if (o === void 0) return { blocker: "draws SVG shapes with dashes it cannot read" };
2639
+ out["stroke-dashoffset"] = String(+(o * scale).toFixed(3));
2640
+ }
2641
+ delete out.pathLength;
2642
+ delete out.strokeWidth;
2643
+ delete out.key;
2644
+ shapes.push(`<${shape.tag} ${Object.entries(out).map(([k, v]) => `${k}="${esc(v)}"`).join(" ")}/>`);
2645
+ }
2646
+ if (!shapes.length) return { blocker: "draws SVG it cannot read" };
2647
+ const viewBox = node.svgAttrs?.viewBox ?? "0 0 24 24";
2648
+ const [, , w, h] = viewBox.split(/\s+/).map(Number);
2649
+ const rot = /^rotate\((-?\d+(?:\.\d+)?)deg\)$/.exec(computed.get("transform")?.trim() ?? "");
2650
+ const body = rot ? `<g transform="rotate(${rot[1]} ${(w ?? 24) / 2} ${(h ?? 24) / 2})">${shapes.join("")}</g>` : shapes.join("");
2651
+ return { svg: `<svg xmlns="http://www.w3.org/2000/svg" width="${w ?? 24}" height="${h ?? 24}" viewBox="${viewBox}" fill="none">${body}</svg>`, strokes };
2652
+ }
2653
+ const POPUP_PART = /\.(Portal|Popup|Positioner|Backdrop|Viewport)$/;
2654
+ function translateX(transform) {
2655
+ const m = /^translateX\(([\s\S]+)\)$/.exec(transform?.trim() ?? "");
2656
+ return m?.[1]?.trim();
2657
+ }
2658
+ const visuallyHidden = (own) => /inset\(50%\)/.test(own.get("clip-path") ?? "") || /rect\(0/.test(own.get("clip") ?? "");
2659
+ function evaluateVariant(reader, src, rules, props, state, tokens, preview, page, view, examples) {
2660
+ const built = build(reader, src, props, preview, view, rules, tokens, examples);
1281
2661
  if (!built) return void 0;
1282
2662
  const els = toCssTree(built);
2663
+ const gridRows = /* @__PURE__ */ new Map();
2664
+ const gridGrow = /* @__PURE__ */ new Set();
1283
2665
  const computedById = /* @__PURE__ */ new Map();
2666
+ const ownById = /* @__PURE__ */ new Map();
1284
2667
  const values = {};
1285
2668
  const unmapped = [];
1286
2669
  const layouts = /* @__PURE__ */ new Map();
2670
+ const hidden = /* @__PURE__ */ new Set();
2671
+ const cover = /* @__PURE__ */ new Set();
2672
+ const pins = /* @__PURE__ */ new Map();
2673
+ const vectors = /* @__PURE__ */ new Map();
2674
+ const stretch = /* @__PURE__ */ new Map();
2675
+ const blockers = [...built.blockers];
1287
2676
  for (const [id, node] of built.parts) {
1288
2677
  const el = els.get(id);
1289
2678
  const parentId = built.parents.get(id);
1290
- const own = declaredFor(rules, el, state);
1291
- let computed = computeValues(own, parentId ? computedById.get(parentId) : void 0);
2679
+ const own = node.pseudo && el.parent ? declaredFor(rules, el.parent, state, node.pseudo) : declaredFor(rules, el, state);
2680
+ if (node.follows) {
2681
+ for (const [k, far] of [["left", ""], ["top", ""], ["width", "right"], ["height", "bottom"]]) {
2682
+ const d = own.get(k);
2683
+ if (!d || !runtimeVar(d.value, rules, tokens)) continue;
2684
+ if (far) {
2685
+ own.delete(k);
2686
+ own.set(far, { ...d, prop: far, value: "0" });
2687
+ } else own.set(k, { ...d, value: "0" });
2688
+ }
2689
+ }
2690
+ let computed = computeValues(own, parentId ? computedById.get(parentId) : page);
2691
+ for (const [k, v] of node.styleVars ?? []) computed.set(k, v);
2692
+ if (!computed.has("display") && TABLE_DISPLAY[node.tag]) computed.set("display", TABLE_DISPLAY[node.tag]);
2693
+ if (computed.get("display") === "table-cell" && resolveValue(computed.get("width"), computed, tokens) === void 0 || gridGrow.has(id)) computed.set("flex-grow", "1");
1292
2694
  const ownValues = new Map([...own].map(([k, d]) => [k, d.value]));
1293
- if (node.placeholder) {
1294
- const ph = declaredFor(rules, el, state, "placeholder");
2695
+ if (node.placeholder && el.parent) {
2696
+ const ph = declaredFor(rules, el.parent, state, "placeholder");
1295
2697
  computed = computeValues(ph, computed);
1296
2698
  }
1297
2699
  computedById.set(id, computed);
1298
- const pv = partValues(node, computed, ownValues, tokens, parentId ? computedById.get(parentId) : void 0);
2700
+ ownById.set(id, ownValues);
2701
+ if (POPUP_PART.test(node.tag) && parentId !== void 0) blockers.push("opens a popup");
2702
+ const parentHidden = parentId !== void 0 && hidden.has(parentId);
2703
+ const transform = ownValues.get("transform");
2704
+ if (parentHidden || computed.get("display") === "none" || computed.get("visibility") === "hidden" || /^0(\.0+)?$/.test(ownValues.get("opacity")?.trim() ?? "") || /scale\(0\)/.test(transform ?? "") || visuallyHidden(ownValues) || node.inactive || node.whenChecked && !/^(true|mixed)$/.test((node.checked === "state" ? state.checked : node.checked) ?? ""))
2705
+ hidden.add(id);
2706
+ const pv = partValues(node, computed, ownValues, tokens, parentId ? computedById.get(parentId) : page);
1299
2707
  values[id] = pv.values;
2708
+ if (node.svgAttrs && node.tag === "svg") {
2709
+ const vec = svgVector(node, id, els, rules, state, computed, tokens);
2710
+ if ("blocker" in vec) blockers.push(vec.blocker);
2711
+ else vectors.set(id, vec);
2712
+ }
2713
+ if (node.kind === "frame") layouts.set(id, node.inputLike && node.tag !== "textarea" ? { direction: "HORIZONTAL", primary: "MIN", counter: "CENTER" } : layoutOf(computed));
2714
+ let gridDrawn = false;
2715
+ if (node.kind === "frame" && /grid/.test(computed.get("display") ?? "") && !singleCellGrid(computed)) {
2716
+ const kids = [...built.parts].filter(([cid]) => built.parents.get(cid) === id);
2717
+ const tracks = computed.get("grid-template-columns")?.trim();
2718
+ const parentGrid = parentId ? gridRows.get(parentId) : void 0;
2719
+ if (tracks === "subgrid" && parentGrid) {
2720
+ gridDrawn = true;
2721
+ layouts.set(id, { direction: "HORIZONTAL", primary: "MIN", counter: gridCounter(computed.get("align-items")) });
2722
+ const gap = parentGrid.gap === void 0 ? void 0 : resolveValue(parentGrid.gap, computedById.get(parentId) ?? /* @__PURE__ */ new Map(), tokens);
2723
+ const g = gap && gap.unmapped === void 0 && gap.value !== null ? gap.value : void 0;
2724
+ if (g !== void 0) {
2725
+ pv.values.itemSpacing = g;
2726
+ if (/^0(px)?$/.test(parentGrid.tracks[0] ?? "")) pv.values.paddingLeft = g;
2727
+ if (/^0(px)?$/.test(parentGrid.tracks[parentGrid.tracks.length - 1] ?? "")) pv.values.paddingRight = g;
2728
+ }
2729
+ for (const [cid] of kids) {
2730
+ const start = Number(/^\s*(\d+)/.exec(declaredFor(rules, els.get(cid), state).get("grid-column-start")?.value ?? declaredFor(rules, els.get(cid), state).get("grid-column")?.value ?? "")?.[1]);
2731
+ if (start && /fr\)?$/.test(parentGrid.tracks[start - 1] ?? "")) gridGrow.add(cid);
2732
+ }
2733
+ unmapped.push({ part: id, property: "grid-template-columns", value: "subgrid", reason: "columns line up across rows in code; in Figma each row sizes its own" });
2734
+ } else if (tracks && tracks !== "none" && tracks !== "subgrid" && kids.length && kids.every(([cid]) => /^1\s*\/\s*-1$/.test(declaredFor(rules, els.get(cid), state).get("grid-column")?.value.trim() ?? ""))) {
2735
+ gridDrawn = true;
2736
+ gridRows.set(id, { tracks: splitTop(tracks, /\s/).filter(Boolean), gap: computed.get("column-gap") });
2737
+ layouts.set(id, { direction: "VERTICAL", primary: "MIN", counter: "STRETCH" });
2738
+ }
2739
+ }
2740
+ if (hidden.has(id)) continue;
2741
+ for (const pseudo of ["before", "after"]) {
2742
+ if (node.children.some((c) => c.pseudo === pseudo)) continue;
2743
+ const d = declaredFor(rules, el, state, pseudo);
2744
+ const paints = [...PSEUDO_PAINTS, "box-shadow"].some((k) => d.has(k) && !/^(0|none|transparent)$/.test(d.get(k).value));
2745
+ if (d.has("content") && paints) blockers.push("draws with ::before or ::after");
2746
+ }
2747
+ if (node.kind === "frame" && !node.children.length && !node.inputLike && !node.media && !node.svgAttrs && Object.values(pv.values).every((x) => x === 0 || x === null || x === void 0))
2748
+ blockers.push("has parts whose content the generator cannot read yet");
2749
+ if (/grid/.test(computed.get("display") ?? "") && !singleCellGrid(computed) && !gridDrawn) blockers.push("uses CSS grid");
2750
+ if (node.fraction !== void 0 && ownValues.get("height") === void 0) stretch.set(id, { ...stretch.get(id), height: true });
2751
+ if (node.inlineFill) unmapped.push({ part: id, property: "background-color", value: node.inlineFill, reason: "the token's own color, from the content: not bound, set it on each instance" });
2752
+ const position = ownValues.get("position");
2753
+ const full = ownValues.get("width") === "100%" && ownValues.get("height") === "100%";
2754
+ const pVals = parentId ? values[parentId] : void 0;
2755
+ const inset = ["top", "right", "bottom", "left"].every((k) => /^0(px)?$/.test(ownValues.get(k) ?? ""));
2756
+ if (parentId && (position === "absolute" && (inset || full) || position !== "absolute" && full && pVals?.width !== void 0 && pVals?.height !== void 0)) {
2757
+ cover.add(id);
2758
+ delete pv.values.width;
2759
+ delete pv.values.height;
2760
+ pv.unmapped = pv.unmapped.filter((u) => u.property !== "width" && u.property !== "height");
2761
+ } else if (parentId && position !== "absolute" && (ownValues.get("width") === "100%" || ownValues.get("height") === "100%")) {
2762
+ stretch.set(id, { ...ownValues.get("width") === "100%" ? { width: true } : {}, ...ownValues.get("height") === "100%" ? { height: true } : {} });
2763
+ pv.unmapped = pv.unmapped.filter((u) => !((u.property === "width" || u.property === "height") && u.value === "100%"));
2764
+ for (const k of ["width", "height"]) if (ownValues.get(k) === "100%") delete pv.values[k];
2765
+ } else if (parentId && position === "absolute") {
2766
+ const pin = {};
2767
+ let ok = true;
2768
+ for (const side of ["top", "right", "bottom", "left"]) {
2769
+ const raw = ownValues.get(side);
2770
+ if (raw === void 0 || raw === "auto") continue;
2771
+ const r = resolveValue(raw, computed, tokens);
2772
+ if (!r || r.unmapped !== void 0 || r.value === null || r.value === void 0) ok = false;
2773
+ else pin[side] = r.value;
2774
+ }
2775
+ if (ok && Object.keys(pin).length) pins.set(id, pin);
2776
+ else blockers.push("places layers over each other");
2777
+ } else if (parentId && position === "fixed") blockers.push("places layers over each other");
1300
2778
  for (const u of pv.unmapped) unmapped.push({ part: id, ...u });
1301
- if (node.kind === "frame") layouts.set(id, layoutOf(computed));
2779
+ if (transform && !/^(none|scale\(1\))$/.test(transform.trim()) && !translateX(transform) && /^(translate|rotate|scale)/.test(transform.trim()) && !/^0(\.0+)?$/.test(ownValues.get("opacity") ?? "1")) {
2780
+ unmapped.push({ part: id, property: "transform", value: transform, reason: "transforms are not drawn" });
2781
+ }
2782
+ }
2783
+ for (const [id] of built.parts) {
2784
+ const tx = translateX(ownById.get(id)?.get("transform"));
2785
+ if (!tx || hidden.has(id)) continue;
2786
+ const parentId = built.parents.get(id);
2787
+ const siblings = [...built.parts].filter(([cid, n]) => built.parents.get(cid) === parentId && !hidden.has(cid) && !n.visible);
2788
+ const parentLayout = parentId ? layouts.get(parentId) : void 0;
2789
+ const r = resolveValue(tx, computedById.get(id), tokens);
2790
+ if (r?.value === 0) continue;
2791
+ const pv = parentId ? values[parentId] : void 0;
2792
+ const own = values[id] ?? {};
2793
+ if (!parentId || !pv || siblings.length !== 1 || parentLayout?.direction !== "HORIZONTAL" || !r || r.unmapped !== void 0 || r.value === null || r.value === void 0) {
2794
+ blockers.push("moves a part with a transform");
2795
+ continue;
2796
+ }
2797
+ const v = r.value;
2798
+ const end = typeof v === "string" && v.startsWith("calc(") ? new RegExp(`^calc\\(${escapeRe(String(pv.width))} - ${escapeRe(String(own.width))} - (\\S+)\\)$`).exec(v) : null;
2799
+ if (end) {
2800
+ pv.paddingRight = end[1];
2801
+ layouts.set(parentId, { ...parentLayout, primary: "MAX" });
2802
+ } else if (typeof v === "number" || typeof v === "string" && !v.startsWith("calc(")) {
2803
+ pv.paddingLeft = v;
2804
+ layouts.set(parentId, { ...parentLayout, primary: "MIN" });
2805
+ } else blockers.push("moves a part with a transform");
1302
2806
  }
1303
2807
  const classes = [...built.parts].map(([id, n]) => `${id}:${n.classes.join(",")}:${n.visible?.prop ?? ""}`).join(";");
1304
- return { built, values, unmapped, layouts, classes, computed: computedById };
2808
+ return { built, values, unmapped, layouts, classes, computed: computedById, hidden, cover, pins, vectors, stretch, blockers };
1305
2809
  }
2810
+ const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1306
2811
  function product(axes) {
1307
2812
  let out = [{}];
1308
2813
  for (const a of axes) out = out.flatMap((p) => a.values.map((v) => ({ ...p, [a.name]: v })));
1309
2814
  return out;
1310
2815
  }
1311
- const sameValues = (a, b) => JSON.stringify(a) === JSON.stringify(b);
2816
+ const drawn = (e) => JSON.stringify([e.values, [...e.hidden].sort(), [...e.layouts], [...e.vectors]]);
1312
2817
  const BOX_FIELDS = ["paddingTop", "paddingRight", "paddingBottom", "paddingLeft", "itemSpacing"];
1313
2818
  const PAINT_FIELDS = ["fill", "stroke", "strokeWeight", "strokeTopWeight", "strokeRightWeight", "strokeBottomWeight", "strokeLeftWeight", "cornerRadius", "width", "height", "minWidth", "minHeight"];
1314
2819
  const empty = (v) => v === void 0 || v === 0 || v === null;
@@ -1316,11 +2821,12 @@ function flattenWrapper(parts, variants, unmapped) {
1316
2821
  const root = parts.find((p) => !p.parent);
1317
2822
  const children = parts.filter((p) => p.parent === root?.id);
1318
2823
  const w = children[0];
1319
- if (!root || root.kind !== "frame" || children.length !== 1 || !w || w.kind !== "frame" || w.visible || w.clip) return;
2824
+ if (!root || root.kind !== "frame" || children.length !== 1 || !w || w.kind !== "frame" || w.visible || w.clip || w.cover) return;
1320
2825
  for (const v of variants) {
1321
2826
  const r = v.values[root.id] ?? {};
1322
2827
  const x = v.values[w.id] ?? {};
1323
- if (BOX_FIELDS.some((f) => !empty(r[f])) || PAINT_FIELDS.some((f) => !empty(x[f]))) return;
2828
+ if (BOX_FIELDS.some((f) => !empty(r[f])) || PAINT_FIELDS.some((f) => !empty(x[f])) || x.opacity !== void 0) return;
2829
+ if (v.hidden?.includes(w.id) || v.layout?.[w.id] || v.layout?.[root.id]) return;
1324
2830
  }
1325
2831
  for (const v of variants) {
1326
2832
  const r = v.values[root.id] ??= {};
@@ -1334,11 +2840,37 @@ function flattenWrapper(parts, variants, unmapped) {
1334
2840
  for (const u of unmapped) if (u.part === w.id) u.part = root.id;
1335
2841
  parts.splice(parts.indexOf(w), 1);
1336
2842
  }
1337
- function generateComponentSpec(name, opts) {
2843
+ function mergeFieldBoxes(parts, variants, unmapped, fields) {
2844
+ for (const box of parts.filter((p) => fields.has(p.id))) {
2845
+ const kids = parts.filter((p) => p.parent === box.id);
2846
+ const text = kids[0];
2847
+ if (kids.length !== 1 || text?.kind !== "text" || !box.parent || box.visible || box.cover) continue;
2848
+ const drawsNothing = variants.every((v) => {
2849
+ const x = v.values[box.id] ?? {};
2850
+ return BOX_FIELDS.every((f) => empty(x[f])) && PAINT_FIELDS.every((f) => empty(x[f])) && x.opacity === void 0 && !v.hidden?.includes(box.id) && !v.layout?.[box.id];
2851
+ });
2852
+ if (!drawsNothing) continue;
2853
+ text.parent = box.parent;
2854
+ if (box.source) text.source = box.source;
2855
+ for (const v of variants) delete v.values[box.id];
2856
+ for (const u of unmapped) if (u.part === box.id) u.part = text.id;
2857
+ parts.splice(parts.indexOf(box), 1);
2858
+ }
2859
+ }
2860
+ const REASON_ORDER = ["opens a popup", "draws SVG shapes", "uses CSS grid", "draws with ::before or ::after", "moves a part with a transform", "places layers over each other", "shows content computed while the page runs"];
2861
+ function mainReason(reasons) {
2862
+ const rank = (r) => {
2863
+ const i = REASON_ORDER.indexOf(r);
2864
+ return i < 0 ? REASON_ORDER.length : i;
2865
+ };
2866
+ return [...new Set(reasons)].sort((a, b) => rank(a) - rank(b))[0];
2867
+ }
2868
+ const CHECKED = { false: "false", true: "true", mixed: "mixed" };
2869
+ function generateComponentSpec(name, opts, view, setName = name, fixed = {}) {
1338
2870
  const reader = new Reader(opts);
1339
2871
  const src = reader.load(name);
1340
2872
  if (!src) throw new Error(`${name}: ${opts.componentsDir}/${name}/${name}.tsx was not found.`);
1341
- if (!src.fn) throw new Error(`${name}: no forwardRef(function ${name}(...)) or exported function ${name} in ${src.file}.`);
2873
+ if (!src.fn) throw new UnsupportedComponentError(name, "is shown from a hook or a provider, not placed as a component");
1342
2874
  const modules = /* @__PURE__ */ new Map();
1343
2875
  const addModules = (s) => {
1344
2876
  if (!s) return;
@@ -1366,16 +2898,49 @@ function generateComponentSpec(name, opts) {
1366
2898
  visit(s.sf);
1367
2899
  };
1368
2900
  findNested(src, 0);
1369
- const ordered = [...modules].sort(([a], [b]) => Number(a.startsWith(`${opts.componentsDir}/${name}/`)) - Number(b.startsWith(`${opts.componentsDir}/${name}/`)));
2901
+ const docs = readDocs(src.folder, opts);
2902
+ const fromDocs = previewElement(docs?.preview, name);
2903
+ const previewEl = fromDocs ?? storyElement(readStories(src.folder, opts), name);
2904
+ const previewKids = previewEl?.children?.some((c) => typeof c !== "string") ? previewEl.children : void 0;
2905
+ for (const c of docElements(previewKids)) {
2906
+ if (typeof c.c !== "string" || nestedNames.has(c.c) || !reader.exists(c.c)) continue;
2907
+ nestedNames.add(c.c);
2908
+ const other = reader.load(c.c);
2909
+ addModules(other);
2910
+ if (other) findNested(other, 1);
2911
+ }
2912
+ const ownDir = `${opts.componentsDir}/${src.folder}/`;
2913
+ const ordered = [...modules].sort(([a], [b]) => Number(a.startsWith(ownDir)) - Number(b.startsWith(ownDir)));
1370
2914
  const rules = [];
1371
2915
  const cssErrors = [];
2916
+ let page;
2917
+ const baseText = opts.baseCss ? opts.read(opts.baseCss) : void 0;
2918
+ if (baseText !== void 0) {
2919
+ const r = parseCssModule(opts.baseCss, baseText, 0);
2920
+ rules.push(...r.rules);
2921
+ const html = { tag: "html", classes: /* @__PURE__ */ new Set(), children: [], present: true, isRoot: false, inputLike: false };
2922
+ page = computeValues(declaredFor(r.rules, html, DEFAULT_STATE), void 0);
2923
+ }
1372
2924
  for (const [path, text] of ordered) {
1373
2925
  const r = parseCssModule(path, text, rules.length);
1374
2926
  rules.push(...r.rules);
1375
2927
  cssErrors.push(...r.errors);
1376
2928
  }
1377
- const base = evaluateVariant(reader, src, rules, {}, DEFAULT_STATE, opts.tokens);
1378
- if (!base) throw new Error(`${name}: could not read what the component renders.`);
2929
+ const baseProps = {};
2930
+ for (const p of src.props.values()) {
2931
+ const v = previewEl?.p?.[p.name];
2932
+ if (v === void 0 || v === null) continue;
2933
+ if (p.kind === "string" && typeof v === "string" && (!p.optional || p.name === "defaultValue")) baseProps[p.name] = v;
2934
+ if (p.kind === "other" && (typeof v === "object" || typeof v === "number" || typeof v === "string" && p.name === "defaultValue")) baseProps[p.name] = jsonValue(v);
2935
+ }
2936
+ const examples = {};
2937
+ for (const p of src.props.values()) {
2938
+ const v = previewEl?.p?.[p.name];
2939
+ if (p.kind === "string" && p.optional && typeof v === "string" && baseProps[p.name] === void 0) examples[p.name] = v;
2940
+ }
2941
+ const evalAt = (props, state) => evaluateVariant(reader, src, rules, { ...baseProps, ...props, ...fixed }, state, opts.tokens, previewKids, page, view, Object.keys(examples).length ? examples : void 0);
2942
+ const base = evalAt({}, DEFAULT_STATE);
2943
+ if (!base) throw new UnsupportedComponentError(name, "renders in a way the generator cannot read yet");
1379
2944
  const unmappedProps = [];
1380
2945
  const axes = [];
1381
2946
  const defaults = /* @__PURE__ */ new Map();
@@ -1385,8 +2950,8 @@ function generateComponentSpec(name, opts) {
1385
2950
  for (const [prop] of src.props) defaults.set(prop, scope.vars.get(prop));
1386
2951
  }
1387
2952
  for (const p of src.props.values()) {
1388
- if (p.kind !== "union" || !p.values) continue;
1389
- const changes = p.values.some((v) => evaluateVariant(reader, src, rules, { [p.name]: v }, DEFAULT_STATE, opts.tokens)?.classes !== base.classes);
2953
+ if (p.kind !== "union" || !p.values || fixed[p.name] !== void 0) continue;
2954
+ const changes = p.values.some((v) => evalAt({ [p.name]: v }, DEFAULT_STATE)?.classes !== base.classes);
1390
2955
  if (!changes) {
1391
2956
  unmappedProps.push({ part: "root", property: `prop ${p.name}`, value: p.values.join(" | "), reason: "does not change the styles" });
1392
2957
  continue;
@@ -1394,15 +2959,25 @@ function generateComponentSpec(name, opts) {
1394
2959
  const def = defaults.get(p.name);
1395
2960
  axes.push({ name: sentenceCase(p.name), prop: p.name, values: [...p.values], default: typeof def === "string" && p.values.includes(def) ? def : p.values[0] });
1396
2961
  }
2962
+ const checkable = ["checked", "defaultChecked", "indeterminate"].some((p) => src.props.has(p)) || [...src.baseUiModules].some((m) => /^(checkbox|switch|radio|toggle|menu)/.test(m));
2963
+ if (view !== "popup" && checkable) {
2964
+ const on = evalAt({}, { ...DEFAULT_STATE, checked: "true" });
2965
+ const off = evalAt({}, { ...DEFAULT_STATE, checked: "false" });
2966
+ if (on && off && drawn(on) !== drawn(off)) {
2967
+ const mixed = src.props.has("indeterminate") ? evalAt({}, { ...DEFAULT_STATE, checked: "mixed" }) : void 0;
2968
+ axes.push({ name: "Checked", values: mixed && drawn(mixed) !== drawn(on) ? ["false", "true", "mixed"] : ["false", "true"], default: "false" });
2969
+ }
2970
+ }
1397
2971
  const combos = product(axes);
1398
- const propsFor = (combo) => Object.fromEntries(axes.map((a) => [a.prop, combo[a.name]]));
2972
+ const propsFor = (combo) => Object.fromEntries(axes.filter((a) => a.prop).map((a) => [a.prop, combo[a.name]]));
2973
+ const stateFor = (combo, s) => combo.Checked ? { ...s, checked: CHECKED[combo.Checked] } : s;
1399
2974
  const canDisable = src.native === "button" || src.native === "input" || src.native === "textarea" || src.props.has("disabled");
1400
- const states = STATES.filter((s) => s.name !== "Disabled" || canDisable);
2975
+ const states = view === "popup" ? STATES.slice(0, 1) : STATES.filter((s) => s.name !== "Disabled" || canDisable);
1401
2976
  const byState = /* @__PURE__ */ new Map();
1402
2977
  for (const s of states) {
1403
2978
  const m = /* @__PURE__ */ new Map();
1404
2979
  for (const combo of combos) {
1405
- const ev = evaluateVariant(reader, src, rules, propsFor(combo), s.state, opts.tokens);
2980
+ const ev = evalAt(propsFor(combo), stateFor(combo, s.state));
1406
2981
  if (ev) m.set(JSON.stringify(combo), ev);
1407
2982
  }
1408
2983
  byState.set(s.name, m);
@@ -1410,51 +2985,149 @@ function generateComponentSpec(name, opts) {
1410
2985
  const differs = (s, from) => combos.some((c) => {
1411
2986
  const a = byState.get(s).get(JSON.stringify(c));
1412
2987
  const b = byState.get(from)?.get(JSON.stringify(c));
1413
- return a && b && !sameValues(a.values, b.values);
2988
+ return a && b && drawn(a) !== drawn(b);
1414
2989
  });
1415
2990
  const stateNames = states.map((s) => s.name).filter((s) => s === "Default" || differs(s, "Default") && (s !== "Pressed" || differs(s, "Hover")));
1416
2991
  if (stateNames.length > 1) axes.push({ name: "State", values: stateNames, default: "Default" });
1417
- const variants = [];
1418
- const unmapped = [];
2992
+ const blockers = /* @__PURE__ */ new Set();
2993
+ const evaluated = [];
1419
2994
  for (const combo of combos) {
1420
2995
  for (const s of stateNames.length > 1 ? stateNames : ["Default"]) {
1421
2996
  const ev = byState.get(s).get(JSON.stringify(combo));
1422
2997
  if (!ev) continue;
1423
- const props = stateNames.length > 1 ? { ...combo, State: s } : { ...combo };
1424
- variants.push({ props, values: ev.values });
1425
- unmapped.push(...ev.unmapped);
2998
+ evaluated.push({ props: stateNames.length > 1 ? { ...combo, State: s } : { ...combo }, ev });
2999
+ for (const b of ev.blockers) blockers.add(b);
3000
+ }
3001
+ }
3002
+ const order = [...base.built.parts.keys()];
3003
+ const owner = new Map(order.map((id) => [id, base]));
3004
+ for (const { ev } of evaluated) {
3005
+ let prev;
3006
+ for (const id of ev.built.parts.keys()) {
3007
+ const known = owner.get(id);
3008
+ if (known && known.built.parents.get(id) !== ev.built.parents.get(id)) blockers.add("changes its layers between variants");
3009
+ if (!known) {
3010
+ order.splice(prev === void 0 ? 0 : order.indexOf(prev) + 1, 0, id);
3011
+ owner.set(id, ev);
3012
+ }
3013
+ prev = id;
3014
+ }
3015
+ }
3016
+ for (const { ev } of evaluated) for (const id of order) if (!ev.built.parts.has(id)) ev.hidden.add(id);
3017
+ if (blockers.size) throw new UnsupportedComponentError(name, mainReason([...blockers]));
3018
+ for (const axis of [...axes]) {
3019
+ if (!axis.prop) continue;
3020
+ const key = (p) => JSON.stringify(Object.entries(p).filter(([k]) => k !== axis.name));
3021
+ const groups = /* @__PURE__ */ new Map();
3022
+ for (const e of evaluated) groups.set(key(e.props), [...groups.get(key(e.props)) ?? [], drawn(e.ev)]);
3023
+ if ([...groups.values()].some((g) => new Set(g).size > 1)) continue;
3024
+ axes.splice(axes.indexOf(axis), 1);
3025
+ unmappedProps.push({ part: "root", property: `prop ${axis.prop}`, value: axis.values.join(" | "), reason: "changes nothing the set draws (margins, or content it does not show), not drawn" });
3026
+ for (let i = evaluated.length - 1; i >= 0; i--) {
3027
+ const e = evaluated[i];
3028
+ if (e.props[axis.name] !== axis.default) {
3029
+ evaluated.splice(i, 1);
3030
+ continue;
3031
+ }
3032
+ delete e.props[axis.name];
1426
3033
  }
1427
3034
  }
1428
- if (variants.length > MAX_VARIANTS) throw new Error(`${name}: ${variants.length} variants, more than ${MAX_VARIANTS}. Split an axis before generating.`);
3035
+ const interactions = deriveInteractions(axes, evaluated, opts.tokens, /radio/i.test(setName));
3036
+ const drawnClasses = /* @__PURE__ */ new Set();
3037
+ const drawnElements = /* @__PURE__ */ new Map();
3038
+ for (const { ev } of evaluated)
3039
+ for (const n of ev.built.parts.values()) {
3040
+ for (const c of n.classes) drawnClasses.add(c);
3041
+ if (n.classes.length) drawnElements.set([...n.classes].sort().join(" "), [...n.classes]);
3042
+ }
3043
+ const motion = motionNotes(rules, drawnClasses, opts.tokens, view, [...drawnElements.values()]);
3044
+ const variants = [];
3045
+ const unmapped = [];
3046
+ const neverShown = new Set(order.filter((id) => evaluated.every((e) => e.ev.hidden.has(id))));
3047
+ for (const e of evaluated) {
3048
+ const ev = e.ev;
3049
+ const values = Object.fromEntries(Object.entries(ev.values).filter(([id]) => !neverShown.has(id)));
3050
+ const parentOf = (id) => owner.get(id)?.built.parents.get(id) ?? "";
3051
+ const hidden = order.filter((id) => ev.hidden.has(id) && !neverShown.has(id) && !neverShown.has(parentOf(id)));
3052
+ const layout = {};
3053
+ for (const [id, l] of ev.layouts) if (JSON.stringify(l) !== JSON.stringify(owner.get(id)?.layouts.get(id))) layout[id] = l;
3054
+ const icons = {};
3055
+ for (const [id, n] of ev.built.parts) if (n.kind === "icon" && n.icon && !neverShown.has(id)) icons[id] = iconComponentName(n.icon);
3056
+ const vectors = Object.fromEntries([...ev.vectors].filter(([id]) => !neverShown.has(id)));
3057
+ const pinOf = (x, id) => x.cover.has(id) ? { top: 0, right: 0, bottom: 0, left: 0 } : x.pins.get(id);
3058
+ const pins = {};
3059
+ for (const id of order) {
3060
+ const own = pinOf(ev, id);
3061
+ const base2 = owner.get(id) && pinOf(owner.get(id), id);
3062
+ if (own && base2 && JSON.stringify(own) !== JSON.stringify(base2) && !neverShown.has(id)) pins[id] = own;
3063
+ }
3064
+ variants.push({
3065
+ props: e.props,
3066
+ values,
3067
+ ...Object.keys(icons).length ? { slots: icons } : {},
3068
+ ...hidden.length ? { hidden } : {},
3069
+ ...Object.keys(layout).length ? { layout } : {},
3070
+ ...Object.keys(vectors).length ? { vectors } : {},
3071
+ ...Object.keys(pins).length ? { pins } : {}
3072
+ });
3073
+ unmapped.push(...ev.unmapped.filter((u) => !neverShown.has(u.part)));
3074
+ }
3075
+ if (variants.length > MAX_VARIANTS) throw new UnsupportedComponentError(name, `has ${variants.length} variants, more than ${MAX_VARIANTS}`);
1429
3076
  const parts = [];
1430
3077
  const properties = [];
1431
- for (const [id, n] of base.built.parts) {
1432
- const parent = base.built.parents.get(id);
3078
+ const dropped = /* @__PURE__ */ new Set();
3079
+ for (const id of order) {
3080
+ const at = owner.get(id);
3081
+ const n = at.built.parts.get(id);
3082
+ const parent = at.built.parents.get(id);
3083
+ if (neverShown.has(id) || parent && dropped.has(parent)) {
3084
+ dropped.add(id);
3085
+ continue;
3086
+ }
3087
+ const cls = n.classes[0]?.split("|")[1];
1433
3088
  const part = {
1434
3089
  id,
1435
- name: !parent ? name : n.kind === "text" ? n.text.property : n.visible ? n.visible.property : n.name ?? (n.kind === "icon" ? "Icon" : n.classes[0]?.split("|")[1] ?? n.tag),
1436
- kind: n.kind,
3090
+ name: !parent ? setName : n.kind === "text" ? n.text?.property ?? (cls ? sentenceCase(cls) : "Text") : n.visible ? n.visible.property.replace(/^Show /, "").replace(/^./, (c) => c.toUpperCase()) : n.name ?? (n.kind === "icon" ? "Icon" : cls ?? n.tag),
3091
+ kind: n.svgAttrs && n.tag === "svg" ? "vector" : n.kind,
1437
3092
  ...parent ? { parent } : {},
1438
3093
  ...n.classes.length ? { source: n.classes.map((c) => c.split("|").map((x, i) => i === 0 ? posix.basename(x) : `.${x}`).join(" ")).join(", ") } : {},
1439
- ...n.kind === "frame" ? { layout: base.layouts.get(id) } : {},
1440
- ...n.kind === "frame" && clips(base.computed.get(id)) ? { clip: true } : {},
1441
- ...!parent && fillsContainer(base.computed.get(id)) ? { fill: true } : {},
3094
+ ...n.kind === "frame" ? { layout: at.layouts.get(id) ?? layoutOf(at.computed.get(id) ?? /* @__PURE__ */ new Map()) } : {},
3095
+ ...n.kind === "frame" && clips(at.computed.get(id)) ? { clip: true } : {},
3096
+ ...!parent && fillsContainer(at.computed.get(id), n.tag) ? { fill: true } : {},
1442
3097
  ...n.text ? { text: n.text.property } : {},
1443
- ...n.visible ? { visible: n.visible.property } : {}
3098
+ ...n.chars !== void 0 ? { characters: n.chars } : {},
3099
+ ...n.visible ? { visible: n.visible.property } : {},
3100
+ ...at.cover.has(id) ? { cover: true } : {},
3101
+ ...at.pins.has(id) ? { pin: at.pins.get(id) } : {},
3102
+ ...at.stretch.has(id) ? { stretch: at.stretch.get(id) } : {},
3103
+ ...n.fraction !== void 0 ? { fraction: n.fraction } : {},
3104
+ ...n.at !== void 0 ? { at: n.at } : {},
3105
+ ...parent && (grows(at.computed.get(id)) || shrinksToFit(n, at.computed.get(id), at.layouts.get(parent))) ? { grow: true } : {},
3106
+ ...n.kind === "icon" && n.icon ? { icon: iconComponentName(n.icon) } : {}
1444
3107
  };
1445
3108
  parts.push(part);
1446
3109
  if (n.text && !properties.some((p) => p.name === n.text.property)) properties.push({ name: n.text.property, type: "TEXT", default: n.text.default, ...n.text.prop ? { prop: n.text.prop } : {} });
1447
3110
  }
1448
- for (const n of base.built.parts.values())
1449
- if (n.visible && !properties.some((p) => p.name === n.visible.property)) properties.push({ name: n.visible.property, type: "BOOLEAN", default: false, prop: n.visible.prop });
3111
+ for (const id of order) {
3112
+ const n = owner.get(id).built.parts.get(id);
3113
+ if (!dropped.has(id) && n.visible && !properties.some((p) => p.name === n.visible.property)) properties.push({ name: n.visible.property, type: "BOOLEAN", default: !!n.visible.on, prop: n.visible.prop });
3114
+ }
1450
3115
  const used = /* @__PURE__ */ new Set([...axes.map((a) => a.prop), ...properties.map((p) => p.prop), "disabled", "className", "children", "render"]);
3116
+ if (axes.some((a) => a.name === "Checked")) for (const p of ["checked", "defaultChecked", "indeterminate"]) used.add(p);
1451
3117
  for (const p of src.props.values()) {
1452
3118
  if (used.has(p.name) || p.kind === "function" || p.kind === "string" || p.kind === "other" || p.kind === "union") continue;
1453
3119
  unmappedProps.push({ part: "root", property: `prop ${p.name}`, value: p.kind, reason: p.kind === "node" ? "slot content is not drawn" : p.kind === "icon" ? "icon prop with no icon part" : "not drawn in this version" });
1454
3120
  }
1455
3121
  for (const e of cssErrors) unmappedProps.push({ part: "root", property: "css", value: e, reason: "selector not understood" });
3122
+ mergeFieldBoxes(parts, variants, unmapped, new Set([...base.built.parts].filter(([, n]) => n.inputLike).map(([id]) => id)));
1456
3123
  flattenWrapper(parts, variants, unmapped);
1457
- const swapped = addSwaps(name, src, parts, properties, axes, variants, opts);
3124
+ const compoundParts = new Set([...base.built.parts].filter(([, n]) => n.name).map(([id]) => id));
3125
+ const swapped = addSwaps(name, src, parts, properties, axes, variants, opts, compoundParts);
3126
+ for (const part of parts) {
3127
+ if (!base.built.parts.get(part.id)?.media || part.swap) continue;
3128
+ part.swap = { property: part.name, default: MEDIA_SLOT.name, options: [MEDIA_SLOT.name] };
3129
+ for (const v of variants) (v.slots ??= {})[part.id] = MEDIA_SLOT.name;
3130
+ }
1458
3131
  for (let i = unmappedProps.length - 1; i >= 0; i--) if (swapped.has(unmappedProps[i].property.replace(/^prop /, ""))) unmappedProps.splice(i, 1);
1459
3132
  const seen = /* @__PURE__ */ new Set();
1460
3133
  const dedup = [...unmappedProps, ...unmapped].filter((u) => {
@@ -1464,9 +3137,73 @@ function generateComponentSpec(name, opts) {
1464
3137
  return true;
1465
3138
  });
1466
3139
  const source = [src.file, ...[...modules.keys()].sort()];
1467
- const spec = { name, source, axes, properties, parts, variants, unmapped: dedup };
1468
- return { name, hash: hashSpec(spec), source, axes, properties, parts, variants, unmapped: dedup };
3140
+ const shownExamples = Object.fromEntries(Object.entries(examples).filter(([k]) => properties.some((p) => p.type === "BOOLEAN" && p.prop === k && p.default === true)));
3141
+ const exampleProps = Object.fromEntries(Object.entries({ ...baseProps, ...shownExamples }).filter(([, v]) => isData(v)));
3142
+ const example = previewKids && !base.built.root.fromExports || Object.keys(exampleProps).length ? { c: src.name, ...Object.keys(exampleProps).length ? { p: exampleProps } : {}, ...previewKids && !base.built.root.fromExports ? { children: previewKids } : {} } : void 0;
3143
+ const component = {
3144
+ ...src.folder !== setName ? { component: src.folder } : {},
3145
+ ...Object.keys(fixed).length ? { fixed: { ...fixed } } : {},
3146
+ ...view ? { view } : {},
3147
+ ...example ? { example } : {}
3148
+ };
3149
+ const motionFields = { ...interactions.length ? { interactions } : {}, ...motion.length ? { motion } : {} };
3150
+ const spec = { name: setName, ...component, source, axes, properties, parts, variants, unmapped: dedup, ...motionFields };
3151
+ return { name: setName, ...component, hash: hashSpec(spec), source, axes, properties, parts, variants, unmapped: dedup, ...motionFields };
3152
+ }
3153
+ function deriveInteractions(axes, evaluated, tokens, radio) {
3154
+ const byKey = new Map(evaluated.map((e) => [variantKeyOf(e.props), e]));
3155
+ const state = axes.find((a) => a.name === "State");
3156
+ const checked = axes.find((a) => a.name === "Checked");
3157
+ const rules = [];
3158
+ if (state) {
3159
+ const has = (v) => state.values.includes(v);
3160
+ if (has("Hover")) {
3161
+ if (checked) rules.push({ trigger: "MOUSE_ENTER", axis: "State", from: "Default", to: "Hover" }, { trigger: "MOUSE_LEAVE", axis: "State", from: "Hover", to: "Default" });
3162
+ else rules.push({ trigger: "ON_HOVER", axis: "State", from: "Default", to: "Hover" });
3163
+ }
3164
+ if (has("Pressed")) rules.push({ trigger: "ON_PRESS", axis: "State", from: has("Hover") ? "Hover" : "Default", to: "Pressed" });
3165
+ }
3166
+ if (checked) {
3167
+ const pairs = [["false", "true"]];
3168
+ if (!radio) pairs.push(["true", "false"]);
3169
+ if (checked.values.includes("mixed")) pairs.push(["mixed", "true"]);
3170
+ const only = (p) => p.State === void 0 || p.State === "Default" || p.State === "Hover";
3171
+ for (const [from, to] of pairs) rules.push({ trigger: "ON_CLICK", axis: "Checked", from, to, only });
3172
+ }
3173
+ const out = [];
3174
+ for (const r of rules) {
3175
+ const groups = /* @__PURE__ */ new Map();
3176
+ let candidates = 0;
3177
+ for (const e of evaluated) {
3178
+ if (e.props[r.axis] !== r.from) continue;
3179
+ candidates++;
3180
+ if (r.only && !r.only(e.props)) continue;
3181
+ const target = byKey.get(variantKeyOf({ ...e.props, [r.axis]: r.to }));
3182
+ if (!target || drawn(target.ev) === drawn(e.ev)) continue;
3183
+ const transition = transitionBetween(e.ev.computed, target.ev.computed, tokens);
3184
+ const k = JSON.stringify(transition);
3185
+ const g = groups.get(k) ?? { transition, keys: [] };
3186
+ g.keys.push(variantKeyOf(e.props));
3187
+ groups.set(k, g);
3188
+ }
3189
+ const total = [...groups.values()].reduce((n, g) => n + g.keys.length, 0);
3190
+ for (const g of groups.values()) out.push({ trigger: r.trigger, axis: r.axis, from: r.from, to: r.to, ...groups.size > 1 || total < candidates ? { variants: g.keys } : {}, transition: g.transition });
3191
+ }
3192
+ return out;
1469
3193
  }
3194
+ function specDrawsSomething(spec) {
3195
+ const kinds = new Map(spec.parts.map((p) => [p.id, p]));
3196
+ for (const v of spec.variants)
3197
+ for (const [id, vals] of Object.entries(v.values)) {
3198
+ const part = kinds.get(id);
3199
+ if (part?.kind === "icon" || part?.kind === "vector") return true;
3200
+ if (part?.kind === "text" && part.text !== "Label") return true;
3201
+ if (part?.kind === "text" && (vals.textDecoration !== void 0 || typeof vals.textFill === "string" && vals.textFill !== "color.text.default")) return true;
3202
+ if (vals.fill !== void 0 && vals.fill !== null || vals.stroke !== void 0 && vals.stroke !== null) return true;
3203
+ }
3204
+ return false;
3205
+ }
3206
+ const ICON_COMPONENT = "Icon";
1470
3207
  const COMMON_ICONS = ["Plus", "Minus", "Pencil", "Trash2", "Copy", "Search", "X", "Check", "ChevronDown", "ChevronRight", "Info", "AlertTriangle", "Star", "Settings", "ExternalLink", "Download", "Upload", "Ellipsis"];
1471
3208
  const FALLBACK_ICON = { Badge: "Info" };
1472
3209
  const DEFAULT_FALLBACK_ICON = "Plus";
@@ -1541,13 +3278,13 @@ function allStatesIcon(sf, name, prop) {
1541
3278
  if (decl) visit(decl);
1542
3279
  return found;
1543
3280
  }
1544
- function defaultIcon(name, prop, opts) {
1545
- const docs = readDocs(name, opts);
3281
+ function defaultIcon(name, prop, opts, folder = name) {
3282
+ const docs = readDocs(folder, opts);
1546
3283
  for (const el of docElements(docs?.preview)) {
1547
3284
  const v = el.c === name ? el.p?.[prop] : void 0;
1548
3285
  if (typeof v === "string" && /^[A-Z]\w*$/.test(v)) return v;
1549
3286
  }
1550
- return allStatesIcon(readStories(name, opts), name, prop) ?? FALLBACK_ICON[name] ?? DEFAULT_FALLBACK_ICON;
3287
+ return allStatesIcon(readStories(folder, opts), name, prop) ?? FALLBACK_ICON[name] ?? DEFAULT_FALLBACK_ICON;
1551
3288
  }
1552
3289
  function referencedIcons(name, opts) {
1553
3290
  const out = [];
@@ -1557,7 +3294,7 @@ function referencedIcons(name, opts) {
1557
3294
  return out;
1558
3295
  }
1559
3296
  const zeroInline = (v) => !!v && v.paddingLeft === 0 && v.paddingRight === 0;
1560
- function addSwaps(name, src, parts, properties, axes, variants, opts) {
3297
+ function addSwaps(name, src, parts, properties, axes, variants, opts, compound) {
1561
3298
  const props = /* @__PURE__ */ new Set();
1562
3299
  const iconProps = [...src.props.values()].filter((p) => p.kind === "icon");
1563
3300
  const defaultKey = variantKeyOf(Object.fromEntries(axes.map((a) => [a.name, a.default])));
@@ -1570,14 +3307,14 @@ function addSwaps(name, src, parts, properties, axes, variants, opts) {
1570
3307
  const visibleProp = part.visible ? properties.find((p) => p.type === "BOOLEAN" && p.name === part.visible)?.prop : void 0;
1571
3308
  const prop = visibleProp ?? iconProps.find((p) => !p.optional && !properties.some((x) => x.prop === p.name))?.name;
1572
3309
  if (!prop) continue;
1573
- const icon = iconComponentName(defaultIcon(name, prop, opts));
3310
+ const icon = iconComponentName(defaultIcon(name, prop, opts, src.folder));
1574
3311
  part.swap = { property: sentenceCase(prop), default: icon, prop };
1575
3312
  setSlot(part.id, () => icon);
1576
3313
  props.add(prop);
1577
3314
  continue;
1578
3315
  }
1579
3316
  const parent = parts.find((p) => p.id === part.parent);
1580
- if (part.kind !== "text" || !part.text || !parent?.parent || parent.kind !== "frame") continue;
3317
+ if (part.kind !== "text" || !part.text || !parent?.parent || parent.kind !== "frame" || !compound.has(parent.id)) continue;
1581
3318
  if (!variants.some((v) => zeroInline(v.values[parent.id]))) continue;
1582
3319
  const textSlot = `Slot/${name} ${part.text.toLowerCase()} text`;
1583
3320
  const media = MEDIA_SLOT.name;
@@ -1645,8 +3382,12 @@ function generateIcons(components, opts, slots = []) {
1645
3382
  const index = lucideIndex(dir, opts);
1646
3383
  const names = [];
1647
3384
  for (const c of components) {
1648
- for (const p of c.parts) if (p.swap && !p.swap.options) names.push(p.swap.default.replace(/^Icon\//, ""));
1649
- names.push(...referencedIcons(c.name, opts));
3385
+ for (const p of c.parts) {
3386
+ if (p.swap && !p.swap.options) names.push(p.swap.default.replace(/^Icon\//, ""));
3387
+ if (p.icon) names.push(p.icon.replace(/^Icon\//, ""));
3388
+ }
3389
+ for (const v of c.variants) for (const x of Object.values(v.slots ?? {})) if (x.startsWith("Icon/")) names.push(x.slice(5));
3390
+ names.push(...referencedIcons(c.component ?? c.name, opts));
1650
3391
  }
1651
3392
  for (const s of slots) if (s.icon) names.push(s.icon);
1652
3393
  names.push(...COMMON_ICONS);
@@ -1677,8 +3418,7 @@ function generateSlots(components) {
1677
3418
  if (media) out.push(MEDIA_SLOT);
1678
3419
  return out;
1679
3420
  }
1680
- function generateComponentSpecs(names, opts) {
1681
- const components = names.map((n) => generateComponentSpec(n, opts));
3421
+ function specsFile(components, opts, coverage) {
1682
3422
  const slots = generateSlots(components);
1683
3423
  return {
1684
3424
  version: COMPONENT_SPECS_VERSION,
@@ -1686,27 +3426,164 @@ function generateComponentSpecs(names, opts) {
1686
3426
  components,
1687
3427
  icons: generateIcons(components, opts, slots),
1688
3428
  iconStyle: ICON_STYLE,
1689
- slots
3429
+ slots,
3430
+ ...coverage ? { coverage } : {}
1690
3431
  };
1691
3432
  }
1692
- const SPEC_COMPONENTS = ["Button", "IconButton", "Badge", "Tag", "Input", "Card"];
3433
+ function generateComponentSpecs(names, opts) {
3434
+ return specsFile(
3435
+ names.map((n) => generateComponentSpec(n, opts)),
3436
+ opts
3437
+ );
3438
+ }
3439
+ function splitByLayers(folder, opts) {
3440
+ const src = new Reader(opts).load(folder);
3441
+ for (const p of src?.props.values() ?? []) {
3442
+ if (p.kind !== "union" || !p.values) continue;
3443
+ const out = [];
3444
+ for (const v of p.values) {
3445
+ try {
3446
+ const spec = generateComponentSpec(folder, opts, void 0, `${folder}/${sentenceCase(v)}`, { [p.name]: v });
3447
+ if (specDrawsSomething(spec)) out.push(spec);
3448
+ } catch {
3449
+ out.length = 0;
3450
+ break;
3451
+ }
3452
+ }
3453
+ if (out.length === p.values.length) return out;
3454
+ }
3455
+ return [];
3456
+ }
3457
+ const PAINTS = /(^|[;{\s])(color|background(-color)?|border(-(top|right|bottom|left))?(-color)?|box-shadow|fill|stroke)\s*:(?!\s*(0[;\s}]|none|transparent))/m;
3458
+ function generateAllComponentSpecs(folders, opts) {
3459
+ const withList = { ...opts, components: opts.components ?? folders };
3460
+ const reader = new Reader(withList);
3461
+ const components = [];
3462
+ const coverage = [];
3463
+ const cssOf = (folder) => [...reader.load(folder)?.styles.values() ?? []].map((path) => (opts.read(path) ?? "").replace(/\/\*[\s\S]*?\*\//g, "")).join("\n");
3464
+ const paints = (folder) => PAINTS.test(cssOf(folder));
3465
+ for (const folder of [...folders].sort()) {
3466
+ const reasons = [];
3467
+ const attempt = (name, view, setName) => {
3468
+ try {
3469
+ const spec = generateComponentSpec(name, withList, view, setName);
3470
+ return specDrawsSomething(spec) ? spec : void 0;
3471
+ } catch (err) {
3472
+ reasons.push(err instanceof UnsupportedComponentError ? err.reason : "renders in a way the generator cannot read yet");
3473
+ return void 0;
3474
+ }
3475
+ };
3476
+ const exports = [folder, ...[...reader.load(folder)?.exported.keys() ?? []].filter((n) => n !== folder)];
3477
+ const sets = [];
3478
+ let main = attempt(folder);
3479
+ if (!main && reasons.includes("changes its layers between variants")) {
3480
+ const split = splitByLayers(folder, withList);
3481
+ if (split.length) {
3482
+ sets.push(...split);
3483
+ main = split[0];
3484
+ reasons.length = 0;
3485
+ }
3486
+ }
3487
+ if (main && !sets.length) {
3488
+ sets.push(main);
3489
+ const compound = new Set(main.parts.map((p) => p.name));
3490
+ const drawnIn = new Set(main.parts.map((p) => p.source).filter(Boolean));
3491
+ for (const other of exports.slice(1)) {
3492
+ if (other.startsWith(folder) && compound.has(other.slice(folder.length))) continue;
3493
+ const spec = attempt(other);
3494
+ if (!spec) continue;
3495
+ const readsParent = spec.unmapped.some((u) => /^--_[\w-]+ is not a token$/.test(u.reason));
3496
+ const duplicate = drawnIn.has(spec.parts[0]?.source) && !spec.properties.length && spec.axes.every((a) => a.name === "State");
3497
+ if (readsParent || duplicate) continue;
3498
+ sets.push(spec);
3499
+ }
3500
+ }
3501
+ const hasPopup = /\.(Portal|Popup)\b/.test(opts.read(`${opts.componentsDir}/${folder}/${folder}.tsx`) ?? "");
3502
+ if (!main && hasPopup) {
3503
+ reasons.length = 0;
3504
+ for (const name of exports) {
3505
+ const closedSpec = attempt(name, "without-popup");
3506
+ const closed = closedSpec?.parts.some((p) => p.source?.includes(`${folder}.module.css`)) ? closedSpec : void 0;
3507
+ if (closed && !sets.some((x) => x.name === closed.name)) sets.push(closed);
3508
+ const suffix = name === folder ? "" : name.startsWith(folder) ? name.slice(folder.length) : name;
3509
+ const popupName = `${folder}/${sets.some((x) => x.name === `${folder}/Popup`) ? suffix || "Popup" : "Popup"}`;
3510
+ const fnNode = reader.load(name)?.fn;
3511
+ const ownsPortal = !!fnNode && /\.Portal\b/.test(fnNode.getText());
3512
+ const open = ownsPortal ? attempt(name, "popup", popupName) : void 0;
3513
+ if (open && !sets.some((x) => x.name === open.name)) sets.push(open);
3514
+ }
3515
+ }
3516
+ if (!sets.length && folder === ICON_COMPONENT) {
3517
+ coverage.push({ name: folder, status: "generated", reason: "drawn as icon components on the Icons page" });
3518
+ continue;
3519
+ }
3520
+ if (!sets.length) {
3521
+ const fileText = opts.read(`${opts.componentsDir}/${folder}/${folder}.tsx`) ?? "";
3522
+ const reason = /\.(Portal|Popup|Positioner)\b/.test(fileText) && !reasons.length ? "opens a popup" : reasons.length ? mainReason(reasons) : void 0;
3523
+ const drafted = draftedSpec(folder, withList);
3524
+ if (drafted) {
3525
+ components.push(drafted);
3526
+ coverage.push({ name: folder, status: "drafted", reason: drafted.drafted.reason, ...drafted.name !== folder ? { sets: [drafted.name] } : {} });
3527
+ continue;
3528
+ }
3529
+ if (!paints(folder)) coverage.push({ name: folder, status: "layout", reason: /inset\(50%\)/.test(cssOf(folder)) ? "screen reader text only, nothing to draw" : "layout only, use auto layout" });
3530
+ else coverage.push({ name: folder, status: "unsupported", reason: reason ?? "draws nothing the generator can read yet" });
3531
+ continue;
3532
+ }
3533
+ components.push(...sets);
3534
+ coverage.push({ name: folder, status: "generated", ...sets.length > 1 || sets[0].name !== folder ? { sets: sets.map((x) => x.name) } : {} });
3535
+ }
3536
+ return specsFile(components, withList, coverage);
3537
+ }
3538
+ const overridesDirOf = (componentsDir) => posix.join(posix.dirname(posix.dirname(componentsDir)), "component-spec-overrides");
3539
+ function draftedCodeHash(files, read) {
3540
+ return specHash(files.map((f) => `${f}
3541
+ ${read(f) ?? ""}`).join("\n"));
3542
+ }
3543
+ function draftedSpec(folder, opts) {
3544
+ const path = `${opts.overridesDir ?? overridesDirOf(opts.componentsDir)}/${folder}.json`;
3545
+ const text = opts.read(path);
3546
+ if (text === void 0) return void 0;
3547
+ let raw;
3548
+ try {
3549
+ raw = JSON.parse(text);
3550
+ } catch {
3551
+ throw new Error(`${path} is not valid JSON.`);
3552
+ }
3553
+ if (raw.source !== "drafted") throw new Error(`${path}: a drafted spec says "source": "drafted".`);
3554
+ const files = Array.isArray(raw.files) ? raw.files.filter((f) => typeof f === "string") : [];
3555
+ if (!files.length) throw new Error(`${path}: list the code it was drafted from in "files".`);
3556
+ const reason = typeof raw.reason === "string" && raw.reason ? raw.reason : "the generator cannot read it yet";
3557
+ const { $comment: _c, source: _s, files: _f, reason: _r, codeHash, hash: _h, drafted: _d, ...fields } = raw;
3558
+ const stale = typeof codeHash === "string" && codeHash !== draftedCodeHash(files, opts.read);
3559
+ const body = { ...fields, drafted: { reason, ...stale ? { stale: true } : {} } };
3560
+ const problems = validateComponentSpec({ ...body }, new Map([...typesOf(opts.tokens)].map(([k, v]) => [k, v.type])));
3561
+ if (problems.length) throw new Error(`${path} does not match the component spec format:
3562
+ - ${problems.join("\n- ")}`);
3563
+ const { name, axes, properties, parts, variants, unmapped, ...rest } = body;
3564
+ const spec = { name, ...rest, source: files, axes, properties, parts, variants, unmapped };
3565
+ return { name, ...rest, hash: hashSpec(spec), source: files, axes, properties, parts, variants, unmapped };
3566
+ }
1693
3567
  function tokenCssIndex(files, opts = {}) {
1694
3568
  const set = buildTokenSet(files, opts.layout ?? {});
1695
3569
  const out = /* @__PURE__ */ new Map();
3570
+ const ctx = defaultContext(set);
1696
3571
  for (const t of set.tokens.values()) {
1697
3572
  const v = tokenCssVar(t.path, opts.prefix, opts.separator);
1698
- if (!out.has(v)) out.set(v, { path: t.path, type: t.type });
3573
+ if (out.has(v)) continue;
3574
+ const value = t.type === "duration" || t.type === "cubicBezier" ? resolve$1(set.byPath, t.path, ctx).value : void 0;
3575
+ out.set(v, { path: t.path, type: t.type, ...value !== void 0 ? { value } : {} });
1699
3576
  }
1700
3577
  return out;
1701
3578
  }
1702
- function generateRepoComponentSpecs(root, names = SPEC_COMPONENTS) {
3579
+ function repoSpecOptions(root) {
1703
3580
  const configText = readFileSync(join(root, CONFIG_FILE), "utf8");
1704
3581
  const config = loadConfig(configText);
1705
3582
  const tokensRoot = join(root, config.tokens.root);
1706
3583
  const files = {};
1707
- const walk2 = (dir) => {
1708
- for (const n of readdirSync(dir)) {
1709
- const full = join(dir, n);
3584
+ const walk2 = (dir2) => {
3585
+ for (const n of readdirSync(dir2)) {
3586
+ const full = join(dir2, n);
1710
3587
  if (statSync(full).isDirectory()) walk2(full);
1711
3588
  else if (n.endsWith(".tokens.json")) files[relative(tokensRoot, full).split("\\").join("/")] = readFileSync(full, "utf8");
1712
3589
  }
@@ -1721,7 +3598,14 @@ function generateRepoComponentSpecs(root, names = SPEC_COMPONENTS) {
1721
3598
  }
1722
3599
  };
1723
3600
  const componentsDir = config.components?.dir ?? "packages/react/src/components";
1724
- return generateComponentSpecs(names, { componentsDir, read, tokens, styleSuffix: config.components?.styleSuffix });
3601
+ const dir = join(root, componentsDir);
3602
+ const folders = readdirSync(dir).filter((n) => /^[A-Z]/.test(n) && statSync(join(dir, n)).isDirectory() && read(`${componentsDir}/${n}/${n}.tsx`) !== void 0).sort();
3603
+ const baseCss = posix.join(posix.dirname(componentsDir), "styles/base.css");
3604
+ return { opts: { componentsDir, read, tokens, styleSuffix: config.components?.styleSuffix, components: folders, ...read(baseCss) !== void 0 ? { baseCss } : {} }, folders };
3605
+ }
3606
+ function generateRepoComponentSpecs(root, names) {
3607
+ const { opts, folders } = repoSpecOptions(root);
3608
+ return names ? generateComponentSpecs(names, opts) : generateAllComponentSpecs(folders, opts);
1725
3609
  }
1726
3610
  const serializeComponentSpecs = (f) => `${JSON.stringify(f, null, 2)}
1727
3611
  `;
@@ -1731,16 +3615,21 @@ export {
1731
3615
  ICON_STYLE,
1732
3616
  INHERITED,
1733
3617
  MEDIA_SLOT,
1734
- SPEC_COMPONENTS,
3618
+ UnsupportedComponentError,
1735
3619
  branchExists,
3620
+ changedProperties,
1736
3621
  collectAuditFiles,
1737
3622
  commitToNewLocalBranch,
1738
3623
  computeValues,
1739
3624
  currentBranch,
1740
3625
  declaredFor,
3626
+ draftedCodeHash,
3627
+ draftedSpec,
3628
+ entryFor,
1741
3629
  expandDecl,
1742
3630
  findDesignSystemRoot,
1743
3631
  fixCss,
3632
+ generateAllComponentSpecs,
1744
3633
  generateComponentSpec,
1745
3634
  generateComponentSpecs,
1746
3635
  generateIcons,
@@ -1748,6 +3637,7 @@ export {
1748
3637
  generateSlots,
1749
3638
  git,
1750
3639
  isGitRepo,
3640
+ jsxText,
1751
3641
  listFilesAt,
1752
3642
  loadAllowlist,
1753
3643
  loadDesignSystem,
@@ -1756,14 +3646,25 @@ export {
1756
3646
  localTags,
1757
3647
  matchComplex,
1758
3648
  mediaApplies,
3649
+ motionNotes,
3650
+ openingTransitions,
1759
3651
  originRepo,
3652
+ overridesDirOf,
1760
3653
  parseCssModule,
1761
3654
  parseSelector,
3655
+ parseTransitions,
3656
+ repoSpecOptions,
3657
+ resolveDuration,
3658
+ resolveEasing,
1762
3659
  runAudit,
1763
3660
  sentenceCase,
1764
3661
  serializeComponentSpecs,
3662
+ shorthandOf,
1765
3663
  showFile,
3664
+ specDrawsSomething,
1766
3665
  splitTop,
1767
- tokenCssIndex
3666
+ substituteVars,
3667
+ tokenCssIndex,
3668
+ transitionBetween
1768
3669
  };
1769
3670
  //# sourceMappingURL=index.js.map