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