@bridge-ds/core 0.5.0 → 0.6.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,10 +2,10 @@ 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, t as tokenCssVar, k as COMPONENT_SPECS_VERSION, n as COLOR_FIELDS } from "../component-spec-GFVEE6Fc.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
- 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-4Z008HSf.js";
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";
9
9
  import ts from "typescript";
10
10
  function git(root, args, opts = {}) {
11
11
  return new Promise((resolve2, reject) => {
@@ -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,10 +773,12 @@ 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
  }
@@ -717,7 +795,6 @@ const REPORTED = {
717
795
  "box-shadow": "shadows are code-only tokens (no Figma variable)",
718
796
  outline: "focus rings are not drawn",
719
797
  transform: "transforms are not drawn",
720
- opacity: "opacity is not mapped yet",
721
798
  filter: "filters are not drawn",
722
799
  "margin-left": "margins have no auto layout equivalent",
723
800
  "margin-right": "margins have no auto layout equivalent",
@@ -744,8 +821,25 @@ class Reader {
744
821
  dirOf(name) {
745
822
  return `${this.opts.componentsDir}/${name}`;
746
823
  }
824
+ /** Export name -> component folder, for components a file exports besides its own (Radio in RadioGroup). */
825
+ exportIndex;
826
+ /** The folder a component comes from: its own, or the folder whose file exports it. */
827
+ folderOf(name) {
828
+ if (this.opts.read(`${this.dirOf(name)}/${name}.tsx`) !== void 0) return name;
829
+ if (!this.exportIndex) {
830
+ this.exportIndex = /* @__PURE__ */ new Map();
831
+ for (const folder of this.opts.components ?? []) {
832
+ const text = this.opts.read(`${this.dirOf(folder)}/${folder}.tsx`) ?? "";
833
+ for (const m of text.matchAll(/forwardRef[^(]*\(\s*function\s+([A-Z]\w*)|export function ([A-Z]\w*)/g)) {
834
+ const n = m[1] ?? m[2];
835
+ if (n !== folder && !this.exportIndex.has(n)) this.exportIndex.set(n, folder);
836
+ }
837
+ }
838
+ }
839
+ return this.exportIndex.get(name);
840
+ }
747
841
  exists(name) {
748
- return this.opts.read(`${this.dirOf(name)}/${name}.tsx`) !== void 0;
842
+ return this.folderOf(name) !== void 0;
749
843
  }
750
844
  /** `export type X = ...` of a component file and of the component files it imports from. */
751
845
  aliases(file, sf, depth = 0) {
@@ -769,19 +863,56 @@ class Reader {
769
863
  }
770
864
  load(name) {
771
865
  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);
866
+ const folder = this.folderOf(name);
867
+ const file = `${this.dirOf(folder ?? name)}/${folder ?? name}.tsx`;
868
+ const text = folder ? this.opts.read(file) : void 0;
774
869
  if (text === void 0) {
775
870
  this.cache.set(name, null);
776
871
  return null;
777
872
  }
778
873
  const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
779
874
  const styles = /* @__PURE__ */ new Map();
875
+ const baseUi = /* @__PURE__ */ new Set();
876
+ const helpers = /* @__PURE__ */ new Map();
877
+ const lucide = /* @__PURE__ */ new Map();
878
+ const consts = /* @__PURE__ */ new Map();
879
+ const baseUiModules = /* @__PURE__ */ new Set();
880
+ const fnConsts = /* @__PURE__ */ new Map();
881
+ const imported = /* @__PURE__ */ new Map();
780
882
  for (const st of sf.statements) {
781
- if (!ts.isImportDeclaration(st) || !st.importClause?.name) continue;
883
+ if (ts.isVariableStatement(st))
884
+ for (const d of st.declarationList.declarations) {
885
+ if (!ts.isIdentifier(d.name) || !d.initializer) continue;
886
+ const init = skipCasts(d.initializer);
887
+ if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) fnConsts.set(d.name.text, init);
888
+ else if (!/forwardRef|^\(/.test(d.initializer.getText(sf).slice(0, 12))) consts.set(d.name.text, d.initializer);
889
+ }
890
+ if (ts.isFunctionDeclaration(st) && st.name && /^[a-z]/.test(st.name.text) && st.body) helpers.set(st.name.text, st);
891
+ if (!ts.isImportDeclaration(st) || !st.importClause) continue;
782
892
  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)));
893
+ if (spec.startsWith(".") && !/\.css$/.test(spec) && !/^\.\.\/\w+$/.test(spec)) {
894
+ const b = st.importClause.namedBindings;
895
+ const mod = b && ts.isNamedImports(b) ? this.plainModule(posix.normalize(posix.join(this.dirOf(folder), spec))) : void 0;
896
+ if (mod && b && ts.isNamedImports(b))
897
+ for (const e of b.elements) {
898
+ const f = mod[(e.propertyName ?? e.name).text];
899
+ if (typeof f === "function") imported.set(e.name.text, f);
900
+ }
901
+ }
902
+ if (spec === "lucide-react") {
903
+ const b = st.importClause.namedBindings;
904
+ if (b && ts.isNamedImports(b)) for (const e of b.elements) lucide.set(e.name.text, (e.propertyName ?? e.name).text);
905
+ continue;
906
+ }
907
+ if (spec.startsWith("@base-ui/react")) {
908
+ baseUiModules.add(spec.replace(/^@base-ui\/react\/?/, ""));
909
+ const b = st.importClause.namedBindings;
910
+ if (st.importClause.name) baseUi.add(st.importClause.name.text);
911
+ if (b && ts.isNamedImports(b)) for (const e of b.elements) baseUi.add(e.name.text);
912
+ continue;
913
+ }
914
+ if (!st.importClause.name || !spec.endsWith(this.opts.styleSuffix ?? ".module.css")) continue;
915
+ styles.set(st.importClause.name.text, posix.normalize(posix.join(this.dirOf(folder), spec)));
785
916
  }
786
917
  const aliases = this.aliases(file, sf);
787
918
  const props = /* @__PURE__ */ new Map();
@@ -801,10 +932,40 @@ class Reader {
801
932
  const fns = componentFunctions(sf);
802
933
  const fn = fns.get(name);
803
934
  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 };
935
+ const src = { name, folder, file, sf, styles, props, native, fn, subcomponents, exported: fns, baseUi, helpers, lucide, consts, baseUiModules, fnConsts, imported };
805
936
  this.cache.set(name, src);
806
937
  return src;
807
938
  }
939
+ modules = /* @__PURE__ */ new Map();
940
+ /**
941
+ * Exports of a plain module of the package (`utils/color.ts`): no imports, no JSX, so it runs on its own. Undefined
942
+ * for anything else (a module that imports React or another file).
943
+ */
944
+ plainModule(path) {
945
+ if (this.modules.has(path)) return this.modules.get(path);
946
+ let out;
947
+ const text = this.opts.read(`${path}.ts`) ?? this.opts.read(`${path}/index.ts`);
948
+ if (text !== void 0) {
949
+ const sf = ts.createSourceFile(path, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
950
+ const plain = !sf.statements.some((s) => ts.isImportDeclaration(s) || ts.isExportDeclaration(s) && !!s.moduleSpecifier);
951
+ if (plain) {
952
+ try {
953
+ const js = ts.transpileModule(text, { compilerOptions: { target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.CommonJS } }).outputText;
954
+ const exports = {};
955
+ new Function("exports", "module", js)(exports, { exports });
956
+ out = exports;
957
+ } catch {
958
+ out = void 0;
959
+ }
960
+ }
961
+ }
962
+ this.modules.set(path, out);
963
+ return out;
964
+ }
965
+ }
966
+ function skipCasts(e) {
967
+ while (ts.isParenthesizedExpression(e) || ts.isAsExpression(e) || ts.isSatisfiesExpression(e) || ts.isNonNullExpression(e)) e = e.expression;
968
+ return e;
808
969
  }
809
970
  function classifyType(text, aliases, depth = 0) {
810
971
  const t = text.trim();
@@ -833,7 +994,10 @@ function componentFunctions(sf) {
833
994
  }
834
995
  const CHILDREN = /* @__PURE__ */ Symbol("children");
835
996
  const OPAQUE = /* @__PURE__ */ Symbol("opaque");
997
+ const isFnRef = (v) => !!v && typeof v === "object" && "fnRef" in v;
836
998
  const isClass = (v) => !!v && typeof v === "object" && "cls" in v;
999
+ const isTextRef = (v) => !!v && typeof v === "object" && "textProp" in v;
1000
+ const isLucide = (v) => !!v && typeof v === "object" && "lucide" in v;
837
1001
  const isStyles = (v) => !!v && typeof v === "object" && "module" in v;
838
1002
  const isJsx = (v) => !!v && typeof v === "object" && "jsx" in v;
839
1003
  function lookup(scope, name) {
@@ -847,7 +1011,12 @@ function lookup(scope, name) {
847
1011
  }
848
1012
  const style = scope.src.styles.get(name);
849
1013
  if (style) return { module: style };
850
- if (name === "undefined") return void 0;
1014
+ const icon = scope.src.lucide.get(name);
1015
+ if (icon) return { lucide: icon };
1016
+ const top = scope.src.consts.get(name);
1017
+ if (top) return evaluate(top, { ...scope, vars: /* @__PURE__ */ new Map(), locals: /* @__PURE__ */ new Map() });
1018
+ const fnConst = scope.src.fnConsts.get(name);
1019
+ if (fnConst) return { fnRef: fnConst, scope: { ...scope, vars: /* @__PURE__ */ new Map(), locals: /* @__PURE__ */ new Map(), toggles: /* @__PURE__ */ new Map() } };
851
1020
  return void 0;
852
1021
  }
853
1022
  function classList(v, out = []) {
@@ -872,6 +1041,8 @@ function evaluate(e, scope) {
872
1041
  if (ts.isPropertyAccessExpression(e)) {
873
1042
  const obj = evaluate(e.expression, scope);
874
1043
  if (isStyles(obj)) return { cls: `${obj.module}|${e.name.text}` };
1044
+ if ((Array.isArray(obj) || typeof obj === "string") && e.name.text === "length") return obj.length;
1045
+ if (obj === OPAQUE) return OPAQUE;
875
1046
  if (obj && typeof obj === "object" && !Array.isArray(obj) && !isClass(obj) && !isJsx(obj)) return obj[e.name.text];
876
1047
  return void 0;
877
1048
  }
@@ -879,9 +1050,20 @@ function evaluate(e, scope) {
879
1050
  const obj = evaluate(e.expression, scope);
880
1051
  const key = evaluate(e.argumentExpression, scope);
881
1052
  if (isStyles(obj) && typeof key === "string") return { cls: `${obj.module}|${key}` };
1053
+ if (Array.isArray(obj) && typeof key === "number") return obj[key];
1054
+ if (obj && typeof obj === "object" && !Array.isArray(obj) && (typeof key === "string" || typeof key === "number") && !isClass(obj) && !isJsx(obj)) return obj[key];
882
1055
  return void 0;
883
1056
  }
884
1057
  if (ts.isPrefixUnaryExpression(e) && e.operator === ts.SyntaxKind.ExclamationToken) return !truthy(evaluate(e.operand, scope));
1058
+ if (ts.isPrefixUnaryExpression(e) && e.operator === ts.SyntaxKind.MinusToken) {
1059
+ const v = evaluate(e.operand, scope);
1060
+ return typeof v === "number" ? -v : OPAQUE;
1061
+ }
1062
+ if (ts.isTypeOfExpression(e)) {
1063
+ const v = evaluate(e.expression, scope);
1064
+ if (v === OPAQUE || isJsx(v)) return OPAQUE;
1065
+ return v === null ? "object" : Array.isArray(v) ? "object" : typeof v === "symbol" ? OPAQUE : typeof v;
1066
+ }
885
1067
  if (ts.isConditionalExpression(e)) return truthy(evaluate(e.condition, scope)) ? evaluate(e.whenTrue, scope) : evaluate(e.whenFalse, scope);
886
1068
  if (ts.isBinaryExpression(e)) {
887
1069
  const op = e.operatorToken.kind;
@@ -911,17 +1093,273 @@ function evaluate(e, scope) {
911
1093
  return l != r;
912
1094
  // eslint-disable-line eqeqeq
913
1095
  default:
914
- return OPAQUE;
1096
+ return arithmetic(op, l, r);
915
1097
  }
916
1098
  }
917
1099
  if (ts.isCallExpression(e)) {
918
1100
  const callee = e.expression.getText(sf);
919
1101
  if (callee === "cx" || callee === "clsx" || callee === "classNames") return e.arguments.map((a) => evaluate(a, scope)).filter(truthy);
1102
+ if (callee === "mergeProps") {
1103
+ const out = {};
1104
+ for (const a of e.arguments) {
1105
+ const v = evaluate(a, scope);
1106
+ if (v && typeof v === "object" && !Array.isArray(v) && !isJsx(v) && !isClass(v)) Object.assign(out, v);
1107
+ }
1108
+ return out;
1109
+ }
1110
+ const math = /^Math\.(max|min|floor|ceil|round|abs)$/.exec(callee);
1111
+ if (math) {
1112
+ const args = e.arguments.map((a) => evaluate(a, scope));
1113
+ return args.every((a) => typeof a === "number") ? Math[math[1]](...args) : OPAQUE;
1114
+ }
1115
+ const objFn = /^Object\.(entries|keys|values)$/.exec(callee);
1116
+ if (objFn && e.arguments[0]) {
1117
+ const o = evaluate(e.arguments[0], scope);
1118
+ if (o === void 0 || o === null) return [];
1119
+ if (o && typeof o === "object" && !Array.isArray(o) && !isJsx(o) && !isClass(o)) {
1120
+ const rec = o;
1121
+ return objFn[1] === "keys" ? Object.keys(rec) : objFn[1] === "values" ? Object.values(rec) : Object.entries(rec).map(([k, v]) => [k, v]);
1122
+ }
1123
+ return OPAQUE;
1124
+ }
1125
+ if ((callee === "createElement" || callee === "React.createElement") && e.arguments[0]) {
1126
+ const tag = evaluate(e.arguments[0], scope);
1127
+ const props = e.arguments[1] ? evaluate(e.arguments[1], scope) : void 0;
1128
+ if (typeof tag !== "string") return OPAQUE;
1129
+ return { element: tag, props: props && typeof props === "object" && !Array.isArray(props) ? props : {}, kids: e.arguments.slice(2).map((a) => evaluate(a, scope)) };
1130
+ }
1131
+ if ((callee === "createContext" || callee === "React.createContext") && e.arguments[0]) return evaluate(e.arguments[0], scope);
1132
+ if ((callee === "useContext" || callee === "React.useContext") && e.arguments[0]) {
1133
+ const key = e.arguments[0].getText(sf);
1134
+ return scope.contexts?.has(key) ? scope.contexts.get(key) : evaluate(e.arguments[0], scope);
1135
+ }
1136
+ if (callee === "useRender" && e.arguments[0]) {
1137
+ const o = evaluate(e.arguments[0], scope);
1138
+ const props = o?.props && typeof o.props === "object" && !Array.isArray(o.props) ? o.props : {};
1139
+ return { element: typeof o?.defaultTagName === "string" ? o.defaultTagName : "div", props, kids: [] };
1140
+ }
1141
+ if ((callee === "useMemo" || callee === "React.useMemo") && e.arguments[0]) return callArrow(e.arguments[0], [], scope);
1142
+ if (callee === "Array.isArray" && e.arguments[0]) {
1143
+ const v = evaluate(e.arguments[0], scope);
1144
+ return v === OPAQUE || isFnRef(v) ? OPAQUE : Array.isArray(v);
1145
+ }
1146
+ if (callee === "Array.from" && e.arguments[0]) {
1147
+ const v = evaluate(e.arguments[0], scope);
1148
+ const length = v && typeof v === "object" && !Array.isArray(v) ? v.length : void 0;
1149
+ const list = Array.isArray(v) ? v : typeof length === "number" && length >= 0 && length < 1e3 ? Array.from({ length }, () => void 0) : void 0;
1150
+ if (!list) return OPAQUE;
1151
+ const fn = e.arguments[1];
1152
+ if (!fn) return list;
1153
+ const results = list.map((x, i) => callArrow(fn, [x, i], scope));
1154
+ return results.some((r) => r === OPAQUE) ? OPAQUE : results;
1155
+ }
1156
+ if (ts.isPropertyAccessExpression(e.expression) && /^(map|flatMap|filter|some|every|find)$/.test(e.expression.name.text)) {
1157
+ const list = evaluate(e.expression.expression, scope);
1158
+ if (Array.isArray(list)) return listMethod(list, e.expression.name.text, e.arguments, scope);
1159
+ }
1160
+ if (ts.isPropertyAccessExpression(e.expression)) {
1161
+ const obj = evaluate(e.expression.expression, scope);
1162
+ const r = valueMethod(obj, e.expression.name.text, e.arguments.map((a) => evaluate(a, scope)));
1163
+ if (r !== void 0) return r;
1164
+ }
1165
+ if (ts.isIdentifier(e.expression)) {
1166
+ const fnExpr = scope.inits?.get(e.expression.text) ?? scope.locals.get(e.expression.text);
1167
+ if (fnExpr && (ts.isArrowFunction(fnExpr) || ts.isFunctionExpression(fnExpr)))
1168
+ return callArrow(
1169
+ fnExpr,
1170
+ e.arguments.map((a) => evaluate(a, scope)),
1171
+ scope
1172
+ );
1173
+ }
1174
+ const helper = ts.isIdentifier(e.expression) ? scope.src.helpers.get(e.expression.text) : void 0;
1175
+ if (helper) {
1176
+ const args = e.arguments.map((a) => evaluate(a, scope));
1177
+ if (containsJsx(helper)) return callArrow(helper, args, { ...scope, vars: /* @__PURE__ */ new Map(), locals: /* @__PURE__ */ new Map(), toggles: /* @__PURE__ */ new Map(), inits: /* @__PURE__ */ new Map() });
1178
+ return callHelper(helper, args, scope.src);
1179
+ }
1180
+ const imported = ts.isIdentifier(e.expression) ? scope.src.imported.get(e.expression.text) : void 0;
1181
+ if (imported) return callData(imported, e.arguments.map((a) => evaluate(a, scope)));
1182
+ if (ts.isIdentifier(e.expression)) {
1183
+ const v = evaluate(e.expression, scope);
1184
+ if (isFnRef(v)) return callArrow(v.fnRef, e.arguments.map((a) => evaluate(a, scope)), v.scope);
1185
+ }
920
1186
  return OPAQUE;
921
1187
  }
1188
+ if (ts.isArrowFunction(e) || ts.isFunctionExpression(e)) return { fnRef: e, scope };
922
1189
  if (ts.isJsxElement(e) || ts.isJsxSelfClosingElement(e) || ts.isJsxFragment(e)) return { jsx: e, scope };
1190
+ if (ts.isObjectLiteralExpression(e)) {
1191
+ const out = {};
1192
+ for (const p of e.properties) {
1193
+ if (ts.isPropertyAssignment(p)) out[unquote(p.name.getText(sf))] = evaluate(p.initializer, scope);
1194
+ else if (ts.isShorthandPropertyAssignment(p)) out[p.name.text] = lookup(scope, p.name.text);
1195
+ else if (ts.isSpreadAssignment(p)) {
1196
+ const v = evaluate(p.expression, scope);
1197
+ if (v && typeof v === "object" && !Array.isArray(v) && !isJsx(v) && !isClass(v)) Object.assign(out, v);
1198
+ } else return OPAQUE;
1199
+ }
1200
+ return out;
1201
+ }
1202
+ if (ts.isArrayLiteralExpression(e)) return e.elements.map((x) => evaluate(x, scope));
923
1203
  return OPAQUE;
924
1204
  }
1205
+ 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);
1206
+ function callData(fn, args) {
1207
+ const data = args.map((a) => isTextRef(a) ? a.text : a);
1208
+ if (!data.every(isData)) return OPAQUE;
1209
+ try {
1210
+ const out = fn(...structuredClone(data));
1211
+ const v = jsonValue(JSON.parse(JSON.stringify(out ?? null)));
1212
+ return isData(v) ? v : OPAQUE;
1213
+ } catch {
1214
+ return OPAQUE;
1215
+ }
1216
+ }
1217
+ function callHelper(fn, args, src) {
1218
+ try {
1219
+ const js = ts.transpileModule(fn.getText(src.sf).replace(/^export\s+(default\s+)?/, ""), { compilerOptions: { target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.None } }).outputText;
1220
+ const run = new Function(`${js}; return ${fn.name.text}.apply(null, arguments);`);
1221
+ return callData(run, args);
1222
+ } catch {
1223
+ return OPAQUE;
1224
+ }
1225
+ }
1226
+ function containsJsx(fn) {
1227
+ let found = false;
1228
+ const visit = (n) => {
1229
+ if (found) return;
1230
+ if (ts.isJsxElement(n) || ts.isJsxSelfClosingElement(n) || ts.isJsxFragment(n)) found = true;
1231
+ else ts.forEachChild(n, visit);
1232
+ };
1233
+ visit(fn);
1234
+ return found;
1235
+ }
1236
+ function valueMethod(obj, method, args) {
1237
+ const str = (i) => typeof args[i] === "string" ? args[i] : void 0;
1238
+ const int = (i) => typeof args[i] === "number" ? args[i] : args[i] === void 0 ? void 0 : NaN;
1239
+ if (typeof obj === "string") {
1240
+ switch (method) {
1241
+ case "split":
1242
+ return str(0) !== void 0 ? obj.split(str(0)) : void 0;
1243
+ case "trim":
1244
+ return obj.trim();
1245
+ case "toLowerCase":
1246
+ return obj.toLowerCase();
1247
+ case "toUpperCase":
1248
+ return obj.toUpperCase();
1249
+ case "startsWith":
1250
+ return str(0) !== void 0 ? obj.startsWith(str(0)) : void 0;
1251
+ case "endsWith":
1252
+ return str(0) !== void 0 ? obj.endsWith(str(0)) : void 0;
1253
+ case "includes":
1254
+ return str(0) !== void 0 ? obj.includes(str(0)) : void 0;
1255
+ case "slice":
1256
+ return Number.isNaN(int(0)) || Number.isNaN(int(1)) ? void 0 : obj.slice(int(0), int(1));
1257
+ default:
1258
+ return void 0;
1259
+ }
1260
+ }
1261
+ if (Array.isArray(obj)) {
1262
+ switch (method) {
1263
+ case "join":
1264
+ return obj.every((x) => typeof x === "string" || typeof x === "number") ? obj.join(str(0) ?? ",") : void 0;
1265
+ case "slice":
1266
+ return Number.isNaN(int(0)) || Number.isNaN(int(1)) ? void 0 : obj.slice(int(0), int(1));
1267
+ case "includes":
1268
+ return obj.includes(args[0]);
1269
+ default:
1270
+ return void 0;
1271
+ }
1272
+ }
1273
+ return void 0;
1274
+ }
1275
+ function callArrow(fn, args, scope) {
1276
+ if (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn) && !ts.isFunctionDeclaration(fn)) return OPAQUE;
1277
+ if (!fn.body) return OPAQUE;
1278
+ const vars = new Map(scope.vars);
1279
+ fn.parameters.forEach((p, i) => {
1280
+ const v = args[i];
1281
+ if (ts.isIdentifier(p.name)) vars.set(p.name.text, v);
1282
+ else if (ts.isObjectBindingPattern(p.name) && v && typeof v === "object") {
1283
+ 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)]);
1284
+ }
1285
+ const pattern = p.name;
1286
+ if (ts.isArrayBindingPattern(pattern) && Array.isArray(v))
1287
+ pattern.elements.forEach((el, j) => {
1288
+ if (ts.isBindingElement(el) && ts.isIdentifier(el.name)) vars.set(el.name.text, v[j]);
1289
+ });
1290
+ });
1291
+ const inner = { ...scope, vars, locals: new Map(scope.locals) };
1292
+ if (!ts.isBlock(fn.body)) return evaluate(fn.body, inner);
1293
+ const run = (stmts) => {
1294
+ for (const st of stmts) {
1295
+ if (ts.isVariableStatement(st)) {
1296
+ for (const d of st.declarationList.declarations) if (ts.isIdentifier(d.name) && d.initializer) inner.locals.set(d.name.text, d.initializer);
1297
+ }
1298
+ if (ts.isIfStatement(st)) {
1299
+ const c = evaluate(st.expression, inner);
1300
+ if (c === OPAQUE) return { value: OPAQUE };
1301
+ const branch = truthy(c) ? st.thenStatement : st.elseStatement;
1302
+ const r = branch ? run(ts.isBlock(branch) ? branch.statements : [branch]) : void 0;
1303
+ if (r) return r;
1304
+ }
1305
+ if (ts.isForOfStatement(st) || ts.isForStatement(st) || ts.isWhileStatement(st)) return { value: OPAQUE };
1306
+ if (ts.isReturnStatement(st)) return { value: st.expression ? evaluate(st.expression, inner) : void 0 };
1307
+ }
1308
+ return void 0;
1309
+ };
1310
+ return run(fn.body.statements)?.value;
1311
+ }
1312
+ function listMethod(list, method, args, scope) {
1313
+ let fn = args[0];
1314
+ if (fn && ts.isIdentifier(fn)) fn = scope.inits?.get(fn.text) ?? scope.locals.get(fn.text) ?? fn;
1315
+ if (!fn) return OPAQUE;
1316
+ const results = list.map((x, i) => callArrow(fn, [x, i], scope));
1317
+ if (results.some((r) => r === OPAQUE)) return OPAQUE;
1318
+ switch (method) {
1319
+ case "map":
1320
+ return results;
1321
+ case "flatMap":
1322
+ return results.flatMap((r) => Array.isArray(r) ? r : [r]);
1323
+ case "filter":
1324
+ return list.filter((_, i) => truthy(results[i]));
1325
+ case "some":
1326
+ return results.some(truthy);
1327
+ case "every":
1328
+ return results.every(truthy);
1329
+ case "find":
1330
+ return list.find((_, i) => truthy(results[i]));
1331
+ default:
1332
+ return OPAQUE;
1333
+ }
1334
+ }
1335
+ function arithmetic(op, l, r) {
1336
+ if (l === OPAQUE || r === OPAQUE) return OPAQUE;
1337
+ const K = ts.SyntaxKind;
1338
+ const compare = op === K.LessThanToken || op === K.GreaterThanToken || op === K.LessThanEqualsToken || op === K.GreaterThanEqualsToken;
1339
+ if (compare && (l === void 0 || l === null || r === void 0 || r === null)) return false;
1340
+ 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}`;
1341
+ if (typeof l !== "number" || typeof r !== "number") return OPAQUE;
1342
+ switch (op) {
1343
+ case K.PlusToken:
1344
+ return l + r;
1345
+ case K.MinusToken:
1346
+ return l - r;
1347
+ case K.AsteriskToken:
1348
+ return l * r;
1349
+ case K.SlashToken:
1350
+ return l / r;
1351
+ case K.LessThanToken:
1352
+ return l < r;
1353
+ case K.GreaterThanToken:
1354
+ return l > r;
1355
+ case K.LessThanEqualsToken:
1356
+ return l <= r;
1357
+ case K.GreaterThanEqualsToken:
1358
+ return l >= r;
1359
+ default:
1360
+ return OPAQUE;
1361
+ }
1362
+ }
925
1363
  function truthy(v) {
926
1364
  if (v === OPAQUE) return true;
927
1365
  if (Array.isArray(v)) return true;
@@ -931,14 +1369,19 @@ function bindProps(fn, scope, given) {
931
1369
  const names = /* @__PURE__ */ new Map();
932
1370
  const param = fn.parameters[0];
933
1371
  if (!param || !ts.isObjectBindingPattern(param.name)) return names;
1372
+ const taken = /* @__PURE__ */ new Set();
934
1373
  for (const el of param.name.elements) {
935
1374
  if (el.dotDotDotToken || !ts.isIdentifier(el.name)) continue;
936
1375
  const prop = el.propertyName ? el.propertyName.getText(scope.src.sf) : el.name.text;
937
1376
  names.set(el.name.text, prop);
1377
+ taken.add(prop);
938
1378
  let v = given[prop];
939
1379
  if (v === void 0 && el.initializer) v = evaluate(el.initializer, scope);
940
1380
  scope.vars.set(el.name.text, v);
941
1381
  }
1382
+ const rest = param.name.elements.find((el) => el.dotDotDotToken && ts.isIdentifier(el.name));
1383
+ const keepChildren = !scope.main && !!scope.preview?.some((c) => typeof c !== "string");
1384
+ 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
1385
  return names;
943
1386
  }
944
1387
  function renderedJsx(fn, scope) {
@@ -951,7 +1394,31 @@ function renderedJsx(fn, scope) {
951
1394
  const run = (stmts) => {
952
1395
  for (const st of stmts) {
953
1396
  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);
1397
+ for (const d of st.declarationList.declarations) {
1398
+ if (ts.isArrayBindingPattern(d.name) && d.initializer && ts.isCallExpression(d.initializer) && /^(React\.)?useState$/.test(d.initializer.expression.getText(scope.src.sf))) {
1399
+ const first = d.name.elements[0];
1400
+ const init = d.initializer.arguments[0];
1401
+ if (first && ts.isBindingElement(first) && ts.isIdentifier(first.name) && init) {
1402
+ scope.locals.set(first.name.text, init);
1403
+ scope.inits?.set(first.name.text, init);
1404
+ }
1405
+ continue;
1406
+ }
1407
+ if (ts.isObjectBindingPattern(d.name) && d.initializer) {
1408
+ const obj = evaluate(d.initializer, scope);
1409
+ for (const el of d.name.elements) {
1410
+ if (!ts.isIdentifier(el.name) || el.dotDotDotToken) continue;
1411
+ const key = (el.propertyName ?? el.name).getText(scope.src.sf);
1412
+ let v = obj && typeof obj === "object" && !Array.isArray(obj) ? obj[key] : void 0;
1413
+ if (v === void 0 && el.initializer) v = evaluate(el.initializer, scope);
1414
+ scope.vars.set(el.name.text, v);
1415
+ }
1416
+ continue;
1417
+ }
1418
+ if (!ts.isIdentifier(d.name) || !d.initializer) continue;
1419
+ scope.locals.set(d.name.text, d.initializer);
1420
+ scope.inits?.set(d.name.text, d.initializer);
1421
+ }
955
1422
  } else if (ts.isIfStatement(st)) {
956
1423
  const branch = truthy(evaluate(st.expression, scope)) ? st.thenStatement : st.elseStatement;
957
1424
  if (!branch) continue;
@@ -968,10 +1435,17 @@ function renderedJsx(fn, scope) {
968
1435
  return run(body.statements) ?? void 0;
969
1436
  }
970
1437
  const INPUT_TAGS = /* @__PURE__ */ new Set(["input", "textarea", "BaseInput"]);
1438
+ const SVG_SHAPES = /^(circle|path|rect|line|polyline|polygon|ellipse)$/;
1439
+ const isInputTag = (tag) => INPUT_TAGS.has(tag) || /\.Input$/.test(tag);
971
1440
  const sentenceCase = (prop) => {
972
1441
  const words = prop.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase();
973
1442
  return words.charAt(0).toUpperCase() + words.slice(1);
974
1443
  };
1444
+ function attrString(v) {
1445
+ if (v === void 0 || v === null || v === OPAQUE || typeof v === "symbol") return void 0;
1446
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") return String(v);
1447
+ return void 0;
1448
+ }
975
1449
  function renderJsx(ref, ctx, visible) {
976
1450
  const { jsx, scope } = ref;
977
1451
  const sf = scope.src.sf;
@@ -980,11 +1454,29 @@ function renderJsx(ref, ctx, visible) {
980
1454
  if (!ts.isJsxElement(jsx) && !ts.isJsxSelfClosingElement(jsx)) return [];
981
1455
  const open = ts.isJsxElement(jsx) ? jsx.openingElement : jsx;
982
1456
  const tag = open.tagName.getText(sf);
1457
+ if (tag === "wbr") return [];
1458
+ if (/\.Provider$/.test(tag) && ts.isJsxElement(jsx)) {
1459
+ const valueAttr = open.attributes.properties.find((a) => ts.isJsxAttribute(a) && a.name.getText(sf) === "value");
1460
+ const value = valueAttr?.initializer && ts.isJsxExpression(valueAttr.initializer) && valueAttr.initializer.expression ? evaluate(valueAttr.initializer.expression, scope) : void 0;
1461
+ const contexts = new Map(ctx.contexts ?? []);
1462
+ contexts.set(tag.replace(/\.Provider$/, ""), value);
1463
+ const inner = { ...scope, contexts };
1464
+ return renderChildren(jsx.children, inner, { ...ctx, contexts });
1465
+ }
1466
+ if (/^(React\.)?Fragment$|\.Provider$/.test(tag)) return ts.isJsxElement(jsx) ? renderChildren(jsx.children, scope, ctx) : [];
983
1467
  const attrs = {};
984
1468
  let spreads = false;
1469
+ let spreadChildren;
985
1470
  for (const a of open.attributes.properties) {
986
1471
  if (ts.isJsxSpreadAttribute(a)) {
987
1472
  spreads = true;
1473
+ const v = evaluate(a.expression, scope);
1474
+ if (v && typeof v === "object" && !Array.isArray(v) && !isJsx(v) && !isClass(v) && !isElementValue(v) && !isFnRef(v)) {
1475
+ for (const [k, x] of Object.entries(v)) {
1476
+ if (k === "children") spreadChildren = x;
1477
+ else if (/^(data|aria)-/.test(k) || SPREAD_PROPS.has(k)) attrs[k] = x;
1478
+ }
1479
+ }
988
1480
  continue;
989
1481
  }
990
1482
  const name = a.name.getText(sf);
@@ -993,36 +1485,137 @@ function renderJsx(ref, ctx, visible) {
993
1485
  else if (ts.isJsxExpression(a.initializer) && a.initializer.expression) attrs[name] = evaluate(a.initializer.expression, scope);
994
1486
  }
995
1487
  const children = ts.isJsxElement(jsx) ? jsx.children : void 0;
1488
+ 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 [];
1489
+ if (scope.src.baseUi.has(tag.split(".")[0]) && attrs.render !== void 0 && !children?.some((c) => !ts.isJsxText(c) || c.text.trim())) {
1490
+ const r = attrs.render;
1491
+ const extra = classList(attrs.className);
1492
+ if (isJsx(r)) {
1493
+ const nodes = renderJsx(r, ctx, visible);
1494
+ if (nodes[0]) nodes[0].classes = [...nodes[0].classes, ...extra];
1495
+ return nodes;
1496
+ }
1497
+ const init = open.attributes.properties.find((a) => ts.isJsxAttribute(a) && a.name.getText(sf) === "render")?.initializer;
1498
+ if (r === CHILDREN && init && ts.isJsxExpression(init) && init.expression) return renderExpression(init.expression, scope, ctx, visible);
1499
+ }
996
1500
  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 });
1501
+ const nested = renderComponent(ctx.reader, tag, withFormError(tag, children?.length ? { ...attrs, children: childrenValue(children, scope) } : attrs, ctx), { ...ctx, depth: ctx.depth + 1 });
998
1502
  if (nested) {
999
- if (tag === "Icon") nested.kind = "icon";
1503
+ if (tag === ICON_COMPONENT) {
1504
+ nested.kind = "icon";
1505
+ const iconAttr = open.attributes.properties.find((a) => ts.isJsxAttribute(a) && a.name.getText(sf) === "icon");
1506
+ const expr = iconAttr?.initializer && ts.isJsxExpression(iconAttr.initializer) ? iconAttr.initializer.expression : void 0;
1507
+ const lucideName = expr && ts.isIdentifier(expr) ? scope.src.lucide.get(expr.text) : void 0;
1508
+ if (lucideName) nested.icon = lucideName;
1509
+ else if (isLucide(attrs.icon)) nested.icon = attrs.icon.lucide;
1510
+ else if (typeof attrs.icon === "string" && /^[A-Z]\w*$/.test(attrs.icon)) nested.icon = attrs.icon;
1511
+ }
1000
1512
  if (visible) nested.visible = visible;
1001
1513
  return [nested];
1002
1514
  }
1003
1515
  }
1516
+ const dataAttrs = /* @__PURE__ */ new Map();
1517
+ for (const [k, v] of Object.entries(attrs)) {
1518
+ if (!/^(data|aria)-/.test(k)) continue;
1519
+ const str = attrString(v);
1520
+ if (str !== void 0) dataAttrs.set(k, str);
1521
+ }
1522
+ const isBaseUi = scope.src.baseUi.has(tag.split(".")[0]);
1523
+ const orientation = isBaseUi && typeof attrs.orientation === "string" ? attrs.orientation : ctx.orientation;
1524
+ if (isBaseUi && orientation && !dataAttrs.has("data-orientation")) dataAttrs.set("data-orientation", orientation);
1004
1525
  const node = {
1005
1526
  key: "",
1006
1527
  tag,
1007
1528
  classes: classList(attrs.className),
1008
1529
  children: [],
1009
- kind: INPUT_TAGS.has(tag) ? "text" : "frame",
1010
- ...visible ? { visible } : {}
1530
+ kind: "frame",
1531
+ ...visible ? { visible } : {},
1532
+ ...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" } : {},
1533
+ ...dataAttrs.size ? { attrs: dataAttrs } : {},
1534
+ .../(^img$|\.Image$)/.test(tag) ? { media: true } : {},
1535
+ // Base UI indicators are only mounted while their item is checked, unless keepMounted.
1536
+ .../\.(Item)?Indicator$/.test(tag) && scope.src.baseUi.has(tag.split(".")[0]) && attrs.keepMounted !== true && ctx.fraction === void 0 ? { whenChecked: true } : {}
1011
1537
  };
1012
- if (INPUT_TAGS.has(tag)) {
1538
+ if (isBaseUi && /\.Panel$/.test(tag) && node.selectedBy && node.checked === "false") node.inactive = true;
1539
+ if (isBaseUi && /\.Thumb$/.test(tag) && ctx.fraction !== void 0) node.at = ctx.fraction;
1540
+ const bg = attrs.style && typeof attrs.style === "object" && !Array.isArray(attrs.style) ? attrs.style.backgroundColor : void 0;
1541
+ if (typeof bg === "string") node.inlineFill = bg;
1542
+ const style = attrs.style;
1543
+ if (style && typeof style === "object" && !Array.isArray(style) && !isJsx(style)) {
1544
+ const vars = /* @__PURE__ */ new Map();
1545
+ for (const [k, v] of Object.entries(style)) if (k.startsWith("--") && (typeof v === "number" || typeof v === "string")) vars.set(k, String(v));
1546
+ if (vars.size) node.styleVars = vars;
1547
+ }
1548
+ if (tag === "svg") {
1549
+ const lit = {};
1550
+ for (const [k, v] of Object.entries(attrs)) if ((typeof v === "string" || typeof v === "number") && k !== "className") lit[k] = String(v);
1551
+ node.svgAttrs = lit;
1552
+ node.shapes = children ? renderChildren(children, scope, ctx).filter((c) => SVG_SHAPES.test(c.tag)) : [];
1553
+ return [node];
1554
+ }
1555
+ if (SVG_SHAPES.test(tag)) {
1556
+ const lit = {};
1557
+ for (const [k, v] of Object.entries(attrs)) if ((typeof v === "string" || typeof v === "number") && k !== "className") lit[k] = String(v);
1558
+ node.svgAttrs = lit;
1559
+ return [node];
1560
+ }
1561
+ if (isInputTag(tag)) {
1013
1562
  node.inputLike = true;
1014
- node.placeholder = true;
1015
- node.text = { property: "Placeholder", default: typeof attrs.placeholder === "string" ? attrs.placeholder : "Placeholder" };
1563
+ const typed = typeof attrs.value === "string" && attrs.value ? attrs.value : typeof attrs.defaultValue === "string" && attrs.defaultValue ? attrs.defaultValue : void 0;
1564
+ if (typed !== void 0 && !scope.main) {
1565
+ node.children = [textNode(typed)];
1566
+ return [node];
1567
+ }
1568
+ const value = typeof attrs.placeholder === "string" ? attrs.placeholder : "Placeholder";
1569
+ node.children = [{ key: "", tag: "#placeholder", classes: [], children: [], kind: "text", placeholder: true, text: { property: "Placeholder", default: value } }];
1016
1570
  return [node];
1017
1571
  }
1018
- if (children) node.children = renderChildren(children, scope, ctx);
1572
+ if (node.baseUi && /\.Indicator$/.test(tag) && ctx.fraction !== void 0) node.fraction = ctx.fraction;
1573
+ let childCtx = Array.isArray(attrs.items) && node.baseUi ? { ...ctx, items: attrs.items } : ctx;
1574
+ if (isBaseUi && orientation) childCtx = { ...childCtx, orientation };
1575
+ if (isBaseUi && attrs.errors && typeof attrs.errors === "object" && !Array.isArray(attrs.errors) && !isFnRef(attrs.errors)) childCtx = { ...childCtx, formErrors: attrs.errors };
1576
+ const numeric = typeof attrs.value === "number" ? attrs.value : attrs.value === void 0 && typeof attrs.defaultValue === "number" ? attrs.defaultValue : void 0;
1577
+ if (node.baseUi && /\.Root$/.test(tag) && numeric !== void 0) {
1578
+ const min = typeof attrs.min === "number" ? attrs.min : 0;
1579
+ const max = typeof attrs.max === "number" ? attrs.max : 100;
1580
+ const local = tag.split(".")[0].toLowerCase();
1581
+ const module = [...scope.src.baseUiModules].find((m) => m && local.includes(m.split("/")[0])) ?? "";
1582
+ childCtx = { ...childCtx, fraction: Math.min(1, Math.max(0, (numeric - min) / (max - min || 1))), numeric: { value: numeric, module } };
1583
+ }
1584
+ 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;
1585
+ 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] };
1586
+ if (children) node.children = renderChildren(children, scope, childCtx);
1587
+ else if (spreadChildren === CHILDREN && scope.preview?.some((c) => typeof c !== "string")) node.children = renderPreview(scope.preview, childCtx);
1588
+ else if (spreadChildren !== void 0 && spreadChildren !== CHILDREN) node.children = renderValues([spreadChildren], ctx);
1019
1589
  else if (spreads) node.takesChildren = true;
1590
+ if (!node.children.length && node.baseUi && /\.Value$/.test(tag)) {
1591
+ const placeholder = formatted ?? (ctx.fraction !== void 0 ? `${Math.round(ctx.fraction * 100)}%` : typeof attrs.placeholder === "string" ? attrs.placeholder : "Value");
1592
+ node.children = [{ key: "", tag: "#text", classes: [], children: [], kind: "text", text: { property: "Value", default: placeholder } }];
1593
+ }
1020
1594
  return [node];
1021
1595
  }
1596
+ const SPREAD_PROPS = /* @__PURE__ */ new Set(["className", "value", "defaultValue", "min", "max", "orientation", "errors"]);
1597
+ function withFormError(tag, props, ctx) {
1598
+ const name = props.name;
1599
+ if (!ctx.formErrors || typeof name !== "string" || props.error !== void 0 || !ctx.reader.load(tag)?.props.has("error")) return props;
1600
+ const message = ctx.formErrors[name];
1601
+ const text = Array.isArray(message) ? message[0] : message;
1602
+ return typeof text === "string" ? { ...props, error: text } : props;
1603
+ }
1604
+ const isChildrenRef = (v) => !!v && typeof v === "object" && "children" in v && "scope" in v;
1605
+ function childrenValue(children, scope) {
1606
+ const texts = children.filter((c) => !ts.isJsxText(c) || c.text.trim());
1607
+ if (texts.every((c) => ts.isJsxText(c))) return texts.map((c) => c.text.trim()).join(" ");
1608
+ return { children, scope };
1609
+ }
1022
1610
  function renderChildren(children, scope, ctx) {
1023
1611
  const out = [];
1024
1612
  for (const c of children) {
1025
- if (ts.isJsxText(c)) continue;
1613
+ if (ts.isJsxText(c)) {
1614
+ const raw = jsxText(c.text);
1615
+ if (raw.trim()) out.push(textNode(raw.replace(/\s+/g, " ").trim(), raw));
1616
+ else if (raw) out.push(spaceNode(raw));
1617
+ continue;
1618
+ }
1026
1619
  if (ts.isJsxExpression(c)) {
1027
1620
  if (c.expression) out.push(...renderExpression(c.expression, scope, ctx));
1028
1621
  continue;
@@ -1031,66 +1624,394 @@ function renderChildren(children, scope, ctx) {
1031
1624
  }
1032
1625
  return out;
1033
1626
  }
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;
1627
+ const spaceNode = (raw) => ({ key: "", tag: "#text", classes: [], children: [], kind: "text", chars: "", raw, space: true });
1628
+ const textNode = (chars, raw) => ({ key: "", tag: "#text", classes: [], children: [], kind: "text", chars, ...raw !== void 0 && raw !== chars ? { raw } : {} });
1629
+ function jsxText(text) {
1630
+ const lines = text.split(/\r\n|\n|\r/);
1631
+ let out = "";
1632
+ lines.forEach((line, i) => {
1633
+ let l = line.replace(/\t/g, " ");
1634
+ if (i !== 0) l = l.replace(/^ +/, "");
1635
+ if (i !== lines.length - 1) l = l.replace(/ +$/, "");
1636
+ if (!l) return;
1637
+ if (out && i !== 0) out += " ";
1638
+ out += l;
1639
+ });
1640
+ return out;
1641
+ }
1642
+ function identifiersIn(e, scope, seen = /* @__PURE__ */ new Set()) {
1643
+ const visit = (n) => {
1644
+ if (ts.isIdentifier(n) && !seen.has(n.text)) {
1645
+ seen.add(n.text);
1646
+ const init = scope.inits?.get(n.text);
1647
+ if (init) identifiersIn(init, scope, seen);
1648
+ }
1649
+ if (ts.isPropertyAccessExpression(n)) return visit(n.expression);
1650
+ ts.forEachChild(n, visit);
1651
+ };
1652
+ visit(e);
1653
+ return seen;
1654
+ }
1655
+ function toggledScope(scope, local) {
1656
+ const vars = new Map(scope.vars);
1657
+ for (const k of scope.inits?.keys() ?? []) vars.delete(k);
1658
+ vars.set(local, scope.toggleValues?.get(local) ?? true);
1659
+ return { ...scope, vars, locals: new Map(scope.inits ?? []) };
1660
+ }
1661
+ function toggleFor(cond, scope) {
1662
+ if (!scope.toggles.size) return void 0;
1663
+ const ids = identifiersIn(cond, scope);
1664
+ const local = [...scope.toggles.keys()].find((k) => ids.has(k));
1665
+ if (!local) return void 0;
1666
+ const prop = scope.toggles.get(local);
1667
+ const inner = toggledScope(scope, local);
1668
+ if (!truthy(evaluate(cond, inner))) return void 0;
1669
+ const off = { ...scope, vars: new Map(scope.vars), locals: new Map(scope.inits ?? []) };
1670
+ for (const k of scope.inits?.keys() ?? []) off.vars.delete(k);
1671
+ if (truthy(evaluate(cond, off))) return void 0;
1672
+ const kind = scope.src.props.get(prop)?.kind;
1673
+ const property = kind === "icon" ? sentenceCase(prop) : `Show ${sentenceCase(prop).toLowerCase()}`;
1674
+ return { visible: { property, prop, ...scope.toggleOn?.has(prop) ? { on: true } : {} }, scope: inner };
1675
+ }
1676
+ function nameToggle(visible, nodes, scope) {
1677
+ const cls = nodes[0]?.classes[0]?.split("|")[1];
1678
+ if (scope.src.props.get(visible.prop)?.kind === "string" && cls) visible.property = `Show ${sentenceCase(cls).toLowerCase()}`;
1679
+ return nodes;
1039
1680
  }
1040
1681
  function renderExpression(e, scope, ctx, visible) {
1041
1682
  if (ts.isParenthesizedExpression(e)) return renderExpression(e.expression, scope, ctx, visible);
1042
1683
  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);
1684
+ const t = visible ? void 0 : toggleFor(e.left, scope);
1685
+ if (t) return nameToggle(t.visible, renderExpression(e.right, t.scope, ctx, t.visible), scope);
1045
1686
  return truthy(evaluate(e.left, scope)) ? renderExpression(e.right, scope, ctx, visible) : [];
1046
1687
  }
1047
1688
  if (ts.isConditionalExpression(e)) {
1048
- const t = toggleOf(e.condition, scope);
1049
- if (t) return renderExpression(e.whenTrue, scope, ctx, visible ?? t);
1689
+ const t = visible || truthy(evaluate(e.whenFalse, scope)) ? void 0 : toggleFor(e.condition, scope);
1690
+ if (t) return nameToggle(t.visible, renderExpression(e.whenTrue, t.scope, ctx, t.visible), scope);
1050
1691
  return renderExpression(truthy(evaluate(e.condition, scope)) ? e.whenTrue : e.whenFalse, scope, ctx, visible);
1051
1692
  }
1052
1693
  if (ts.isJsxElement(e) || ts.isJsxSelfClosingElement(e) || ts.isJsxFragment(e)) return renderJsx({ jsx: e, scope }, ctx, visible);
1053
1694
  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" } }];
1695
+ if (v === CHILDREN) {
1696
+ if (scope.preview?.some((c) => typeof c !== "string")) return renderPreview(scope.preview, ctx);
1697
+ return [{ key: "", tag: "#text", classes: [], children: [], kind: "text", text: { property: "Label", default: ctx.component, prop: "children" } }];
1698
+ }
1699
+ if (isTextRef(v)) {
1700
+ const property = v.textProp === "children" ? "Label" : sentenceCase(v.textProp);
1701
+ return [{ key: "", tag: "#text", classes: [], children: [], kind: "text", text: { property, default: v.text, prop: v.textProp }, ...visible ? { visible } : {} }];
1702
+ }
1703
+ if (isChildrenRef(v)) return renderChildren(v.children, v.scope, ctx);
1704
+ if (typeof v === "string" && v.trim()) return [{ ...textNode(v.trim(), v), ...visible ? { visible } : {} }];
1705
+ if (typeof v === "string" && v) return [spaceNode(v)];
1706
+ if (typeof v === "number") return [textNode(String(v))];
1055
1707
  if (isJsx(v)) return renderJsx(v, ctx, visible);
1708
+ if (Array.isArray(v) || isElementValue(v)) return renderValues([v], ctx);
1709
+ if (v === OPAQUE || isFnRef(v)) {
1710
+ const mapped = renderMap(e, scope, ctx);
1711
+ if (mapped) return mapped;
1712
+ const fn = functionOf(e, scope);
1713
+ if (fn && ctx.items) return renderValues(ctx.items.map((item) => callArrow(fn, [item], scope)), ctx);
1714
+ if (fn && ctx.valueArgs) {
1715
+ const out = callArrow(fn, ctx.valueArgs, scope);
1716
+ if (out !== OPAQUE) return renderValues([out], ctx);
1717
+ }
1718
+ ctx.blockers?.push("shows content computed while the page runs");
1719
+ }
1056
1720
  return [];
1057
1721
  }
1058
- function renderComponent(reader, name, props, ctx, fnOverride) {
1722
+ function functionOf(e, scope) {
1723
+ if (ts.isParenthesizedExpression(e)) return functionOf(e.expression, scope);
1724
+ if (ts.isArrowFunction(e) || ts.isFunctionExpression(e)) return e;
1725
+ if (ts.isIdentifier(e)) {
1726
+ const init = scope.inits?.get(e.text) ?? scope.locals.get(e.text);
1727
+ return init ? functionOf(init, scope) : void 0;
1728
+ }
1729
+ if (ts.isConditionalExpression(e)) {
1730
+ const c = evaluate(e.condition, scope);
1731
+ return c === OPAQUE ? void 0 : functionOf(truthy(c) ? e.whenTrue : e.whenFalse, scope);
1732
+ }
1733
+ return void 0;
1734
+ }
1735
+ const isElementValue = (v) => !!v && typeof v === "object" && "element" in v && "kids" in v;
1736
+ function elementNode(el, ctx) {
1737
+ const node = { key: "", tag: el.element, classes: classList(el.props.className), children: [], kind: "frame" };
1738
+ const kids = el.kids.length ? el.kids : el.props.children !== void 0 ? [el.props.children] : [];
1739
+ for (const k of kids) {
1740
+ if (k === CHILDREN) node.children.push({ key: "", tag: "#text", classes: [], children: [], kind: "text", text: { property: "Label", default: ctx.component, prop: "children" } });
1741
+ 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 } });
1742
+ else node.children.push(...renderValues([k], ctx));
1743
+ }
1744
+ return node;
1745
+ }
1746
+ function renderValues(list, ctx) {
1747
+ const out = [];
1748
+ for (const v of list) {
1749
+ if (isElementValue(v)) out.push(elementNode(v, ctx));
1750
+ else if (isChildrenRef(v)) out.push(...renderChildren(v.children, v.scope, ctx));
1751
+ 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 } });
1752
+ else if (isJsx(v)) out.push(...renderJsx(v, ctx));
1753
+ else if (Array.isArray(v)) out.push(...renderValues(v, ctx));
1754
+ else if (typeof v === "string" && v.trim() || typeof v === "number") out.push(textNode(String(v).trim(), String(v)));
1755
+ else if (typeof v === "string" && v) out.push(spaceNode(v));
1756
+ else if (v === OPAQUE) ctx.blockers?.push("shows content computed while the page runs");
1757
+ }
1758
+ return out;
1759
+ }
1760
+ function renderMap(e, scope, ctx) {
1761
+ if (!ts.isCallExpression(e) || !ts.isPropertyAccessExpression(e.expression) || e.expression.name.text !== "map") return void 0;
1762
+ const list = evaluate(e.expression.expression, scope);
1763
+ const fn = e.arguments[0];
1764
+ if (!Array.isArray(list) || !fn || !(ts.isArrowFunction(fn) || ts.isFunctionExpression(fn))) return void 0;
1765
+ const param = fn.parameters[0];
1766
+ const out = [];
1767
+ list.forEach((item, i) => {
1768
+ const vars = new Map(scope.vars);
1769
+ if (param && ts.isIdentifier(param.name)) vars.set(param.name.text, item);
1770
+ else if (param && ts.isObjectBindingPattern(param.name) && item && typeof item === "object") {
1771
+ 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)]);
1772
+ }
1773
+ const index = fn.parameters[1];
1774
+ if (index && ts.isIdentifier(index.name)) vars.set(index.name.text, i);
1775
+ const inner = { ...scope, vars, locals: new Map(scope.locals), inits: new Map(scope.inits ?? []) };
1776
+ const body = fn.body;
1777
+ if (ts.isBlock(body)) {
1778
+ for (const st of body.statements) {
1779
+ if (ts.isVariableStatement(st))
1780
+ for (const d of st.declarationList.declarations) {
1781
+ if (!ts.isIdentifier(d.name) || !d.initializer) continue;
1782
+ inner.locals.set(d.name.text, d.initializer);
1783
+ inner.inits.set(d.name.text, d.initializer);
1784
+ }
1785
+ if (ts.isReturnStatement(st) && st.expression) {
1786
+ out.push(...renderExpression(st.expression, inner, ctx));
1787
+ break;
1788
+ }
1789
+ }
1790
+ } else out.push(...renderExpression(body, inner, ctx));
1791
+ });
1792
+ return out;
1793
+ }
1794
+ function previewElement(v, name) {
1795
+ if (Array.isArray(v)) {
1796
+ for (const x of v) {
1797
+ const hit = previewElement(x, name);
1798
+ if (hit) return hit;
1799
+ }
1800
+ return void 0;
1801
+ }
1802
+ if (!v || typeof v !== "object") return void 0;
1803
+ const n = v;
1804
+ if (n.c === name) return n;
1805
+ return previewElement(n.children, name);
1806
+ }
1807
+ function storyElement(sf, name) {
1808
+ if (!sf) return void 0;
1809
+ const lucide = /* @__PURE__ */ new Set();
1810
+ for (const st of sf.statements) {
1811
+ if (!ts.isImportDeclaration(st) || unquote(st.moduleSpecifier.getText(sf)) !== "lucide-react") continue;
1812
+ const b = st.importClause?.namedBindings;
1813
+ if (b && ts.isNamedImports(b)) for (const e of b.elements) lucide.add(e.name.text);
1814
+ }
1815
+ const convert = (n) => {
1816
+ const open = ts.isJsxElement(n) ? n.openingElement : n;
1817
+ const p = {};
1818
+ for (const a of open.attributes.properties) {
1819
+ if (!ts.isJsxAttribute(a)) continue;
1820
+ const key = a.name.getText(sf);
1821
+ const init = a.initializer;
1822
+ if (!init) p[key] = true;
1823
+ else if (ts.isStringLiteral(init)) p[key] = init.text;
1824
+ else if (ts.isJsxExpression(init) && init.expression) {
1825
+ const e = init.expression;
1826
+ if (ts.isStringLiteral(e) || ts.isNoSubstitutionTemplateLiteral(e)) p[key] = e.text;
1827
+ else if (ts.isNumericLiteral(e)) p[key] = Number(e.text);
1828
+ else if (e.kind === ts.SyntaxKind.TrueKeyword || e.kind === ts.SyntaxKind.FalseKeyword) p[key] = e.kind === ts.SyntaxKind.TrueKeyword;
1829
+ else if (ts.isIdentifier(e) && lucide.has(e.text)) p[key] = e.text;
1830
+ }
1831
+ }
1832
+ const children = [];
1833
+ if (ts.isJsxElement(n))
1834
+ for (const c of n.children) {
1835
+ if (ts.isJsxText(c)) {
1836
+ const t = c.text.replace(/\s+/g, " ").trim();
1837
+ if (t) children.push(t);
1838
+ } else if (ts.isJsxElement(c) || ts.isJsxSelfClosingElement(c)) children.push(convert(c));
1839
+ else if (ts.isJsxExpression(c) && c.expression && (ts.isStringLiteral(c.expression) || ts.isNoSubstitutionTemplateLiteral(c.expression))) children.push(c.expression.text);
1840
+ }
1841
+ return { c: open.tagName.getText(sf), ...Object.keys(p).length ? { p } : {}, ...children.length ? { children } : {} };
1842
+ };
1843
+ let found;
1844
+ const visit = (n) => {
1845
+ if (found) return;
1846
+ if ((ts.isJsxElement(n) || ts.isJsxSelfClosingElement(n)) && (ts.isJsxElement(n) ? n.openingElement : n).tagName.getText(sf) === name) {
1847
+ const el = convert(n);
1848
+ if (el.children?.some((c) => typeof c !== "string")) found = el;
1849
+ }
1850
+ ts.forEachChild(n, visit);
1851
+ };
1852
+ visit(sf);
1853
+ return found;
1854
+ }
1855
+ function previewProps(el) {
1856
+ const out = {};
1857
+ for (const [k, v] of Object.entries(el.p ?? {})) if (v !== null && v !== void 0) out[k] = jsonValue(v);
1858
+ return out;
1859
+ }
1860
+ function jsonValue(v) {
1861
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean" || v === null) return v;
1862
+ if (Array.isArray(v)) return v.map(jsonValue);
1863
+ if (v && typeof v === "object") return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, jsonValue(x)]));
1864
+ return void 0;
1865
+ }
1866
+ function renderPreview(children, ctx) {
1867
+ const out = [];
1868
+ for (const c of children) {
1869
+ if (typeof c === "string") {
1870
+ if (c.trim()) out.push(textNode(c.trim()));
1871
+ continue;
1872
+ }
1873
+ if (!ctx.reader.exists(c.c) || ctx.depth >= 4) continue;
1874
+ const kids = c.children ?? [];
1875
+ let props = previewProps(c);
1876
+ if (kids.length && kids.every((k) => typeof k === "string")) props.children = kids.join(" ");
1877
+ else if (kids.length) props.children = CHILDREN;
1878
+ props = withFormError(c.c, props, ctx);
1879
+ const n = renderComponent(ctx.reader, c.c, props, { ...ctx, depth: ctx.depth + 1 }, void 0, kids.some((k) => typeof k !== "string") ? kids : void 0);
1880
+ if (n) out.push(n);
1881
+ else ctx.blockers?.push(`cannot draw its ${c.c} parts yet`);
1882
+ }
1883
+ return out;
1884
+ }
1885
+ function useRenderElement(fn, scope, ctx) {
1886
+ if (!fn.body || !ts.isBlock(fn.body)) return void 0;
1887
+ const ret = fn.body.statements.find(ts.isReturnStatement)?.expression;
1888
+ if (!ret || !ts.isCallExpression(ret) || ret.expression.getText(scope.src.sf) !== "useRender" || !ret.arguments[0]) return void 0;
1889
+ const opts = evaluate(ret.arguments[0], scope);
1890
+ const tag = typeof opts?.defaultTagName === "string" ? opts.defaultTagName : "div";
1891
+ const props = opts?.props && typeof opts.props === "object" ? opts.props : {};
1892
+ return [elementNode({ element: tag, props, kids: [] }, ctx)];
1893
+ }
1894
+ function renderComponent(reader, name, props, ctx, fnOverride, preview) {
1059
1895
  const src = reader.load(name);
1060
1896
  const fn = fnOverride ?? src?.fn;
1061
1897
  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);
1898
+ 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 } : {} };
1899
+ const given = { ...props };
1900
+ if (!scope.main) {
1901
+ const on = (k) => props[k] === true;
1902
+ scope.checked = on("indeterminate") ? "mixed" : on("checked") || on("defaultChecked") ? "true" : "false";
1903
+ }
1064
1904
  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);
1905
+ for (const p of src.props.values()) {
1906
+ if (given[p.name] !== void 0 || p.name === "children") continue;
1907
+ if (p.kind === "node" && !p.optional) given[p.name] = { textProp: p.name, text: sentenceCase(p.name) };
1908
+ if (p.kind === "string" && !p.optional) given[p.name] = sentenceCase(p.name);
1909
+ }
1910
+ }
1911
+ const names = bindProps(fn, scope, given);
1912
+ if (scope.main) {
1913
+ scope.toggleValues = /* @__PURE__ */ new Map();
1914
+ for (const [local, prop] of names) {
1915
+ const info = src.props.get(prop);
1916
+ if (!info?.optional || given[prop] !== void 0) continue;
1917
+ if (info.kind === "icon") scope.toggles.set(local, prop);
1918
+ else if (info.kind === "node" && prop !== "children") {
1919
+ scope.toggles.set(local, prop);
1920
+ scope.toggleValues.set(local, { textProp: prop, text: sentenceCase(prop) });
1921
+ } else if (info.kind === "string") {
1922
+ scope.toggles.set(local, prop);
1923
+ const example = ctx.examples?.[prop];
1924
+ scope.toggleValues.set(local, typeof example === "string" ? example : sentenceCase(prop));
1925
+ if (typeof example === "string") (scope.toggleOn ??= /* @__PURE__ */ new Set()).add(prop);
1926
+ }
1927
+ }
1066
1928
  }
1067
1929
  const jsx = renderedJsx(fn, scope);
1068
- if (!jsx) return void 0;
1069
- const nodes = renderJsx(jsx, ctx);
1930
+ const nodes = jsx ? renderJsx(jsx, ctx) : useRenderElement(fn, scope, ctx);
1931
+ if (!nodes) return void 0;
1070
1932
  return nodes.length === 1 ? nodes[0] : nodes.length ? { key: "", tag: "div", classes: [], children: nodes, kind: "frame" } : void 0;
1071
1933
  }
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) {
1934
+ function collapseText(n, root = true, boxed = () => false, unseen = () => false) {
1935
+ n.children = n.children.map((c) => collapseText(c, false, boxed, unseen));
1936
+ const seen = n.children.filter((c) => !unseen(c));
1937
+ if (seen.length < n.children.length && seen.length && /^(span|p|label|small|strong|em|code)$/.test(n.tag)) n.children = seen;
1938
+ 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;
1939
+ 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)) {
1940
+ let chars = "";
1941
+ n.children.forEach((c, i) => {
1942
+ const text = run(c);
1943
+ const prev = n.children[i - 1];
1944
+ if (prev) {
1945
+ const before = run(prev);
1946
+ chars += /\s$/.test(before) || /^\s/.test(text) || prev.tag === "#text" && c.tag === "#text" ? "" : " ";
1947
+ }
1948
+ chars += text;
1949
+ });
1950
+ n.children = [{ ...textNode(chars.replace(/\s+/g, " ").trim()) }];
1951
+ }
1952
+ n.children = n.children.filter((c) => !c.space);
1953
+ 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
1954
  const t = n.children[0];
1076
1955
  return { ...t, tag: n.tag, classes: n.classes, ...n.visible ? { visible: n.visible } : {} };
1077
1956
  }
1078
1957
  return n;
1079
1958
  }
1080
- function build(reader, src, props) {
1081
- const root = renderComponent(reader, src.name, { ...props, children: CHILDREN }, { reader, depth: 0, component: src.name });
1959
+ function applyView(root, view) {
1960
+ if (!view) return root;
1961
+ const isPortal = (n) => /\.Portal$/.test(n.tag);
1962
+ if (view === "without-popup") {
1963
+ const strip2 = (n) => ({ ...n, children: n.children.filter((c) => !isPortal(c)).map(strip2) });
1964
+ return isPortal(root) ? void 0 : strip2(root);
1965
+ }
1966
+ let found;
1967
+ const find = (n, inPortal) => {
1968
+ if (found) return;
1969
+ if (inPortal && /\.Popup$/.test(n.tag)) found = n;
1970
+ for (const c of n.children) find(c, inPortal || isPortal(n));
1971
+ };
1972
+ find(root, false);
1973
+ const strip = (n) => ({ ...n, children: n.children.filter((c) => !isPortal(c)).map(strip) });
1974
+ return found ? strip({ ...found, visible: void 0 }) : void 0;
1975
+ }
1976
+ function build(reader, src, props, preview, view, rules = [], tokens, examples) {
1977
+ const blockers = [];
1978
+ const selected = typeof props.defaultValue === "string" ? props.defaultValue : void 0;
1979
+ 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);
1980
+ let root = rendered && applyView(rendered, view);
1082
1981
  if (!root) return void 0;
1982
+ const overlay = (n) => POPUP_PART.test(n.tag) || /\.Portal$/.test(n.tag);
1983
+ 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]))
1984
+ root = root.children[0];
1083
1985
  if (root.takesChildren && !root.children.length && src.subcomponents.length) {
1986
+ root.fromExports = true;
1084
1987
  for (const sub of src.subcomponents) {
1085
1988
  const label = sub.name.slice(src.name.length);
1086
- const n = renderComponent(reader, src.name, {}, { reader, depth: 1, component: src.name }, sub.fn);
1989
+ const n = renderComponent(reader, src.name, {}, { reader, depth: 1, component: src.name, blockers }, sub.fn);
1087
1990
  if (!n) continue;
1088
1991
  n.name = label;
1089
1992
  if (n.takesChildren && !n.children.length) n.children = [{ key: "", tag: "#text", classes: [], children: [], kind: "text", text: { property: label, default: label } }];
1090
1993
  root.children.push(n);
1091
1994
  }
1092
1995
  }
1093
- const tree = collapseText(root);
1996
+ const follow = (n, inherited) => {
1997
+ if (n.baseUi && n.selectedBy) inherited = n.checked;
1998
+ else if (n.baseUi && inherited) n.checked = inherited;
1999
+ for (const c of n.children) follow(c, inherited);
2000
+ };
2001
+ follow(root, void 0);
2002
+ decorate(root, rules, tokens);
2003
+ const boxed = (n) => {
2004
+ const el = { tag: n.tag, classes: new Set(n.classes), children: [], present: true, isRoot: false, inputLike: false };
2005
+ const d = declaredFor(rules, el, DEFAULT_STATE);
2006
+ const set = (k) => d.has(k) && !/^(0|0px|none|transparent)$/.test(d.get(k).value.trim());
2007
+ return ["background-color", "border-top-width", "padding-left", "padding-top"].some(set);
2008
+ };
2009
+ const unseen = (n) => {
2010
+ if (n.kind !== "frame" && n.kind !== "text") return false;
2011
+ const el = { tag: n.tag, classes: new Set(n.classes), children: [], present: true, isRoot: false, inputLike: false };
2012
+ return visuallyHidden(new Map([...declaredFor(rules, el, DEFAULT_STATE)].map(([k, d]) => [k, d.value])));
2013
+ };
2014
+ const tree = collapseText(root, true, boxed, unseen);
1094
2015
  const parts = /* @__PURE__ */ new Map();
1095
2016
  const parents = /* @__PURE__ */ new Map();
1096
2017
  const used = /* @__PURE__ */ new Set();
@@ -1102,7 +2023,7 @@ function build(reader, src, props) {
1102
2023
  else if (n.text && n.kind === "text" && !n.placeholder) base = n.text.prop === "children" ? "text" : `${slug(n.text.property)}-text`;
1103
2024
  else if (n.placeholder) base = "placeholder";
1104
2025
  else if (n.name) base = slug(n.name);
1105
- else base = slug(n.classes[0]?.split("|")[1] ?? n.tag) || "part";
2026
+ else base = slug(n.classes[0]?.split("|")[1] ?? (n.chars !== void 0 ? "text" : n.tag)) || "part";
1106
2027
  let id = base;
1107
2028
  for (let i = 2; used.has(id); i++) id = `${base}-${i}`;
1108
2029
  used.add(id);
@@ -1112,14 +2033,94 @@ function build(reader, src, props) {
1112
2033
  for (const c of n.children) assign(c, id);
1113
2034
  };
1114
2035
  assign(tree, void 0);
1115
- return { root: tree, parts, parents };
2036
+ return { root: tree, parts, parents, blockers };
2037
+ }
2038
+ function cssElements(root) {
2039
+ const out = /* @__PURE__ */ new Map();
2040
+ const make = (n, parent) => {
2041
+ const el = {
2042
+ tag: n.tag,
2043
+ classes: new Set(n.classes),
2044
+ parent,
2045
+ children: [],
2046
+ present: (!n.visible || !!n.visible.on) && !n.pseudo,
2047
+ isRoot: !parent,
2048
+ inputLike: !!n.inputLike,
2049
+ ...n.baseUi ? { baseUi: true, checked: n.checked } : {},
2050
+ ...n.attrs ? { attrs: n.attrs } : {}
2051
+ };
2052
+ out.set(n, el);
2053
+ el.children = n.children.map((c) => make(c, el));
2054
+ return el;
2055
+ };
2056
+ make(root, void 0);
2057
+ return out;
2058
+ }
2059
+ const declaredVarCache = /* @__PURE__ */ new WeakMap();
2060
+ function declaredVars(rules) {
2061
+ let s = declaredVarCache.get(rules);
2062
+ if (!s) {
2063
+ s = new Set(rules.flatMap((r) => r.decls.filter((d) => d.prop.startsWith("--")).map((d) => d.prop)));
2064
+ declaredVarCache.set(rules, s);
2065
+ }
2066
+ return s;
2067
+ }
2068
+ function runtimeVar(raw, rules, tokens) {
2069
+ const m = raw ? /^var\(\s*(--[\w-]+)\s*\)$/.exec(raw.trim()) : null;
2070
+ return !!m && !declaredVars(rules).has(m[1]) && !tokens?.has(m[1]);
2071
+ }
2072
+ const PSEUDO_PAINTS = ["background-color", "border-top-width", "border-bottom-width", "border-left-width", "border-right-width"];
2073
+ function decorate(root, rules, tokens) {
2074
+ if (!rules.length) return;
2075
+ const els = cssElements(root);
2076
+ const visit = (n, parent) => {
2077
+ for (const c of [...n.children]) visit(c, n);
2078
+ if (n.kind !== "frame" || n.svgAttrs || n.pseudo) return;
2079
+ const el = els.get(n);
2080
+ if (n.baseUi && n.whenChecked && /\.Indicator$/.test(n.tag) && parent) {
2081
+ const d = declaredFor(rules, el, DEFAULT_STATE);
2082
+ if (["left", "top", "width", "height"].some((k) => runtimeVar(d.get(k)?.value, rules, tokens))) {
2083
+ const item = parent.children.find((s) => s !== n && s.baseUi && s.selectedBy && s.checked === "true");
2084
+ if (item) {
2085
+ parent.children.splice(parent.children.indexOf(n), 1);
2086
+ item.children.unshift(n);
2087
+ n.follows = true;
2088
+ delete n.whenChecked;
2089
+ }
2090
+ }
2091
+ }
2092
+ for (const pseudo of ["before", "after"]) {
2093
+ const d = declaredFor(rules, el, DEFAULT_STATE, pseudo);
2094
+ if (!/^(""|'')$/.test(d.get("content")?.value.trim() ?? "")) continue;
2095
+ if (!PSEUDO_PAINTS.some((k) => d.has(k) && !/^(0|none|transparent)$/.test(d.get(k).value.trim()))) continue;
2096
+ const deco = { key: "", tag: `::${pseudo}`, classes: [], children: [], kind: "frame", pseudo, name: "Decoration" };
2097
+ if (pseudo === "before" || /^-/.test(d.get("z-index")?.value.trim() ?? "")) n.children.unshift(deco);
2098
+ else n.children.push(deco);
2099
+ }
2100
+ };
2101
+ visit(root, void 0);
1116
2102
  }
1117
2103
  function toCssTree(b) {
1118
2104
  const els = /* @__PURE__ */ new Map();
1119
2105
  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 };
2106
+ const el = {
2107
+ tag: n.tag,
2108
+ classes: new Set(n.classes),
2109
+ parent,
2110
+ children: [],
2111
+ present: (!n.visible || !!n.visible.on) && !n.pseudo,
2112
+ isRoot: !parent,
2113
+ inputLike: !!n.inputLike,
2114
+ ...n.baseUi ? { baseUi: true, checked: n.checked } : {},
2115
+ ...n.attrs ? { attrs: n.attrs } : {}
2116
+ };
1121
2117
  els.set(n.key, el);
1122
2118
  el.children = n.children.map((c) => make(c, el));
2119
+ for (const [i, shape] of (n.shapes ?? []).entries()) {
2120
+ const sel = { tag: shape.tag, classes: new Set(shape.classes), parent: el, children: [], present: true, isRoot: false, inputLike: false };
2121
+ els.set(`${n.key}#shape-${i}`, sel);
2122
+ el.children.push(sel);
2123
+ }
1123
2124
  return el;
1124
2125
  };
1125
2126
  make(b.root, void 0);
@@ -1145,6 +2146,14 @@ function resolveValue(raw, values, tokens, depth = 0, parent, forColor = false)
1145
2146
  return { unmapped: `${name} is not a token` };
1146
2147
  }
1147
2148
  if (/^calc\(/i.test(v)) return calcValue(v, values, tokens, depth, parent);
2149
+ const mm = /^min\(([\s\S]+)\)$/i.exec(v);
2150
+ if (mm) {
2151
+ for (const arg of splitTop(mm[1], ",")) {
2152
+ const r = resolveValue(arg, values, tokens, depth + 1, parent, forColor);
2153
+ if (r && r.unmapped === void 0 && typeof r.value === "string") return r;
2154
+ }
2155
+ return { unmapped: "min() of no token" };
2156
+ }
1148
2157
  if (v === "100%") return { unmapped: "fills its container: Figma draws it at a fixed width" };
1149
2158
  if (/%$/.test(v)) return { unmapped: "relative size: the Figma part hugs its content" };
1150
2159
  return { unmapped: "literal value, not a token" };
@@ -1158,7 +2167,7 @@ function calcValue(v, values, tokens, depth, parent) {
1158
2167
  failed ??= r?.unmapped ?? `${m} has no value`;
1159
2168
  return "0";
1160
2169
  }
1161
- if (r.value === 0) return "0";
2170
+ if (typeof r.value === "number") return String(r.value);
1162
2171
  return r.value.startsWith("calc(") ? `(${r.value.slice(5, -1)})` : r.value;
1163
2172
  });
1164
2173
  if (failed) return { unmapped: `calc(): ${failed}` };
@@ -1194,6 +2203,7 @@ const TEXT_FIELDS = [
1194
2203
  ["line-height", "lineHeight"],
1195
2204
  ["letter-spacing", "letterSpacing"]
1196
2205
  ];
2206
+ const NUMBER_FIELDS = /* @__PURE__ */ new Set(["strokeWeight", "opacity"]);
1197
2207
  const SIDE_FIELDS = { top: "strokeTopWeight", right: "strokeRightWeight", bottom: "strokeBottomWeight", left: "strokeLeftWeight" };
1198
2208
  const typeCache = /* @__PURE__ */ new WeakMap();
1199
2209
  function typesOf(tokens) {
@@ -1205,14 +2215,30 @@ function typesOf(tokens) {
1205
2215
  return m;
1206
2216
  }
1207
2217
  function expectedTypes(field) {
2218
+ if (field === "textDecoration") return ["keyword"];
1208
2219
  if (COLOR_FIELDS.has(field)) return ["color"];
1209
2220
  if (field === "fontFamily") return ["fontFamily"];
1210
2221
  if (field === "fontWeight") return ["fontWeight", "number"];
2222
+ if (field === "opacity") return ["number"];
1211
2223
  return ["dimension", "number"];
1212
2224
  }
1213
2225
  function partValues(node, computed, own, tokens, parent) {
1214
2226
  const out = { values: {}, unmapped: [] };
1215
2227
  const set = (field, prop, raw, skipZero = false) => {
2228
+ if (raw !== void 0 && NUMBER_FIELDS.has(field) && /^-?(\d+\.?\d*|\.\d+)$/.test(raw.trim())) {
2229
+ out.values[field] = Number(raw.trim());
2230
+ return;
2231
+ }
2232
+ const em = raw !== void 0 ? /^(\d*\.?\d+)em$/.exec(raw.trim()) : null;
2233
+ if (em) {
2234
+ const base = prop === "font-size" ? parent?.get("font-size") : computed.get("font-size");
2235
+ const b = resolveValue(base, prop === "font-size" ? parent ?? /* @__PURE__ */ new Map() : computed, tokens, 0, parent);
2236
+ if (b && typeof b.value === "string" && !b.value.startsWith("calc(")) {
2237
+ const n = Number(em[1]);
2238
+ out.values[field] = n === 1 ? b.value : `calc(${b.value} * ${n})`;
2239
+ return;
2240
+ }
2241
+ }
1216
2242
  const r = resolveValue(raw, computed, tokens, 0, parent, prop === "color");
1217
2243
  if (!r) return;
1218
2244
  if (r.unmapped) {
@@ -1230,8 +2256,17 @@ function partValues(node, computed, own, tokens, parent) {
1230
2256
  }
1231
2257
  out.values[field] = r.value;
1232
2258
  };
2259
+ const opacity = own.get("opacity");
2260
+ const setOpacity = () => {
2261
+ if (opacity !== void 0 && !/^(0|1|1\.0+)$/.test(opacity.trim())) set("opacity", "opacity", opacity);
2262
+ };
1233
2263
  if (node.kind === "text") {
1234
2264
  for (const [prop, field] of TEXT_FIELDS) set(field, prop, computed.get(prop));
2265
+ const line = computed.get("text-decoration-line") ?? "";
2266
+ const lineColor = (computed.get("text-decoration-color") ?? "").trim().toLowerCase();
2267
+ const keyword = /underline/.test(line) ? "UNDERLINE" : /line-through/.test(line) ? "STRIKETHROUGH" : void 0;
2268
+ if (keyword && lineColor !== "transparent") out.values.textDecoration = keyword;
2269
+ setOpacity();
1235
2270
  return out;
1236
2271
  }
1237
2272
  if (node.kind === "icon") {
@@ -1239,9 +2274,14 @@ function partValues(node, computed, own, tokens, parent) {
1239
2274
  set("height", "height", computed.get("height"));
1240
2275
  set("stroke", "color", computed.get("color"));
1241
2276
  set("strokeWeight", "stroke-width", computed.get("stroke-width"));
2277
+ setOpacity();
1242
2278
  return out;
1243
2279
  }
1244
- for (const [prop, field] of FRAME_FIELDS) set(field, prop, computed.get(prop), field === "minWidth" || field === "minHeight");
2280
+ setOpacity();
2281
+ const layout = layoutOf(computed);
2282
+ const gap = computed.get(layout.direction === "HORIZONTAL" ? "column-gap" : "row-gap");
2283
+ for (const [prop, field] of FRAME_FIELDS) set(field, prop, field === "itemSpacing" ? gap : computed.get(prop), field === "minWidth" || field === "minHeight");
2284
+ if (layout.wrap) set("counterAxisSpacing", "row-gap", computed.get("row-gap"));
1245
2285
  const widths = ["top", "right", "bottom", "left"].map((s) => computed.get(`border-${s}-width`));
1246
2286
  const colors = ["top", "right", "bottom", "left"].map((s) => computed.get(`border-${s}-color`));
1247
2287
  const present = widths.map((w) => w !== void 0 && !isZero(w));
@@ -1259,7 +2299,23 @@ function partValues(node, computed, own, tokens, parent) {
1259
2299
  }
1260
2300
  return out;
1261
2301
  }
2302
+ 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");
2303
+ 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" };
2304
+ const gridCounter = (v) => /center/.test(v ?? "") ? "CENTER" : /end/.test(v ?? "") ? "MAX" : /start/.test(v ?? "") ? "MIN" : "STRETCH";
1262
2305
  function layoutOf(computed) {
2306
+ const shown = computed.get("display");
2307
+ if (shown === "table-row") return { direction: "HORIZONTAL", primary: "MIN", counter: "STRETCH" };
2308
+ if (shown === "table-cell") {
2309
+ const va = computed.get("vertical-align") ?? "middle";
2310
+ const ta = computed.get("text-align") ?? "start";
2311
+ return { direction: "VERTICAL", primary: /middle/.test(va) ? "CENTER" : /bottom/.test(va) ? "MAX" : "MIN", counter: /center/.test(ta) ? "CENTER" : /(end|right)/.test(ta) ? "MAX" : "MIN" };
2312
+ }
2313
+ if (singleCellGrid(computed)) {
2314
+ const align2 = computed.get("align-items") ?? "stretch";
2315
+ const justify2 = computed.get("justify-items") ?? "stretch";
2316
+ const at = (v) => /center/.test(v) ? "CENTER" : /end/.test(v) ? "MAX" : "MIN";
2317
+ return { direction: "VERTICAL", primary: at(align2), counter: /stretch|normal/.test(justify2) ? "STRETCH" : at(justify2) };
2318
+ }
1263
2319
  const dir = computed.get("flex-direction") ?? "row";
1264
2320
  const display = computed.get("display") ?? "block";
1265
2321
  const flex = /flex/.test(display);
@@ -1267,48 +2323,267 @@ function layoutOf(computed) {
1267
2323
  const align = computed.get("align-items") ?? "normal";
1268
2324
  const primary = /center/.test(justify) ? "CENTER" : /space-between/.test(justify) ? "SPACE_BETWEEN" : /end/.test(justify) ? "MAX" : "MIN";
1269
2325
  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" };
2326
+ return {
2327
+ direction: flex && !/column/.test(dir) ? "HORIZONTAL" : "VERTICAL",
2328
+ primary,
2329
+ counter: flex ? counter : "STRETCH",
2330
+ ...flex && /reverse/.test(dir) ? { reverse: true } : {},
2331
+ ...flex && !/column/.test(dir) && /^wrap/.test(computed.get("flex-wrap") ?? "") ? { wrap: true } : {}
2332
+ };
2333
+ }
2334
+ function grows(c) {
2335
+ const flex = c?.get("flex")?.trim();
2336
+ const grow = c?.get("flex-grow") ?? (flex && /^\d/.test(flex) ? flex.split(/\s+/)[0] : flex === "auto" ? "1" : void 0);
2337
+ return grow !== void 0 && Number(grow) >= 1;
2338
+ }
2339
+ function shrinksToFit(n, c, parent) {
2340
+ 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
2341
  }
1272
2342
  const clips = (c) => /^(hidden|clip)\b/.test(c?.get("overflow") ?? c?.get("overflow-x") ?? "");
1273
- function fillsContainer(c) {
2343
+ const INLINE_TAGS = /* @__PURE__ */ new Set(["a", "span", "label", "kbd", "code", "em", "strong", "b", "i", "small", "abbr", "button", "input", "textarea", "select", "img"]);
2344
+ function fillsContainer(c, tag = "div") {
1274
2345
  if (!c) return false;
1275
2346
  if (c.get("width") === "100%") return true;
1276
- const display = c.get("display") ?? "block";
2347
+ const display = c.get("display") ?? (INLINE_TAGS.has(tag) ? "inline" : "block");
1277
2348
  return /^(block|flex|grid|flow-root)$/.test(display);
1278
2349
  }
1279
- function evaluateVariant(reader, src, rules, props, state, tokens) {
1280
- const built = build(reader, src, props);
2350
+ class UnsupportedComponentError extends Error {
2351
+ constructor(component, reason) {
2352
+ super(`${component}: ${reason}`);
2353
+ this.component = component;
2354
+ this.reason = reason;
2355
+ }
2356
+ component;
2357
+ reason;
2358
+ }
2359
+ function svgVector(node, id, els, rules, state, computed, tokens) {
2360
+ const esc = (v) => v.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
2361
+ const num = (raw, values) => {
2362
+ if (raw === void 0) return void 0;
2363
+ const t = raw.trim();
2364
+ if (/^-?\d*\.?\d+$/.test(t)) return Number(t);
2365
+ const sub = t.replace(/var\(\s*(--[\w-]+)\s*(?:,\s*([^()]+))?\)/g, (_m, name, fb) => values.get(name) ?? fb ?? "NaN");
2366
+ if (/^calc\(/.test(sub) && /^[\d\s.+\-*/()calc]+$/.test(sub)) {
2367
+ try {
2368
+ const n = Number(new Function(`return (${sub.replace(/calc/g, "")});`)());
2369
+ return Number.isFinite(n) ? n : void 0;
2370
+ } catch {
2371
+ return void 0;
2372
+ }
2373
+ }
2374
+ return void 0;
2375
+ };
2376
+ const strokes = [];
2377
+ const shapes = [];
2378
+ for (const [i, shape] of (node.shapes ?? []).entries()) {
2379
+ const el = els.get(`${id}#shape-${i}`);
2380
+ const own = new Map([...declaredFor(rules, el, state)].map(([k, d]) => [k, d.value]));
2381
+ const c = computeValues(declaredFor(rules, el, state), computed);
2382
+ const attrs = { ...shape.svgAttrs };
2383
+ const fill = own.get("fill") ?? attrs.fill;
2384
+ if (fill && !/^(none|transparent)$/.test(fill)) return { blocker: "draws SVG shapes with fills" };
2385
+ const strokeRaw = own.get("stroke") ?? attrs.stroke ?? node.svgAttrs?.stroke ?? "currentcolor";
2386
+ const color = resolveValue(strokeRaw, c, tokens, 0, computed, false);
2387
+ if (!color || color.unmapped !== void 0 || color.value === void 0) return { blocker: "draws SVG shapes in colors that are not tokens" };
2388
+ strokes.push(color.value);
2389
+ const out = { ...attrs, fill: "none", stroke: "#000000" };
2390
+ const width = num(own.get("stroke-width") ?? attrs["stroke-width"] ?? attrs.strokeWidth, c);
2391
+ if (width !== void 0) out["stroke-width"] = String(width);
2392
+ const opacity = num(own.get("opacity"), c);
2393
+ if (opacity !== void 0 && opacity !== 1) out.opacity = String(opacity);
2394
+ const cap = own.get("stroke-linecap");
2395
+ if (cap) out["stroke-linecap"] = cap;
2396
+ const pathLength = num(attrs.pathLength, c);
2397
+ const r = num(attrs.r, c);
2398
+ const scale = pathLength && r !== void 0 ? 2 * Math.PI * r / pathLength : 1;
2399
+ const dash = own.get("stroke-dasharray");
2400
+ if (dash) {
2401
+ const parts = splitTop(dash, /\s|,/).map((x) => num(x, c));
2402
+ if (parts.some((x) => x === void 0)) return { blocker: "draws SVG shapes with dashes it cannot read" };
2403
+ out["stroke-dasharray"] = parts.map((x) => +(x * scale).toFixed(3)).join(" ");
2404
+ }
2405
+ const offset = own.get("stroke-dashoffset");
2406
+ if (offset) {
2407
+ const o = num(offset, c);
2408
+ if (o === void 0) return { blocker: "draws SVG shapes with dashes it cannot read" };
2409
+ out["stroke-dashoffset"] = String(+(o * scale).toFixed(3));
2410
+ }
2411
+ delete out.pathLength;
2412
+ delete out.strokeWidth;
2413
+ delete out.key;
2414
+ shapes.push(`<${shape.tag} ${Object.entries(out).map(([k, v]) => `${k}="${esc(v)}"`).join(" ")}/>`);
2415
+ }
2416
+ if (!shapes.length) return { blocker: "draws SVG it cannot read" };
2417
+ const viewBox = node.svgAttrs?.viewBox ?? "0 0 24 24";
2418
+ const [, , w, h] = viewBox.split(/\s+/).map(Number);
2419
+ const rot = /^rotate\((-?\d+(?:\.\d+)?)deg\)$/.exec(computed.get("transform")?.trim() ?? "");
2420
+ const body = rot ? `<g transform="rotate(${rot[1]} ${(w ?? 24) / 2} ${(h ?? 24) / 2})">${shapes.join("")}</g>` : shapes.join("");
2421
+ return { svg: `<svg xmlns="http://www.w3.org/2000/svg" width="${w ?? 24}" height="${h ?? 24}" viewBox="${viewBox}" fill="none">${body}</svg>`, strokes };
2422
+ }
2423
+ const POPUP_PART = /\.(Portal|Popup|Positioner|Backdrop|Viewport)$/;
2424
+ function translateX(transform) {
2425
+ const m = /^translateX\(([\s\S]+)\)$/.exec(transform?.trim() ?? "");
2426
+ return m?.[1]?.trim();
2427
+ }
2428
+ const visuallyHidden = (own) => /inset\(50%\)/.test(own.get("clip-path") ?? "") || /rect\(0/.test(own.get("clip") ?? "");
2429
+ function evaluateVariant(reader, src, rules, props, state, tokens, preview, page, view, examples) {
2430
+ const built = build(reader, src, props, preview, view, rules, tokens, examples);
1281
2431
  if (!built) return void 0;
1282
2432
  const els = toCssTree(built);
2433
+ const gridRows = /* @__PURE__ */ new Map();
2434
+ const gridGrow = /* @__PURE__ */ new Set();
1283
2435
  const computedById = /* @__PURE__ */ new Map();
2436
+ const ownById = /* @__PURE__ */ new Map();
1284
2437
  const values = {};
1285
2438
  const unmapped = [];
1286
2439
  const layouts = /* @__PURE__ */ new Map();
2440
+ const hidden = /* @__PURE__ */ new Set();
2441
+ const cover = /* @__PURE__ */ new Set();
2442
+ const pins = /* @__PURE__ */ new Map();
2443
+ const vectors = /* @__PURE__ */ new Map();
2444
+ const stretch = /* @__PURE__ */ new Map();
2445
+ const blockers = [...built.blockers];
1287
2446
  for (const [id, node] of built.parts) {
1288
2447
  const el = els.get(id);
1289
2448
  const parentId = built.parents.get(id);
1290
- const own = declaredFor(rules, el, state);
1291
- let computed = computeValues(own, parentId ? computedById.get(parentId) : void 0);
2449
+ const own = node.pseudo && el.parent ? declaredFor(rules, el.parent, state, node.pseudo) : declaredFor(rules, el, state);
2450
+ if (node.follows) {
2451
+ for (const [k, far] of [["left", ""], ["top", ""], ["width", "right"], ["height", "bottom"]]) {
2452
+ const d = own.get(k);
2453
+ if (!d || !runtimeVar(d.value, rules, tokens)) continue;
2454
+ if (far) {
2455
+ own.delete(k);
2456
+ own.set(far, { ...d, prop: far, value: "0" });
2457
+ } else own.set(k, { ...d, value: "0" });
2458
+ }
2459
+ }
2460
+ let computed = computeValues(own, parentId ? computedById.get(parentId) : page);
2461
+ for (const [k, v] of node.styleVars ?? []) computed.set(k, v);
2462
+ if (!computed.has("display") && TABLE_DISPLAY[node.tag]) computed.set("display", TABLE_DISPLAY[node.tag]);
2463
+ if (computed.get("display") === "table-cell" && resolveValue(computed.get("width"), computed, tokens) === void 0 || gridGrow.has(id)) computed.set("flex-grow", "1");
1292
2464
  const ownValues = new Map([...own].map(([k, d]) => [k, d.value]));
1293
- if (node.placeholder) {
1294
- const ph = declaredFor(rules, el, state, "placeholder");
2465
+ if (node.placeholder && el.parent) {
2466
+ const ph = declaredFor(rules, el.parent, state, "placeholder");
1295
2467
  computed = computeValues(ph, computed);
1296
2468
  }
1297
2469
  computedById.set(id, computed);
1298
- const pv = partValues(node, computed, ownValues, tokens, parentId ? computedById.get(parentId) : void 0);
2470
+ ownById.set(id, ownValues);
2471
+ if (POPUP_PART.test(node.tag) && parentId !== void 0) blockers.push("opens a popup");
2472
+ const parentHidden = parentId !== void 0 && hidden.has(parentId);
2473
+ const transform = ownValues.get("transform");
2474
+ 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) ?? ""))
2475
+ hidden.add(id);
2476
+ const pv = partValues(node, computed, ownValues, tokens, parentId ? computedById.get(parentId) : page);
1299
2477
  values[id] = pv.values;
2478
+ if (node.svgAttrs && node.tag === "svg") {
2479
+ const vec = svgVector(node, id, els, rules, state, computed, tokens);
2480
+ if ("blocker" in vec) blockers.push(vec.blocker);
2481
+ else vectors.set(id, vec);
2482
+ }
2483
+ if (node.kind === "frame") layouts.set(id, node.inputLike && node.tag !== "textarea" ? { direction: "HORIZONTAL", primary: "MIN", counter: "CENTER" } : layoutOf(computed));
2484
+ let gridDrawn = false;
2485
+ if (node.kind === "frame" && /grid/.test(computed.get("display") ?? "") && !singleCellGrid(computed)) {
2486
+ const kids = [...built.parts].filter(([cid]) => built.parents.get(cid) === id);
2487
+ const tracks = computed.get("grid-template-columns")?.trim();
2488
+ const parentGrid = parentId ? gridRows.get(parentId) : void 0;
2489
+ if (tracks === "subgrid" && parentGrid) {
2490
+ gridDrawn = true;
2491
+ layouts.set(id, { direction: "HORIZONTAL", primary: "MIN", counter: gridCounter(computed.get("align-items")) });
2492
+ const gap = parentGrid.gap === void 0 ? void 0 : resolveValue(parentGrid.gap, computedById.get(parentId) ?? /* @__PURE__ */ new Map(), tokens);
2493
+ const g = gap && gap.unmapped === void 0 && gap.value !== null ? gap.value : void 0;
2494
+ if (g !== void 0) {
2495
+ pv.values.itemSpacing = g;
2496
+ if (/^0(px)?$/.test(parentGrid.tracks[0] ?? "")) pv.values.paddingLeft = g;
2497
+ if (/^0(px)?$/.test(parentGrid.tracks[parentGrid.tracks.length - 1] ?? "")) pv.values.paddingRight = g;
2498
+ }
2499
+ for (const [cid] of kids) {
2500
+ 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]);
2501
+ if (start && /fr\)?$/.test(parentGrid.tracks[start - 1] ?? "")) gridGrow.add(cid);
2502
+ }
2503
+ 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" });
2504
+ } 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() ?? ""))) {
2505
+ gridDrawn = true;
2506
+ gridRows.set(id, { tracks: splitTop(tracks, /\s/).filter(Boolean), gap: computed.get("column-gap") });
2507
+ layouts.set(id, { direction: "VERTICAL", primary: "MIN", counter: "STRETCH" });
2508
+ }
2509
+ }
2510
+ if (hidden.has(id)) continue;
2511
+ for (const pseudo of ["before", "after"]) {
2512
+ if (node.children.some((c) => c.pseudo === pseudo)) continue;
2513
+ const d = declaredFor(rules, el, state, pseudo);
2514
+ const paints = [...PSEUDO_PAINTS, "box-shadow"].some((k) => d.has(k) && !/^(0|none|transparent)$/.test(d.get(k).value));
2515
+ if (d.has("content") && paints) blockers.push("draws with ::before or ::after");
2516
+ }
2517
+ 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))
2518
+ blockers.push("has parts whose content the generator cannot read yet");
2519
+ if (/grid/.test(computed.get("display") ?? "") && !singleCellGrid(computed) && !gridDrawn) blockers.push("uses CSS grid");
2520
+ if (node.fraction !== void 0 && ownValues.get("height") === void 0) stretch.set(id, { ...stretch.get(id), height: true });
2521
+ 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" });
2522
+ const position = ownValues.get("position");
2523
+ const full = ownValues.get("width") === "100%" && ownValues.get("height") === "100%";
2524
+ const pVals = parentId ? values[parentId] : void 0;
2525
+ const inset = ["top", "right", "bottom", "left"].every((k) => /^0(px)?$/.test(ownValues.get(k) ?? ""));
2526
+ if (parentId && (position === "absolute" && (inset || full) || position !== "absolute" && full && pVals?.width !== void 0 && pVals?.height !== void 0)) {
2527
+ cover.add(id);
2528
+ delete pv.values.width;
2529
+ delete pv.values.height;
2530
+ pv.unmapped = pv.unmapped.filter((u) => u.property !== "width" && u.property !== "height");
2531
+ } else if (parentId && position !== "absolute" && (ownValues.get("width") === "100%" || ownValues.get("height") === "100%")) {
2532
+ stretch.set(id, { ...ownValues.get("width") === "100%" ? { width: true } : {}, ...ownValues.get("height") === "100%" ? { height: true } : {} });
2533
+ pv.unmapped = pv.unmapped.filter((u) => !((u.property === "width" || u.property === "height") && u.value === "100%"));
2534
+ for (const k of ["width", "height"]) if (ownValues.get(k) === "100%") delete pv.values[k];
2535
+ } else if (parentId && position === "absolute") {
2536
+ const pin = {};
2537
+ let ok = true;
2538
+ for (const side of ["top", "right", "bottom", "left"]) {
2539
+ const raw = ownValues.get(side);
2540
+ if (raw === void 0 || raw === "auto") continue;
2541
+ const r = resolveValue(raw, computed, tokens);
2542
+ if (!r || r.unmapped !== void 0 || r.value === null || r.value === void 0) ok = false;
2543
+ else pin[side] = r.value;
2544
+ }
2545
+ if (ok && Object.keys(pin).length) pins.set(id, pin);
2546
+ else blockers.push("places layers over each other");
2547
+ } else if (parentId && position === "fixed") blockers.push("places layers over each other");
1300
2548
  for (const u of pv.unmapped) unmapped.push({ part: id, ...u });
1301
- if (node.kind === "frame") layouts.set(id, layoutOf(computed));
2549
+ if (transform && !/^(none|scale\(1\))$/.test(transform.trim()) && !translateX(transform) && /^(translate|rotate|scale)/.test(transform.trim()) && !/^0(\.0+)?$/.test(ownValues.get("opacity") ?? "1")) {
2550
+ unmapped.push({ part: id, property: "transform", value: transform, reason: "transforms are not drawn" });
2551
+ }
2552
+ }
2553
+ for (const [id] of built.parts) {
2554
+ const tx = translateX(ownById.get(id)?.get("transform"));
2555
+ if (!tx || hidden.has(id)) continue;
2556
+ const parentId = built.parents.get(id);
2557
+ const siblings = [...built.parts].filter(([cid, n]) => built.parents.get(cid) === parentId && !hidden.has(cid) && !n.visible);
2558
+ const parentLayout = parentId ? layouts.get(parentId) : void 0;
2559
+ const r = resolveValue(tx, computedById.get(id), tokens);
2560
+ if (r?.value === 0) continue;
2561
+ const pv = parentId ? values[parentId] : void 0;
2562
+ const own = values[id] ?? {};
2563
+ if (!parentId || !pv || siblings.length !== 1 || parentLayout?.direction !== "HORIZONTAL" || !r || r.unmapped !== void 0 || r.value === null || r.value === void 0) {
2564
+ blockers.push("moves a part with a transform");
2565
+ continue;
2566
+ }
2567
+ const v = r.value;
2568
+ const end = typeof v === "string" && v.startsWith("calc(") ? new RegExp(`^calc\\(${escapeRe(String(pv.width))} - ${escapeRe(String(own.width))} - (\\S+)\\)$`).exec(v) : null;
2569
+ if (end) {
2570
+ pv.paddingRight = end[1];
2571
+ layouts.set(parentId, { ...parentLayout, primary: "MAX" });
2572
+ } else if (typeof v === "number" || typeof v === "string" && !v.startsWith("calc(")) {
2573
+ pv.paddingLeft = v;
2574
+ layouts.set(parentId, { ...parentLayout, primary: "MIN" });
2575
+ } else blockers.push("moves a part with a transform");
1302
2576
  }
1303
2577
  const classes = [...built.parts].map(([id, n]) => `${id}:${n.classes.join(",")}:${n.visible?.prop ?? ""}`).join(";");
1304
- return { built, values, unmapped, layouts, classes, computed: computedById };
2578
+ return { built, values, unmapped, layouts, classes, computed: computedById, hidden, cover, pins, vectors, stretch, blockers };
1305
2579
  }
2580
+ const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1306
2581
  function product(axes) {
1307
2582
  let out = [{}];
1308
2583
  for (const a of axes) out = out.flatMap((p) => a.values.map((v) => ({ ...p, [a.name]: v })));
1309
2584
  return out;
1310
2585
  }
1311
- const sameValues = (a, b) => JSON.stringify(a) === JSON.stringify(b);
2586
+ const drawn = (e) => JSON.stringify([e.values, [...e.hidden].sort(), [...e.layouts], [...e.vectors]]);
1312
2587
  const BOX_FIELDS = ["paddingTop", "paddingRight", "paddingBottom", "paddingLeft", "itemSpacing"];
1313
2588
  const PAINT_FIELDS = ["fill", "stroke", "strokeWeight", "strokeTopWeight", "strokeRightWeight", "strokeBottomWeight", "strokeLeftWeight", "cornerRadius", "width", "height", "minWidth", "minHeight"];
1314
2589
  const empty = (v) => v === void 0 || v === 0 || v === null;
@@ -1316,11 +2591,12 @@ function flattenWrapper(parts, variants, unmapped) {
1316
2591
  const root = parts.find((p) => !p.parent);
1317
2592
  const children = parts.filter((p) => p.parent === root?.id);
1318
2593
  const w = children[0];
1319
- if (!root || root.kind !== "frame" || children.length !== 1 || !w || w.kind !== "frame" || w.visible || w.clip) return;
2594
+ if (!root || root.kind !== "frame" || children.length !== 1 || !w || w.kind !== "frame" || w.visible || w.clip || w.cover) return;
1320
2595
  for (const v of variants) {
1321
2596
  const r = v.values[root.id] ?? {};
1322
2597
  const x = v.values[w.id] ?? {};
1323
- if (BOX_FIELDS.some((f) => !empty(r[f])) || PAINT_FIELDS.some((f) => !empty(x[f]))) return;
2598
+ if (BOX_FIELDS.some((f) => !empty(r[f])) || PAINT_FIELDS.some((f) => !empty(x[f])) || x.opacity !== void 0) return;
2599
+ if (v.hidden?.includes(w.id) || v.layout?.[w.id] || v.layout?.[root.id]) return;
1324
2600
  }
1325
2601
  for (const v of variants) {
1326
2602
  const r = v.values[root.id] ??= {};
@@ -1334,11 +2610,37 @@ function flattenWrapper(parts, variants, unmapped) {
1334
2610
  for (const u of unmapped) if (u.part === w.id) u.part = root.id;
1335
2611
  parts.splice(parts.indexOf(w), 1);
1336
2612
  }
1337
- function generateComponentSpec(name, opts) {
2613
+ function mergeFieldBoxes(parts, variants, unmapped, fields) {
2614
+ for (const box of parts.filter((p) => fields.has(p.id))) {
2615
+ const kids = parts.filter((p) => p.parent === box.id);
2616
+ const text = kids[0];
2617
+ if (kids.length !== 1 || text?.kind !== "text" || !box.parent || box.visible || box.cover) continue;
2618
+ const drawsNothing = variants.every((v) => {
2619
+ const x = v.values[box.id] ?? {};
2620
+ 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];
2621
+ });
2622
+ if (!drawsNothing) continue;
2623
+ text.parent = box.parent;
2624
+ if (box.source) text.source = box.source;
2625
+ for (const v of variants) delete v.values[box.id];
2626
+ for (const u of unmapped) if (u.part === box.id) u.part = text.id;
2627
+ parts.splice(parts.indexOf(box), 1);
2628
+ }
2629
+ }
2630
+ 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"];
2631
+ function mainReason(reasons) {
2632
+ const rank = (r) => {
2633
+ const i = REASON_ORDER.indexOf(r);
2634
+ return i < 0 ? REASON_ORDER.length : i;
2635
+ };
2636
+ return [...new Set(reasons)].sort((a, b) => rank(a) - rank(b))[0];
2637
+ }
2638
+ const CHECKED = { false: "false", true: "true", mixed: "mixed" };
2639
+ function generateComponentSpec(name, opts, view, setName = name, fixed = {}) {
1338
2640
  const reader = new Reader(opts);
1339
2641
  const src = reader.load(name);
1340
2642
  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}.`);
2643
+ if (!src.fn) throw new UnsupportedComponentError(name, "is shown from a hook or a provider, not placed as a component");
1342
2644
  const modules = /* @__PURE__ */ new Map();
1343
2645
  const addModules = (s) => {
1344
2646
  if (!s) return;
@@ -1366,16 +2668,49 @@ function generateComponentSpec(name, opts) {
1366
2668
  visit(s.sf);
1367
2669
  };
1368
2670
  findNested(src, 0);
1369
- const ordered = [...modules].sort(([a], [b]) => Number(a.startsWith(`${opts.componentsDir}/${name}/`)) - Number(b.startsWith(`${opts.componentsDir}/${name}/`)));
2671
+ const docs = readDocs(src.folder, opts);
2672
+ const fromDocs = previewElement(docs?.preview, name);
2673
+ const previewEl = fromDocs ?? storyElement(readStories(src.folder, opts), name);
2674
+ const previewKids = previewEl?.children?.some((c) => typeof c !== "string") ? previewEl.children : void 0;
2675
+ for (const c of docElements(previewKids)) {
2676
+ if (typeof c.c !== "string" || nestedNames.has(c.c) || !reader.exists(c.c)) continue;
2677
+ nestedNames.add(c.c);
2678
+ const other = reader.load(c.c);
2679
+ addModules(other);
2680
+ if (other) findNested(other, 1);
2681
+ }
2682
+ const ownDir = `${opts.componentsDir}/${src.folder}/`;
2683
+ const ordered = [...modules].sort(([a], [b]) => Number(a.startsWith(ownDir)) - Number(b.startsWith(ownDir)));
1370
2684
  const rules = [];
1371
2685
  const cssErrors = [];
2686
+ let page;
2687
+ const baseText = opts.baseCss ? opts.read(opts.baseCss) : void 0;
2688
+ if (baseText !== void 0) {
2689
+ const r = parseCssModule(opts.baseCss, baseText, 0);
2690
+ rules.push(...r.rules);
2691
+ const html = { tag: "html", classes: /* @__PURE__ */ new Set(), children: [], present: true, isRoot: false, inputLike: false };
2692
+ page = computeValues(declaredFor(r.rules, html, DEFAULT_STATE), void 0);
2693
+ }
1372
2694
  for (const [path, text] of ordered) {
1373
2695
  const r = parseCssModule(path, text, rules.length);
1374
2696
  rules.push(...r.rules);
1375
2697
  cssErrors.push(...r.errors);
1376
2698
  }
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.`);
2699
+ const baseProps = {};
2700
+ for (const p of src.props.values()) {
2701
+ const v = previewEl?.p?.[p.name];
2702
+ if (v === void 0 || v === null) continue;
2703
+ if (p.kind === "string" && typeof v === "string" && (!p.optional || p.name === "defaultValue")) baseProps[p.name] = v;
2704
+ if (p.kind === "other" && (typeof v === "object" || typeof v === "number" || typeof v === "string" && p.name === "defaultValue")) baseProps[p.name] = jsonValue(v);
2705
+ }
2706
+ const examples = {};
2707
+ for (const p of src.props.values()) {
2708
+ const v = previewEl?.p?.[p.name];
2709
+ if (p.kind === "string" && p.optional && typeof v === "string" && baseProps[p.name] === void 0) examples[p.name] = v;
2710
+ }
2711
+ const evalAt = (props, state) => evaluateVariant(reader, src, rules, { ...baseProps, ...props, ...fixed }, state, opts.tokens, previewKids, page, view, Object.keys(examples).length ? examples : void 0);
2712
+ const base = evalAt({}, DEFAULT_STATE);
2713
+ if (!base) throw new UnsupportedComponentError(name, "renders in a way the generator cannot read yet");
1379
2714
  const unmappedProps = [];
1380
2715
  const axes = [];
1381
2716
  const defaults = /* @__PURE__ */ new Map();
@@ -1385,8 +2720,8 @@ function generateComponentSpec(name, opts) {
1385
2720
  for (const [prop] of src.props) defaults.set(prop, scope.vars.get(prop));
1386
2721
  }
1387
2722
  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);
2723
+ if (p.kind !== "union" || !p.values || fixed[p.name] !== void 0) continue;
2724
+ const changes = p.values.some((v) => evalAt({ [p.name]: v }, DEFAULT_STATE)?.classes !== base.classes);
1390
2725
  if (!changes) {
1391
2726
  unmappedProps.push({ part: "root", property: `prop ${p.name}`, value: p.values.join(" | "), reason: "does not change the styles" });
1392
2727
  continue;
@@ -1394,15 +2729,25 @@ function generateComponentSpec(name, opts) {
1394
2729
  const def = defaults.get(p.name);
1395
2730
  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
2731
  }
2732
+ const checkable = ["checked", "defaultChecked", "indeterminate"].some((p) => src.props.has(p)) || [...src.baseUiModules].some((m) => /^(checkbox|switch|radio|toggle|menu)/.test(m));
2733
+ if (view !== "popup" && checkable) {
2734
+ const on = evalAt({}, { ...DEFAULT_STATE, checked: "true" });
2735
+ const off = evalAt({}, { ...DEFAULT_STATE, checked: "false" });
2736
+ if (on && off && drawn(on) !== drawn(off)) {
2737
+ const mixed = src.props.has("indeterminate") ? evalAt({}, { ...DEFAULT_STATE, checked: "mixed" }) : void 0;
2738
+ axes.push({ name: "Checked", values: mixed && drawn(mixed) !== drawn(on) ? ["false", "true", "mixed"] : ["false", "true"], default: "false" });
2739
+ }
2740
+ }
1397
2741
  const combos = product(axes);
1398
- const propsFor = (combo) => Object.fromEntries(axes.map((a) => [a.prop, combo[a.name]]));
2742
+ const propsFor = (combo) => Object.fromEntries(axes.filter((a) => a.prop).map((a) => [a.prop, combo[a.name]]));
2743
+ const stateFor = (combo, s) => combo.Checked ? { ...s, checked: CHECKED[combo.Checked] } : s;
1399
2744
  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);
2745
+ const states = view === "popup" ? STATES.slice(0, 1) : STATES.filter((s) => s.name !== "Disabled" || canDisable);
1401
2746
  const byState = /* @__PURE__ */ new Map();
1402
2747
  for (const s of states) {
1403
2748
  const m = /* @__PURE__ */ new Map();
1404
2749
  for (const combo of combos) {
1405
- const ev = evaluateVariant(reader, src, rules, propsFor(combo), s.state, opts.tokens);
2750
+ const ev = evalAt(propsFor(combo), stateFor(combo, s.state));
1406
2751
  if (ev) m.set(JSON.stringify(combo), ev);
1407
2752
  }
1408
2753
  byState.set(s.name, m);
@@ -1410,51 +2755,140 @@ function generateComponentSpec(name, opts) {
1410
2755
  const differs = (s, from) => combos.some((c) => {
1411
2756
  const a = byState.get(s).get(JSON.stringify(c));
1412
2757
  const b = byState.get(from)?.get(JSON.stringify(c));
1413
- return a && b && !sameValues(a.values, b.values);
2758
+ return a && b && drawn(a) !== drawn(b);
1414
2759
  });
1415
2760
  const stateNames = states.map((s) => s.name).filter((s) => s === "Default" || differs(s, "Default") && (s !== "Pressed" || differs(s, "Hover")));
1416
2761
  if (stateNames.length > 1) axes.push({ name: "State", values: stateNames, default: "Default" });
1417
- const variants = [];
1418
- const unmapped = [];
2762
+ const blockers = /* @__PURE__ */ new Set();
2763
+ const evaluated = [];
1419
2764
  for (const combo of combos) {
1420
2765
  for (const s of stateNames.length > 1 ? stateNames : ["Default"]) {
1421
2766
  const ev = byState.get(s).get(JSON.stringify(combo));
1422
2767
  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);
2768
+ evaluated.push({ props: stateNames.length > 1 ? { ...combo, State: s } : { ...combo }, ev });
2769
+ for (const b of ev.blockers) blockers.add(b);
2770
+ }
2771
+ }
2772
+ const order = [...base.built.parts.keys()];
2773
+ const owner = new Map(order.map((id) => [id, base]));
2774
+ for (const { ev } of evaluated) {
2775
+ let prev;
2776
+ for (const id of ev.built.parts.keys()) {
2777
+ const known = owner.get(id);
2778
+ if (known && known.built.parents.get(id) !== ev.built.parents.get(id)) blockers.add("changes its layers between variants");
2779
+ if (!known) {
2780
+ order.splice(prev === void 0 ? 0 : order.indexOf(prev) + 1, 0, id);
2781
+ owner.set(id, ev);
2782
+ }
2783
+ prev = id;
2784
+ }
2785
+ }
2786
+ for (const { ev } of evaluated) for (const id of order) if (!ev.built.parts.has(id)) ev.hidden.add(id);
2787
+ if (blockers.size) throw new UnsupportedComponentError(name, mainReason([...blockers]));
2788
+ for (const axis of [...axes]) {
2789
+ if (!axis.prop) continue;
2790
+ const key = (p) => JSON.stringify(Object.entries(p).filter(([k]) => k !== axis.name));
2791
+ const groups = /* @__PURE__ */ new Map();
2792
+ for (const e of evaluated) groups.set(key(e.props), [...groups.get(key(e.props)) ?? [], drawn(e.ev)]);
2793
+ if ([...groups.values()].some((g) => new Set(g).size > 1)) continue;
2794
+ axes.splice(axes.indexOf(axis), 1);
2795
+ 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" });
2796
+ for (let i = evaluated.length - 1; i >= 0; i--) {
2797
+ const e = evaluated[i];
2798
+ if (e.props[axis.name] !== axis.default) {
2799
+ evaluated.splice(i, 1);
2800
+ continue;
2801
+ }
2802
+ delete e.props[axis.name];
2803
+ }
2804
+ }
2805
+ const variants = [];
2806
+ const unmapped = [];
2807
+ const neverShown = new Set(order.filter((id) => evaluated.every((e) => e.ev.hidden.has(id))));
2808
+ for (const e of evaluated) {
2809
+ const ev = e.ev;
2810
+ const values = Object.fromEntries(Object.entries(ev.values).filter(([id]) => !neverShown.has(id)));
2811
+ const parentOf = (id) => owner.get(id)?.built.parents.get(id) ?? "";
2812
+ const hidden = order.filter((id) => ev.hidden.has(id) && !neverShown.has(id) && !neverShown.has(parentOf(id)));
2813
+ const layout = {};
2814
+ for (const [id, l] of ev.layouts) if (JSON.stringify(l) !== JSON.stringify(owner.get(id)?.layouts.get(id))) layout[id] = l;
2815
+ const icons = {};
2816
+ for (const [id, n] of ev.built.parts) if (n.kind === "icon" && n.icon && !neverShown.has(id)) icons[id] = iconComponentName(n.icon);
2817
+ const vectors = Object.fromEntries([...ev.vectors].filter(([id]) => !neverShown.has(id)));
2818
+ const pinOf = (x, id) => x.cover.has(id) ? { top: 0, right: 0, bottom: 0, left: 0 } : x.pins.get(id);
2819
+ const pins = {};
2820
+ for (const id of order) {
2821
+ const own = pinOf(ev, id);
2822
+ const base2 = owner.get(id) && pinOf(owner.get(id), id);
2823
+ if (own && base2 && JSON.stringify(own) !== JSON.stringify(base2) && !neverShown.has(id)) pins[id] = own;
1426
2824
  }
2825
+ variants.push({
2826
+ props: e.props,
2827
+ values,
2828
+ ...Object.keys(icons).length ? { slots: icons } : {},
2829
+ ...hidden.length ? { hidden } : {},
2830
+ ...Object.keys(layout).length ? { layout } : {},
2831
+ ...Object.keys(vectors).length ? { vectors } : {},
2832
+ ...Object.keys(pins).length ? { pins } : {}
2833
+ });
2834
+ unmapped.push(...ev.unmapped.filter((u) => !neverShown.has(u.part)));
1427
2835
  }
1428
- if (variants.length > MAX_VARIANTS) throw new Error(`${name}: ${variants.length} variants, more than ${MAX_VARIANTS}. Split an axis before generating.`);
2836
+ if (variants.length > MAX_VARIANTS) throw new UnsupportedComponentError(name, `has ${variants.length} variants, more than ${MAX_VARIANTS}`);
1429
2837
  const parts = [];
1430
2838
  const properties = [];
1431
- for (const [id, n] of base.built.parts) {
1432
- const parent = base.built.parents.get(id);
2839
+ const dropped = /* @__PURE__ */ new Set();
2840
+ for (const id of order) {
2841
+ const at = owner.get(id);
2842
+ const n = at.built.parts.get(id);
2843
+ const parent = at.built.parents.get(id);
2844
+ if (neverShown.has(id) || parent && dropped.has(parent)) {
2845
+ dropped.add(id);
2846
+ continue;
2847
+ }
2848
+ const cls = n.classes[0]?.split("|")[1];
1433
2849
  const part = {
1434
2850
  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,
2851
+ 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),
2852
+ kind: n.svgAttrs && n.tag === "svg" ? "vector" : n.kind,
1437
2853
  ...parent ? { parent } : {},
1438
2854
  ...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 } : {},
2855
+ ...n.kind === "frame" ? { layout: at.layouts.get(id) ?? layoutOf(at.computed.get(id) ?? /* @__PURE__ */ new Map()) } : {},
2856
+ ...n.kind === "frame" && clips(at.computed.get(id)) ? { clip: true } : {},
2857
+ ...!parent && fillsContainer(at.computed.get(id), n.tag) ? { fill: true } : {},
1442
2858
  ...n.text ? { text: n.text.property } : {},
1443
- ...n.visible ? { visible: n.visible.property } : {}
2859
+ ...n.chars !== void 0 ? { characters: n.chars } : {},
2860
+ ...n.visible ? { visible: n.visible.property } : {},
2861
+ ...at.cover.has(id) ? { cover: true } : {},
2862
+ ...at.pins.has(id) ? { pin: at.pins.get(id) } : {},
2863
+ ...at.stretch.has(id) ? { stretch: at.stretch.get(id) } : {},
2864
+ ...n.fraction !== void 0 ? { fraction: n.fraction } : {},
2865
+ ...n.at !== void 0 ? { at: n.at } : {},
2866
+ ...parent && (grows(at.computed.get(id)) || shrinksToFit(n, at.computed.get(id), at.layouts.get(parent))) ? { grow: true } : {},
2867
+ ...n.kind === "icon" && n.icon ? { icon: iconComponentName(n.icon) } : {}
1444
2868
  };
1445
2869
  parts.push(part);
1446
2870
  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
2871
  }
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 });
2872
+ for (const id of order) {
2873
+ const n = owner.get(id).built.parts.get(id);
2874
+ 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 });
2875
+ }
1450
2876
  const used = /* @__PURE__ */ new Set([...axes.map((a) => a.prop), ...properties.map((p) => p.prop), "disabled", "className", "children", "render"]);
2877
+ if (axes.some((a) => a.name === "Checked")) for (const p of ["checked", "defaultChecked", "indeterminate"]) used.add(p);
1451
2878
  for (const p of src.props.values()) {
1452
2879
  if (used.has(p.name) || p.kind === "function" || p.kind === "string" || p.kind === "other" || p.kind === "union") continue;
1453
2880
  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
2881
  }
1455
2882
  for (const e of cssErrors) unmappedProps.push({ part: "root", property: "css", value: e, reason: "selector not understood" });
2883
+ mergeFieldBoxes(parts, variants, unmapped, new Set([...base.built.parts].filter(([, n]) => n.inputLike).map(([id]) => id)));
1456
2884
  flattenWrapper(parts, variants, unmapped);
1457
- const swapped = addSwaps(name, src, parts, properties, axes, variants, opts);
2885
+ const compoundParts = new Set([...base.built.parts].filter(([, n]) => n.name).map(([id]) => id));
2886
+ const swapped = addSwaps(name, src, parts, properties, axes, variants, opts, compoundParts);
2887
+ for (const part of parts) {
2888
+ if (!base.built.parts.get(part.id)?.media || part.swap) continue;
2889
+ part.swap = { property: part.name, default: MEDIA_SLOT.name, options: [MEDIA_SLOT.name] };
2890
+ for (const v of variants) (v.slots ??= {})[part.id] = MEDIA_SLOT.name;
2891
+ }
1458
2892
  for (let i = unmappedProps.length - 1; i >= 0; i--) if (swapped.has(unmappedProps[i].property.replace(/^prop /, ""))) unmappedProps.splice(i, 1);
1459
2893
  const seen = /* @__PURE__ */ new Set();
1460
2894
  const dedup = [...unmappedProps, ...unmapped].filter((u) => {
@@ -1464,9 +2898,31 @@ function generateComponentSpec(name, opts) {
1464
2898
  return true;
1465
2899
  });
1466
2900
  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 };
2901
+ const shownExamples = Object.fromEntries(Object.entries(examples).filter(([k]) => properties.some((p) => p.type === "BOOLEAN" && p.prop === k && p.default === true)));
2902
+ const exampleProps = Object.fromEntries(Object.entries({ ...baseProps, ...shownExamples }).filter(([, v]) => isData(v)));
2903
+ 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;
2904
+ const component = {
2905
+ ...src.folder !== setName ? { component: src.folder } : {},
2906
+ ...Object.keys(fixed).length ? { fixed: { ...fixed } } : {},
2907
+ ...view ? { view } : {},
2908
+ ...example ? { example } : {}
2909
+ };
2910
+ const spec = { name: setName, ...component, source, axes, properties, parts, variants, unmapped: dedup };
2911
+ return { name: setName, ...component, hash: hashSpec(spec), source, axes, properties, parts, variants, unmapped: dedup };
2912
+ }
2913
+ function specDrawsSomething(spec) {
2914
+ const kinds = new Map(spec.parts.map((p) => [p.id, p]));
2915
+ for (const v of spec.variants)
2916
+ for (const [id, vals] of Object.entries(v.values)) {
2917
+ const part = kinds.get(id);
2918
+ if (part?.kind === "icon" || part?.kind === "vector") return true;
2919
+ if (part?.kind === "text" && part.text !== "Label") return true;
2920
+ if (part?.kind === "text" && (vals.textDecoration !== void 0 || typeof vals.textFill === "string" && vals.textFill !== "color.text.default")) return true;
2921
+ if (vals.fill !== void 0 && vals.fill !== null || vals.stroke !== void 0 && vals.stroke !== null) return true;
2922
+ }
2923
+ return false;
1469
2924
  }
2925
+ const ICON_COMPONENT = "Icon";
1470
2926
  const COMMON_ICONS = ["Plus", "Minus", "Pencil", "Trash2", "Copy", "Search", "X", "Check", "ChevronDown", "ChevronRight", "Info", "AlertTriangle", "Star", "Settings", "ExternalLink", "Download", "Upload", "Ellipsis"];
1471
2927
  const FALLBACK_ICON = { Badge: "Info" };
1472
2928
  const DEFAULT_FALLBACK_ICON = "Plus";
@@ -1541,13 +2997,13 @@ function allStatesIcon(sf, name, prop) {
1541
2997
  if (decl) visit(decl);
1542
2998
  return found;
1543
2999
  }
1544
- function defaultIcon(name, prop, opts) {
1545
- const docs = readDocs(name, opts);
3000
+ function defaultIcon(name, prop, opts, folder = name) {
3001
+ const docs = readDocs(folder, opts);
1546
3002
  for (const el of docElements(docs?.preview)) {
1547
3003
  const v = el.c === name ? el.p?.[prop] : void 0;
1548
3004
  if (typeof v === "string" && /^[A-Z]\w*$/.test(v)) return v;
1549
3005
  }
1550
- return allStatesIcon(readStories(name, opts), name, prop) ?? FALLBACK_ICON[name] ?? DEFAULT_FALLBACK_ICON;
3006
+ return allStatesIcon(readStories(folder, opts), name, prop) ?? FALLBACK_ICON[name] ?? DEFAULT_FALLBACK_ICON;
1551
3007
  }
1552
3008
  function referencedIcons(name, opts) {
1553
3009
  const out = [];
@@ -1557,7 +3013,7 @@ function referencedIcons(name, opts) {
1557
3013
  return out;
1558
3014
  }
1559
3015
  const zeroInline = (v) => !!v && v.paddingLeft === 0 && v.paddingRight === 0;
1560
- function addSwaps(name, src, parts, properties, axes, variants, opts) {
3016
+ function addSwaps(name, src, parts, properties, axes, variants, opts, compound) {
1561
3017
  const props = /* @__PURE__ */ new Set();
1562
3018
  const iconProps = [...src.props.values()].filter((p) => p.kind === "icon");
1563
3019
  const defaultKey = variantKeyOf(Object.fromEntries(axes.map((a) => [a.name, a.default])));
@@ -1570,14 +3026,14 @@ function addSwaps(name, src, parts, properties, axes, variants, opts) {
1570
3026
  const visibleProp = part.visible ? properties.find((p) => p.type === "BOOLEAN" && p.name === part.visible)?.prop : void 0;
1571
3027
  const prop = visibleProp ?? iconProps.find((p) => !p.optional && !properties.some((x) => x.prop === p.name))?.name;
1572
3028
  if (!prop) continue;
1573
- const icon = iconComponentName(defaultIcon(name, prop, opts));
3029
+ const icon = iconComponentName(defaultIcon(name, prop, opts, src.folder));
1574
3030
  part.swap = { property: sentenceCase(prop), default: icon, prop };
1575
3031
  setSlot(part.id, () => icon);
1576
3032
  props.add(prop);
1577
3033
  continue;
1578
3034
  }
1579
3035
  const parent = parts.find((p) => p.id === part.parent);
1580
- if (part.kind !== "text" || !part.text || !parent?.parent || parent.kind !== "frame") continue;
3036
+ if (part.kind !== "text" || !part.text || !parent?.parent || parent.kind !== "frame" || !compound.has(parent.id)) continue;
1581
3037
  if (!variants.some((v) => zeroInline(v.values[parent.id]))) continue;
1582
3038
  const textSlot = `Slot/${name} ${part.text.toLowerCase()} text`;
1583
3039
  const media = MEDIA_SLOT.name;
@@ -1645,8 +3101,12 @@ function generateIcons(components, opts, slots = []) {
1645
3101
  const index = lucideIndex(dir, opts);
1646
3102
  const names = [];
1647
3103
  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));
3104
+ for (const p of c.parts) {
3105
+ if (p.swap && !p.swap.options) names.push(p.swap.default.replace(/^Icon\//, ""));
3106
+ if (p.icon) names.push(p.icon.replace(/^Icon\//, ""));
3107
+ }
3108
+ for (const v of c.variants) for (const x of Object.values(v.slots ?? {})) if (x.startsWith("Icon/")) names.push(x.slice(5));
3109
+ names.push(...referencedIcons(c.component ?? c.name, opts));
1650
3110
  }
1651
3111
  for (const s of slots) if (s.icon) names.push(s.icon);
1652
3112
  names.push(...COMMON_ICONS);
@@ -1677,8 +3137,7 @@ function generateSlots(components) {
1677
3137
  if (media) out.push(MEDIA_SLOT);
1678
3138
  return out;
1679
3139
  }
1680
- function generateComponentSpecs(names, opts) {
1681
- const components = names.map((n) => generateComponentSpec(n, opts));
3140
+ function specsFile(components, opts, coverage) {
1682
3141
  const slots = generateSlots(components);
1683
3142
  return {
1684
3143
  version: COMPONENT_SPECS_VERSION,
@@ -1686,10 +3145,144 @@ function generateComponentSpecs(names, opts) {
1686
3145
  components,
1687
3146
  icons: generateIcons(components, opts, slots),
1688
3147
  iconStyle: ICON_STYLE,
1689
- slots
3148
+ slots,
3149
+ ...coverage ? { coverage } : {}
1690
3150
  };
1691
3151
  }
1692
- const SPEC_COMPONENTS = ["Button", "IconButton", "Badge", "Tag", "Input", "Card"];
3152
+ function generateComponentSpecs(names, opts) {
3153
+ return specsFile(
3154
+ names.map((n) => generateComponentSpec(n, opts)),
3155
+ opts
3156
+ );
3157
+ }
3158
+ function splitByLayers(folder, opts) {
3159
+ const src = new Reader(opts).load(folder);
3160
+ for (const p of src?.props.values() ?? []) {
3161
+ if (p.kind !== "union" || !p.values) continue;
3162
+ const out = [];
3163
+ for (const v of p.values) {
3164
+ try {
3165
+ const spec = generateComponentSpec(folder, opts, void 0, `${folder}/${sentenceCase(v)}`, { [p.name]: v });
3166
+ if (specDrawsSomething(spec)) out.push(spec);
3167
+ } catch {
3168
+ out.length = 0;
3169
+ break;
3170
+ }
3171
+ }
3172
+ if (out.length === p.values.length) return out;
3173
+ }
3174
+ return [];
3175
+ }
3176
+ const PAINTS = /(^|[;{\s])(color|background(-color)?|border(-(top|right|bottom|left))?(-color)?|box-shadow|fill|stroke)\s*:(?!\s*(0[;\s}]|none|transparent))/m;
3177
+ function generateAllComponentSpecs(folders, opts) {
3178
+ const withList = { ...opts, components: opts.components ?? folders };
3179
+ const reader = new Reader(withList);
3180
+ const components = [];
3181
+ const coverage = [];
3182
+ const cssOf = (folder) => [...reader.load(folder)?.styles.values() ?? []].map((path) => (opts.read(path) ?? "").replace(/\/\*[\s\S]*?\*\//g, "")).join("\n");
3183
+ const paints = (folder) => PAINTS.test(cssOf(folder));
3184
+ for (const folder of [...folders].sort()) {
3185
+ const reasons = [];
3186
+ const attempt = (name, view, setName) => {
3187
+ try {
3188
+ const spec = generateComponentSpec(name, withList, view, setName);
3189
+ return specDrawsSomething(spec) ? spec : void 0;
3190
+ } catch (err) {
3191
+ reasons.push(err instanceof UnsupportedComponentError ? err.reason : "renders in a way the generator cannot read yet");
3192
+ return void 0;
3193
+ }
3194
+ };
3195
+ const exports = [folder, ...[...reader.load(folder)?.exported.keys() ?? []].filter((n) => n !== folder)];
3196
+ const sets = [];
3197
+ let main = attempt(folder);
3198
+ if (!main && reasons.includes("changes its layers between variants")) {
3199
+ const split = splitByLayers(folder, withList);
3200
+ if (split.length) {
3201
+ sets.push(...split);
3202
+ main = split[0];
3203
+ reasons.length = 0;
3204
+ }
3205
+ }
3206
+ if (main && !sets.length) {
3207
+ sets.push(main);
3208
+ const compound = new Set(main.parts.map((p) => p.name));
3209
+ const drawnIn = new Set(main.parts.map((p) => p.source).filter(Boolean));
3210
+ for (const other of exports.slice(1)) {
3211
+ if (other.startsWith(folder) && compound.has(other.slice(folder.length))) continue;
3212
+ const spec = attempt(other);
3213
+ if (!spec) continue;
3214
+ const readsParent = spec.unmapped.some((u) => /^--_[\w-]+ is not a token$/.test(u.reason));
3215
+ const duplicate = drawnIn.has(spec.parts[0]?.source) && !spec.properties.length && spec.axes.every((a) => a.name === "State");
3216
+ if (readsParent || duplicate) continue;
3217
+ sets.push(spec);
3218
+ }
3219
+ }
3220
+ const hasPopup = /\.(Portal|Popup)\b/.test(opts.read(`${opts.componentsDir}/${folder}/${folder}.tsx`) ?? "");
3221
+ if (!main && hasPopup) {
3222
+ reasons.length = 0;
3223
+ for (const name of exports) {
3224
+ const closedSpec = attempt(name, "without-popup");
3225
+ const closed = closedSpec?.parts.some((p) => p.source?.includes(`${folder}.module.css`)) ? closedSpec : void 0;
3226
+ if (closed && !sets.some((x) => x.name === closed.name)) sets.push(closed);
3227
+ const suffix = name === folder ? "" : name.startsWith(folder) ? name.slice(folder.length) : name;
3228
+ const popupName = `${folder}/${sets.some((x) => x.name === `${folder}/Popup`) ? suffix || "Popup" : "Popup"}`;
3229
+ const fnNode = reader.load(name)?.fn;
3230
+ const ownsPortal = !!fnNode && /\.Portal\b/.test(fnNode.getText());
3231
+ const open = ownsPortal ? attempt(name, "popup", popupName) : void 0;
3232
+ if (open && !sets.some((x) => x.name === open.name)) sets.push(open);
3233
+ }
3234
+ }
3235
+ if (!sets.length && folder === ICON_COMPONENT) {
3236
+ coverage.push({ name: folder, status: "generated", reason: "drawn as icon components on the Icons page" });
3237
+ continue;
3238
+ }
3239
+ if (!sets.length) {
3240
+ const fileText = opts.read(`${opts.componentsDir}/${folder}/${folder}.tsx`) ?? "";
3241
+ const reason = /\.(Portal|Popup|Positioner)\b/.test(fileText) && !reasons.length ? "opens a popup" : reasons.length ? mainReason(reasons) : void 0;
3242
+ const drafted = draftedSpec(folder, withList);
3243
+ if (drafted) {
3244
+ components.push(drafted);
3245
+ coverage.push({ name: folder, status: "drafted", reason: drafted.drafted.reason, ...drafted.name !== folder ? { sets: [drafted.name] } : {} });
3246
+ continue;
3247
+ }
3248
+ 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" });
3249
+ else coverage.push({ name: folder, status: "unsupported", reason: reason ?? "draws nothing the generator can read yet" });
3250
+ continue;
3251
+ }
3252
+ components.push(...sets);
3253
+ coverage.push({ name: folder, status: "generated", ...sets.length > 1 || sets[0].name !== folder ? { sets: sets.map((x) => x.name) } : {} });
3254
+ }
3255
+ return specsFile(components, withList, coverage);
3256
+ }
3257
+ const overridesDirOf = (componentsDir) => posix.join(posix.dirname(posix.dirname(componentsDir)), "component-spec-overrides");
3258
+ function draftedCodeHash(files, read) {
3259
+ return specHash(files.map((f) => `${f}
3260
+ ${read(f) ?? ""}`).join("\n"));
3261
+ }
3262
+ function draftedSpec(folder, opts) {
3263
+ const path = `${opts.overridesDir ?? overridesDirOf(opts.componentsDir)}/${folder}.json`;
3264
+ const text = opts.read(path);
3265
+ if (text === void 0) return void 0;
3266
+ let raw;
3267
+ try {
3268
+ raw = JSON.parse(text);
3269
+ } catch {
3270
+ throw new Error(`${path} is not valid JSON.`);
3271
+ }
3272
+ if (raw.source !== "drafted") throw new Error(`${path}: a drafted spec says "source": "drafted".`);
3273
+ const files = Array.isArray(raw.files) ? raw.files.filter((f) => typeof f === "string") : [];
3274
+ if (!files.length) throw new Error(`${path}: list the code it was drafted from in "files".`);
3275
+ const reason = typeof raw.reason === "string" && raw.reason ? raw.reason : "the generator cannot read it yet";
3276
+ const { $comment: _c, source: _s, files: _f, reason: _r, codeHash, hash: _h, drafted: _d, ...fields } = raw;
3277
+ const stale = typeof codeHash === "string" && codeHash !== draftedCodeHash(files, opts.read);
3278
+ const body = { ...fields, drafted: { reason, ...stale ? { stale: true } : {} } };
3279
+ const problems = validateComponentSpec({ ...body }, new Map([...typesOf(opts.tokens)].map(([k, v]) => [k, v.type])));
3280
+ if (problems.length) throw new Error(`${path} does not match the component spec format:
3281
+ - ${problems.join("\n- ")}`);
3282
+ const { name, axes, properties, parts, variants, unmapped, ...rest } = body;
3283
+ const spec = { name, ...rest, source: files, axes, properties, parts, variants, unmapped };
3284
+ return { name, ...rest, hash: hashSpec(spec), source: files, axes, properties, parts, variants, unmapped };
3285
+ }
1693
3286
  function tokenCssIndex(files, opts = {}) {
1694
3287
  const set = buildTokenSet(files, opts.layout ?? {});
1695
3288
  const out = /* @__PURE__ */ new Map();
@@ -1699,14 +3292,14 @@ function tokenCssIndex(files, opts = {}) {
1699
3292
  }
1700
3293
  return out;
1701
3294
  }
1702
- function generateRepoComponentSpecs(root, names = SPEC_COMPONENTS) {
3295
+ function repoSpecOptions(root) {
1703
3296
  const configText = readFileSync(join(root, CONFIG_FILE), "utf8");
1704
3297
  const config = loadConfig(configText);
1705
3298
  const tokensRoot = join(root, config.tokens.root);
1706
3299
  const files = {};
1707
- const walk2 = (dir) => {
1708
- for (const n of readdirSync(dir)) {
1709
- const full = join(dir, n);
3300
+ const walk2 = (dir2) => {
3301
+ for (const n of readdirSync(dir2)) {
3302
+ const full = join(dir2, n);
1710
3303
  if (statSync(full).isDirectory()) walk2(full);
1711
3304
  else if (n.endsWith(".tokens.json")) files[relative(tokensRoot, full).split("\\").join("/")] = readFileSync(full, "utf8");
1712
3305
  }
@@ -1721,7 +3314,14 @@ function generateRepoComponentSpecs(root, names = SPEC_COMPONENTS) {
1721
3314
  }
1722
3315
  };
1723
3316
  const componentsDir = config.components?.dir ?? "packages/react/src/components";
1724
- return generateComponentSpecs(names, { componentsDir, read, tokens, styleSuffix: config.components?.styleSuffix });
3317
+ const dir = join(root, componentsDir);
3318
+ const folders = readdirSync(dir).filter((n) => /^[A-Z]/.test(n) && statSync(join(dir, n)).isDirectory() && read(`${componentsDir}/${n}/${n}.tsx`) !== void 0).sort();
3319
+ const baseCss = posix.join(posix.dirname(componentsDir), "styles/base.css");
3320
+ return { opts: { componentsDir, read, tokens, styleSuffix: config.components?.styleSuffix, components: folders, ...read(baseCss) !== void 0 ? { baseCss } : {} }, folders };
3321
+ }
3322
+ function generateRepoComponentSpecs(root, names) {
3323
+ const { opts, folders } = repoSpecOptions(root);
3324
+ return names ? generateComponentSpecs(names, opts) : generateAllComponentSpecs(folders, opts);
1725
3325
  }
1726
3326
  const serializeComponentSpecs = (f) => `${JSON.stringify(f, null, 2)}
1727
3327
  `;
@@ -1731,16 +3331,19 @@ export {
1731
3331
  ICON_STYLE,
1732
3332
  INHERITED,
1733
3333
  MEDIA_SLOT,
1734
- SPEC_COMPONENTS,
3334
+ UnsupportedComponentError,
1735
3335
  branchExists,
1736
3336
  collectAuditFiles,
1737
3337
  commitToNewLocalBranch,
1738
3338
  computeValues,
1739
3339
  currentBranch,
1740
3340
  declaredFor,
3341
+ draftedCodeHash,
3342
+ draftedSpec,
1741
3343
  expandDecl,
1742
3344
  findDesignSystemRoot,
1743
3345
  fixCss,
3346
+ generateAllComponentSpecs,
1744
3347
  generateComponentSpec,
1745
3348
  generateComponentSpecs,
1746
3349
  generateIcons,
@@ -1748,6 +3351,7 @@ export {
1748
3351
  generateSlots,
1749
3352
  git,
1750
3353
  isGitRepo,
3354
+ jsxText,
1751
3355
  listFilesAt,
1752
3356
  loadAllowlist,
1753
3357
  loadDesignSystem,
@@ -1757,12 +3361,15 @@ export {
1757
3361
  matchComplex,
1758
3362
  mediaApplies,
1759
3363
  originRepo,
3364
+ overridesDirOf,
1760
3365
  parseCssModule,
1761
3366
  parseSelector,
3367
+ repoSpecOptions,
1762
3368
  runAudit,
1763
3369
  sentenceCase,
1764
3370
  serializeComponentSpecs,
1765
3371
  showFile,
3372
+ specDrawsSomething,
1766
3373
  splitTop,
1767
3374
  tokenCssIndex
1768
3375
  };