@bridge-ds/core 0.5.1 → 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.
- package/dist/{component-spec-cvrSvIZj.js → component-spec-GFVEE6Fc.js} +288 -190
- package/dist/component-spec-GFVEE6Fc.js.map +1 -0
- package/dist/component-spec.d.ts +124 -7
- package/dist/explorations.d.ts +2 -0
- package/dist/fidelity-references.d.ts +78 -0
- package/dist/fidelity.d.ts +138 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +557 -283
- package/dist/index.js.map +1 -1
- package/dist/node/component-specs.d.ts +57 -4
- package/dist/node/index.js +1724 -117
- package/dist/node/index.js.map +1 -1
- package/dist/node/spec-css.d.ts +11 -0
- package/dist/rights.d.ts +2 -2
- package/package.json +1 -1
- package/dist/component-spec-cvrSvIZj.js.map +0 -1
package/dist/node/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join, resolve, relative, dirname, sep, posix } from "node:path";
|
|
5
|
-
import { a as assertSafeBranch, c as collectHistory, p as packageDirCandidates, b as parseChangelog, m as mergeReleases, r as releasesFromTags, d as buildReport, e as prNumberFromSubject, l as loadConfig, h as hashSpec,
|
|
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
8
|
import { v as isAuditable, Q as planCssFixes, O as parseAllowlist, c as DesignSystemIndex, k as buildAuditReport, n as compareWithBaseline, a as AUDIT_IGNORE_DIRS } from "../report-iPSnsjYi.js";
|
|
@@ -495,13 +495,26 @@ function siblingsBefore(el) {
|
|
|
495
495
|
const idx = sibs.indexOf(el);
|
|
496
496
|
return idx < 0 ? [] : sibs.slice(0, idx);
|
|
497
497
|
}
|
|
498
|
+
function checkedAttr(name, checked) {
|
|
499
|
+
if (checked === void 0) return void 0;
|
|
500
|
+
if (name === "aria-checked") return checked;
|
|
501
|
+
if (name === "data-checked" || name === "data-selected" || name === "data-active") return checked === "true" ? "" : void 0;
|
|
502
|
+
if (name === "data-unchecked") return checked === "false" ? "" : void 0;
|
|
503
|
+
if (name === "data-indeterminate") return checked === "mixed" ? "" : void 0;
|
|
504
|
+
return void 0;
|
|
505
|
+
}
|
|
498
506
|
function attrMatches(el, s, state) {
|
|
499
507
|
switch (s.name) {
|
|
500
508
|
case "data-disabled":
|
|
501
509
|
case "disabled":
|
|
502
|
-
return state.disabled && (el.isRoot || el.inputLike);
|
|
503
|
-
default:
|
|
504
|
-
|
|
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.
|
|
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
|
|
773
|
-
const
|
|
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 (
|
|
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 (
|
|
784
|
-
|
|
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
|
-
|
|
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
|
|
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)
|
|
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 ===
|
|
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:
|
|
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 (
|
|
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
|
-
|
|
1015
|
-
|
|
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 (
|
|
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))
|
|
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
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
const
|
|
1038
|
-
|
|
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 =
|
|
1044
|
-
if (t) return renderExpression(e.right, scope, ctx, visible
|
|
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 =
|
|
1049
|
-
if (t) return renderExpression(e.whenTrue, scope, ctx, visible
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
1069
|
-
|
|
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
|
-
|
|
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
|
|
1081
|
-
|
|
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
|
|
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 = {
|
|
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 ===
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
1280
|
-
|
|
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, "&").replace(/"/g, """).replace(/</g, "<");
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1378
|
-
|
|
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) =>
|
|
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 =
|
|
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 &&
|
|
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
|
|
1418
|
-
const
|
|
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
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
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
|
|
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
|
-
|
|
1432
|
-
|
|
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 ?
|
|
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:
|
|
1440
|
-
...n.kind === "frame" && clips(
|
|
1441
|
-
...!parent && fillsContainer(
|
|
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.
|
|
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
|
|
1449
|
-
|
|
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
|
|
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
|
|
1468
|
-
|
|
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(
|
|
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(
|
|
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)
|
|
1649
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 = (
|
|
1708
|
-
for (const n of readdirSync(
|
|
1709
|
-
const full = join(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|