@dannote/figma-use 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -18920,6 +18920,92 @@ var require_react_reconciler = __commonJS((exports, module) => {
18920
18920
  }
18921
18921
  });
18922
18922
 
18923
+ // packages/cli/src/render/vars.ts
18924
+ var exports_vars = {};
18925
+ __export(exports_vars, {
18926
+ resolveVariable: () => resolveVariable,
18927
+ loadVariablesIntoRegistry: () => loadVariablesIntoRegistry,
18928
+ isVariable: () => isVariable,
18929
+ isRegistryLoaded: () => isRegistryLoaded,
18930
+ getRegistrySize: () => getRegistrySize,
18931
+ figmaVar: () => figmaVar,
18932
+ defineVars: () => defineVars
18933
+ });
18934
+ function isVariable(value) {
18935
+ return typeof value === "object" && value !== null && VAR_SYMBOL in value;
18936
+ }
18937
+ function loadVariablesIntoRegistry(variables) {
18938
+ variableRegistry.clear();
18939
+ for (const v3 of variables) {
18940
+ const match = v3.id.match(/VariableID:(\d+):(\d+)/);
18941
+ if (match) {
18942
+ variableRegistry.set(v3.name, {
18943
+ id: v3.id,
18944
+ sessionID: parseInt(match[1], 10),
18945
+ localID: parseInt(match[2], 10)
18946
+ });
18947
+ }
18948
+ }
18949
+ }
18950
+ function resolveVariable(variable) {
18951
+ if (variable._resolved) {
18952
+ return variable._resolved;
18953
+ }
18954
+ const idMatch = variable.name.match(/^(?:VariableID:)?(\d+):(\d+)$/);
18955
+ if (idMatch) {
18956
+ const resolved2 = {
18957
+ id: `VariableID:${idMatch[1]}:${idMatch[2]}`,
18958
+ sessionID: parseInt(idMatch[1], 10),
18959
+ localID: parseInt(idMatch[2], 10)
18960
+ };
18961
+ variable._resolved = resolved2;
18962
+ return resolved2;
18963
+ }
18964
+ const resolved = variableRegistry.get(variable.name);
18965
+ if (!resolved) {
18966
+ const available = Array.from(variableRegistry.keys()).slice(0, 5).join(", ");
18967
+ throw new Error(`Variable "${variable.name}" not found. ` + `Available: ${available}${variableRegistry.size > 5 ? "..." : ""}. ` + `Make sure variables are loaded before render.`);
18968
+ }
18969
+ variable._resolved = resolved;
18970
+ return resolved;
18971
+ }
18972
+ function isRegistryLoaded() {
18973
+ return variableRegistry.size > 0;
18974
+ }
18975
+ function getRegistrySize() {
18976
+ return variableRegistry.size;
18977
+ }
18978
+ function defineVars(vars) {
18979
+ const result = {};
18980
+ for (const [key, def] of Object.entries(vars)) {
18981
+ if (typeof def === "string") {
18982
+ result[key] = {
18983
+ [VAR_SYMBOL]: true,
18984
+ name: def
18985
+ };
18986
+ } else {
18987
+ result[key] = {
18988
+ [VAR_SYMBOL]: true,
18989
+ name: def.name,
18990
+ value: def.value
18991
+ };
18992
+ }
18993
+ }
18994
+ return result;
18995
+ }
18996
+ function figmaVar(name, value) {
18997
+ return {
18998
+ [VAR_SYMBOL]: true,
18999
+ name,
19000
+ value
19001
+ };
19002
+ }
19003
+ var VAR_SYMBOL, variableRegistry;
19004
+ var init_vars = __esm(() => {
19005
+ VAR_SYMBOL = Symbol.for("figma.variable");
19006
+ variableRegistry = new Map;
19007
+ });
19008
+
18923
19009
  // node_modules/esbuild/lib/main.js
18924
19010
  var require_main = __commonJS((exports, module) => {
18925
19011
  var __dirname = "/Users/dannote/Development/figma-use/node_modules/esbuild/lib", __filename = "/Users/dannote/Development/figma-use/node_modules/esbuild/lib/main.js";
@@ -24126,7 +24212,7 @@ var profile_default = defineCommand({
24126
24212
  });
24127
24213
  // packages/cli/src/commands/render.ts
24128
24214
  init_format();
24129
- var React2 = __toESM(require_react(), 1);
24215
+ var React3 = __toESM(require_react(), 1);
24130
24216
  import { resolve as resolve3 } from "path";
24131
24217
  import { existsSync as existsSync2, writeFileSync as writeFileSync2, unlinkSync } from "fs";
24132
24218
  import { tmpdir } from "os";
@@ -27547,6 +27633,89 @@ function parseColor(color) {
27547
27633
  }
27548
27634
 
27549
27635
  // packages/cli/src/render/reconciler.ts
27636
+ init_vars();
27637
+
27638
+ // packages/cli/src/render/components.tsx
27639
+ init_vars();
27640
+ var React = __toESM(require_react(), 1);
27641
+ var componentRegistry = new Map;
27642
+ function getComponentRegistry() {
27643
+ return componentRegistry;
27644
+ }
27645
+ var c6 = (type) => (props) => React.createElement(type, props);
27646
+ var Frame = c6("frame");
27647
+ var Rectangle = c6("rectangle");
27648
+ var Ellipse = c6("ellipse");
27649
+ var Text = c6("text");
27650
+ var Line = c6("line");
27651
+ var Star = c6("star");
27652
+ var Polygon = c6("polygon");
27653
+ var Vector = c6("vector");
27654
+ var Component = c6("component");
27655
+ var Instance = c6("instance");
27656
+ var Group = c6("group");
27657
+ var Page = c6("page");
27658
+ var INTRINSIC_ELEMENTS = [
27659
+ "Frame",
27660
+ "Rectangle",
27661
+ "Ellipse",
27662
+ "Text",
27663
+ "Line",
27664
+ "Star",
27665
+ "Polygon",
27666
+ "Vector",
27667
+ "Component",
27668
+ "Instance",
27669
+ "Group",
27670
+ "Page",
27671
+ "View"
27672
+ ];
27673
+
27674
+ // packages/cli/src/render/component-set.tsx
27675
+ var React2 = __toESM(require_react(), 1);
27676
+ var componentSetRegistry = new Map;
27677
+ function getComponentSetRegistry() {
27678
+ return componentSetRegistry;
27679
+ }
27680
+ function generateVariantCombinations(variants) {
27681
+ const keys = Object.keys(variants);
27682
+ if (keys.length === 0)
27683
+ return [{}];
27684
+ const result = [];
27685
+ function combine(index, current) {
27686
+ if (index === keys.length) {
27687
+ result.push(current);
27688
+ return;
27689
+ }
27690
+ const key = keys[index];
27691
+ for (const value of variants[key]) {
27692
+ combine(index + 1, { ...current, [key]: value });
27693
+ }
27694
+ }
27695
+ combine(0, {});
27696
+ return result;
27697
+ }
27698
+ function buildVariantName(props) {
27699
+ return Object.entries(props).map(([k6, v3]) => `${k6}=${v3}`).join(", ");
27700
+ }
27701
+ function buildStateGroupPropertyValueOrders(variants) {
27702
+ return Object.entries(variants).map(([property, values]) => ({
27703
+ property,
27704
+ values: [...values]
27705
+ }));
27706
+ }
27707
+
27708
+ // packages/cli/src/render/reconciler.ts
27709
+ var renderedComponents = new Map;
27710
+ var renderedComponentSets = new Map;
27711
+ var renderedComponentSetVariants = new Map;
27712
+ var pendingComponentSetInstances = [];
27713
+ function getPendingComponentSetInstances() {
27714
+ return [...pendingComponentSetInstances];
27715
+ }
27716
+ function clearPendingComponentSetInstances() {
27717
+ pendingComponentSetInstances.length = 0;
27718
+ }
27550
27719
  function styleToNodeChange(type, props, localID, sessionID, parentGUID, position, textContent) {
27551
27720
  const style = props.style || {};
27552
27721
  const name = props.name || type;
@@ -27559,10 +27728,16 @@ function styleToNodeChange(type, props, localID, sessionID, parentGUID, position
27559
27728
  visible: true,
27560
27729
  opacity: typeof style.opacity === "number" ? style.opacity : 1
27561
27730
  };
27562
- const width = style.width ?? props.width ?? (type === "TEXT" ? undefined : 100);
27563
- const height = style.height ?? props.height ?? (type === "TEXT" ? undefined : 100);
27731
+ const width = style.width ?? props.width;
27732
+ const height = style.height ?? props.height;
27564
27733
  if (width !== undefined && height !== undefined) {
27565
27734
  nodeChange.size = { x: Number(width), y: Number(height) };
27735
+ } else if (width !== undefined) {
27736
+ nodeChange.size = { x: Number(width), y: 1 };
27737
+ } else if (height !== undefined) {
27738
+ nodeChange.size = { x: 1, y: Number(height) };
27739
+ } else if (type !== "TEXT") {
27740
+ nodeChange.size = { x: 1, y: 1 };
27566
27741
  }
27567
27742
  const x3 = Number(style.x ?? props.x ?? 0);
27568
27743
  const y5 = Number(style.y ?? props.y ?? 0);
@@ -27575,22 +27750,52 @@ function styleToNodeChange(type, props, localID, sessionID, parentGUID, position
27575
27750
  m12: y5
27576
27751
  };
27577
27752
  if (style.backgroundColor) {
27578
- const color = parseColor(style.backgroundColor);
27579
- nodeChange.fillPaints = [{
27580
- type: "SOLID",
27581
- color: { r: color.r, g: color.g, b: color.b, a: color.a },
27582
- opacity: color.a,
27583
- visible: true
27584
- }];
27753
+ const bgColor = style.backgroundColor;
27754
+ if (isVariable(bgColor)) {
27755
+ const resolved = resolveVariable(bgColor);
27756
+ const fallback = bgColor.value ? parseColor(bgColor.value) : { r: 0, g: 0, b: 0, a: 1 };
27757
+ nodeChange.fillPaints = [{
27758
+ type: "SOLID",
27759
+ color: { r: fallback.r, g: fallback.g, b: fallback.b, a: fallback.a },
27760
+ opacity: 1,
27761
+ visible: true,
27762
+ colorVariableBinding: {
27763
+ variableID: { sessionID: resolved.sessionID, localID: resolved.localID }
27764
+ }
27765
+ }];
27766
+ } else {
27767
+ const color = parseColor(bgColor);
27768
+ nodeChange.fillPaints = [{
27769
+ type: "SOLID",
27770
+ color: { r: color.r, g: color.g, b: color.b, a: color.a },
27771
+ opacity: color.a,
27772
+ visible: true
27773
+ }];
27774
+ }
27585
27775
  }
27586
27776
  if (style.borderColor) {
27587
- const color = parseColor(style.borderColor);
27588
- nodeChange.strokePaints = [{
27589
- type: "SOLID",
27590
- color: { r: color.r, g: color.g, b: color.b, a: color.a },
27591
- opacity: color.a,
27592
- visible: true
27593
- }];
27777
+ const borderColor = style.borderColor;
27778
+ if (isVariable(borderColor)) {
27779
+ const resolved = resolveVariable(borderColor);
27780
+ const fallback = borderColor.value ? parseColor(borderColor.value) : { r: 0, g: 0, b: 0, a: 1 };
27781
+ nodeChange.strokePaints = [{
27782
+ type: "SOLID",
27783
+ color: { r: fallback.r, g: fallback.g, b: fallback.b, a: fallback.a },
27784
+ opacity: 1,
27785
+ visible: true,
27786
+ colorVariableBinding: {
27787
+ variableID: { sessionID: resolved.sessionID, localID: resolved.localID }
27788
+ }
27789
+ }];
27790
+ } else {
27791
+ const color = parseColor(borderColor);
27792
+ nodeChange.strokePaints = [{
27793
+ type: "SOLID",
27794
+ color: { r: color.r, g: color.g, b: color.b, a: color.a },
27795
+ opacity: color.a,
27796
+ visible: true
27797
+ }];
27798
+ }
27594
27799
  nodeChange.strokeWeight = Number(style.borderWidth ?? 1);
27595
27800
  }
27596
27801
  if (style.borderRadius !== undefined) {
@@ -27614,6 +27819,11 @@ function styleToNodeChange(type, props, localID, sessionID, parentGUID, position
27614
27819
  }
27615
27820
  if (style.flexDirection) {
27616
27821
  nodeChange.stackMode = style.flexDirection === "row" ? "HORIZONTAL" : "VERTICAL";
27822
+ const isRow = style.flexDirection === "row";
27823
+ const primarySize = isRow ? width : height;
27824
+ const counterSize = isRow ? height : width;
27825
+ nodeChange.stackPrimarySizing = primarySize !== undefined ? "FIXED" : "RESIZE_TO_FIT";
27826
+ nodeChange.stackCounterSizing = counterSize !== undefined ? "FIXED" : "RESIZE_TO_FIT";
27617
27827
  }
27618
27828
  if (style.gap !== undefined) {
27619
27829
  nodeChange.stackSpacing = Number(style.gap);
@@ -27631,49 +27841,89 @@ function styleToNodeChange(type, props, localID, sessionID, parentGUID, position
27631
27841
  if (pb !== undefined)
27632
27842
  nodeChange.stackPaddingBottom = Number(pb);
27633
27843
  if (style.justifyContent) {
27634
- const map = {
27844
+ const validValues = {
27635
27845
  "flex-start": "MIN",
27636
27846
  center: "CENTER",
27637
27847
  "flex-end": "MAX",
27638
- "space-between": "SPACE_BETWEEN"
27848
+ "space-evenly": "SPACE_EVENLY"
27639
27849
  };
27640
- nodeChange.stackJustify = map[style.justifyContent] || "MIN";
27850
+ const mapped = validValues[style.justifyContent];
27851
+ if (mapped) {
27852
+ nodeChange.stackPrimaryAlignItems = mapped;
27853
+ } else {
27854
+ consola2.warn(`justifyContent: "${style.justifyContent}" not supported, using "flex-start"`);
27855
+ nodeChange.stackPrimaryAlignItems = "MIN";
27856
+ }
27641
27857
  }
27642
27858
  if (style.alignItems) {
27643
- const map = {
27859
+ const validValues = {
27644
27860
  "flex-start": "MIN",
27645
27861
  center: "CENTER",
27646
27862
  "flex-end": "MAX",
27647
27863
  stretch: "STRETCH"
27648
27864
  };
27649
- nodeChange.stackCounterAlign = map[style.alignItems] || "MIN";
27865
+ const mapped = validValues[style.alignItems];
27866
+ if (mapped) {
27867
+ nodeChange.stackCounterAlignItems = mapped;
27868
+ } else {
27869
+ consola2.warn(`alignItems: "${style.alignItems}" not supported, using "flex-start"`);
27870
+ nodeChange.stackCounterAlignItems = "MIN";
27871
+ }
27650
27872
  }
27651
27873
  if (type.toLowerCase() === "text" && textContent) {
27652
27874
  const nc = nodeChange;
27653
27875
  nc.textData = { characters: textContent };
27876
+ nc.textAutoResize = "WIDTH_AND_HEIGHT";
27877
+ nc.textAlignVertical = "TOP";
27654
27878
  if (style.fontSize)
27655
27879
  nc.fontSize = Number(style.fontSize);
27656
- if (style.fontFamily || style.fontWeight) {
27657
- const family = style.fontFamily || "Inter";
27658
- const fontStyle = mapFontWeight(style.fontWeight);
27659
- nc.fontName = {
27660
- family,
27661
- style: fontStyle,
27662
- postscript: `${family}-${fontStyle}`.replace(/\s+/g, "")
27663
- };
27664
- }
27880
+ nc.lineHeight = { value: 100, units: "PERCENT" };
27881
+ const family = style.fontFamily || "Inter";
27882
+ const fontStyle = mapFontWeight(style.fontWeight);
27883
+ nc.fontName = {
27884
+ family,
27885
+ style: fontStyle,
27886
+ postscript: `${family}-${fontStyle}`.replace(/\s+/g, "")
27887
+ };
27665
27888
  if (style.textAlign) {
27666
27889
  const map = { left: "LEFT", center: "CENTER", right: "RIGHT" };
27667
27890
  nc.textAlignHorizontal = map[style.textAlign] || "LEFT";
27668
27891
  }
27669
27892
  if (style.color) {
27670
- const color = parseColor(style.color);
27671
- nodeChange.fillPaints = [{
27672
- type: "SOLID",
27673
- color: { r: color.r, g: color.g, b: color.b, a: color.a },
27674
- opacity: color.a,
27675
- visible: true
27676
- }];
27893
+ const textColor = style.color;
27894
+ if (isVariable(textColor)) {
27895
+ const resolved = resolveVariable(textColor);
27896
+ const fallback = textColor.value ? parseColor(textColor.value) : { r: 0, g: 0, b: 0, a: 1 };
27897
+ nodeChange.fillPaints = [{
27898
+ type: "SOLID",
27899
+ color: { r: fallback.r, g: fallback.g, b: fallback.b, a: fallback.a },
27900
+ opacity: 1,
27901
+ visible: true,
27902
+ colorVariableBinding: {
27903
+ variableID: { sessionID: resolved.sessionID, localID: resolved.localID }
27904
+ }
27905
+ }];
27906
+ } else {
27907
+ const color = parseColor(textColor);
27908
+ nodeChange.fillPaints = [{
27909
+ type: "SOLID",
27910
+ color: { r: color.r, g: color.g, b: color.b, a: color.a },
27911
+ opacity: color.a,
27912
+ visible: true
27913
+ }];
27914
+ }
27915
+ }
27916
+ }
27917
+ if (type.toLowerCase() === "instance" && props.componentId) {
27918
+ const match = String(props.componentId).match(/(\d+):(\d+)/);
27919
+ if (match) {
27920
+ const nc = nodeChange;
27921
+ nc.symbolData = {
27922
+ symbolID: {
27923
+ sessionID: parseInt(match[1], 10),
27924
+ localID: parseInt(match[2], 10)
27925
+ }
27926
+ };
27677
27927
  }
27678
27928
  }
27679
27929
  return nodeChange;
@@ -27688,7 +27938,7 @@ function mapType(type) {
27688
27938
  star: "STAR",
27689
27939
  polygon: "REGULAR_POLYGON",
27690
27940
  vector: "VECTOR",
27691
- component: "COMPONENT",
27941
+ component: "SYMBOL",
27692
27942
  instance: "INSTANCE",
27693
27943
  group: "GROUP",
27694
27944
  page: "CANVAS"
@@ -27713,13 +27963,173 @@ function mapFontWeight(weight) {
27713
27963
  };
27714
27964
  return map[weight] || "Regular";
27715
27965
  }
27716
- function collectNodeChanges(instance, sessionID, parentGUID, position, result) {
27966
+ function collectNodeChanges(instance, sessionID, parentGUID, position, result, container) {
27967
+ if (instance.type === "__component_instance__") {
27968
+ const sym = instance.props.__componentSymbol;
27969
+ const name = instance.props.__componentName;
27970
+ const registry = getComponentRegistry();
27971
+ const def = registry.get(sym);
27972
+ if (!def) {
27973
+ consola2.error(`Component "${name}" not found in registry`);
27974
+ return;
27975
+ }
27976
+ let componentGUID = renderedComponents.get(sym);
27977
+ if (!componentGUID) {
27978
+ const componentLocalID = container.localIDCounter++;
27979
+ componentGUID = { sessionID, localID: componentLocalID };
27980
+ renderedComponents.set(sym, componentGUID);
27981
+ const componentResult = renderToNodeChanges(def.element, {
27982
+ sessionID,
27983
+ parentGUID,
27984
+ startLocalID: container.localIDCounter
27985
+ });
27986
+ container.localIDCounter = componentResult.nextLocalID;
27987
+ if (componentResult.nodeChanges.length > 0) {
27988
+ const rootChange = componentResult.nodeChanges[0];
27989
+ const originalRootGUID = { ...rootChange.guid };
27990
+ rootChange.guid = componentGUID;
27991
+ rootChange.type = "SYMBOL";
27992
+ rootChange.name = name;
27993
+ rootChange.parentIndex = { guid: parentGUID, position };
27994
+ for (let i3 = 1;i3 < componentResult.nodeChanges.length; i3++) {
27995
+ const child = componentResult.nodeChanges[i3];
27996
+ if (child.parentIndex?.guid.localID === originalRootGUID.localID && child.parentIndex?.guid.sessionID === originalRootGUID.sessionID) {
27997
+ child.parentIndex.guid = componentGUID;
27998
+ }
27999
+ }
28000
+ const style = instance.props.style || {};
28001
+ if (style.x !== undefined || style.y !== undefined) {
28002
+ const x3 = Number(style.x ?? 0);
28003
+ const y5 = Number(style.y ?? 0);
28004
+ rootChange.transform = { m00: 1, m01: 0, m02: x3, m10: 0, m11: 1, m12: y5 };
28005
+ }
28006
+ result.push(...componentResult.nodeChanges);
28007
+ }
28008
+ } else {
28009
+ const instanceLocalID = container.localIDCounter++;
28010
+ const style = instance.props.style || {};
28011
+ const x3 = Number(style.x ?? 0);
28012
+ const y5 = Number(style.y ?? 0);
28013
+ const instanceChange = {
28014
+ guid: { sessionID, localID: instanceLocalID },
28015
+ phase: "CREATED",
28016
+ parentIndex: { guid: parentGUID, position },
28017
+ type: "INSTANCE",
28018
+ name,
28019
+ visible: true,
28020
+ opacity: 1,
28021
+ transform: { m00: 1, m01: 0, m02: x3, m10: 0, m11: 1, m12: y5 }
28022
+ };
28023
+ const nc = instanceChange;
28024
+ nc.symbolData = { symbolID: componentGUID };
28025
+ result.push(instanceChange);
28026
+ }
28027
+ return;
28028
+ }
28029
+ if (instance.type === "__component_set_instance__") {
28030
+ const sym = instance.props.__componentSetSymbol;
28031
+ const name = instance.props.__componentSetName;
28032
+ const variantProps = instance.props.__variantProps || {};
28033
+ const csRegistry = getComponentSetRegistry();
28034
+ const csDef = csRegistry.get(sym);
28035
+ if (!csDef) {
28036
+ consola2.error(`ComponentSet "${name}" not found in registry`);
28037
+ return;
28038
+ }
28039
+ let componentSetGUID = renderedComponentSets.get(sym);
28040
+ if (!componentSetGUID) {
28041
+ const componentSetLocalID = container.localIDCounter++;
28042
+ componentSetGUID = { sessionID, localID: componentSetLocalID };
28043
+ renderedComponentSets.set(sym, componentSetGUID);
28044
+ const variants = csDef.variants;
28045
+ const combinations = generateVariantCombinations(variants);
28046
+ const variantComponentIds = new Map;
28047
+ const setChange = {
28048
+ guid: componentSetGUID,
28049
+ phase: "CREATED",
28050
+ parentIndex: { guid: parentGUID, position },
28051
+ type: "FRAME",
28052
+ name,
28053
+ visible: true,
28054
+ opacity: 1,
28055
+ size: { x: 1, y: 1 }
28056
+ };
28057
+ const setNc = setChange;
28058
+ setNc.isStateGroup = true;
28059
+ setNc.stateGroupPropertyValueOrders = buildStateGroupPropertyValueOrders(variants);
28060
+ setNc.stackMode = "HORIZONTAL";
28061
+ setNc.stackSpacing = 20;
28062
+ setNc.stackPrimarySizing = "RESIZE_TO_FIT";
28063
+ setNc.stackCounterSizing = "RESIZE_TO_FIT";
28064
+ result.push(setChange);
28065
+ combinations.forEach((combo, i3) => {
28066
+ const variantName = buildVariantName(combo);
28067
+ const variantLocalID = container.localIDCounter++;
28068
+ const variantGUID = { sessionID, localID: variantLocalID };
28069
+ variantComponentIds.set(variantName, variantGUID);
28070
+ const variantElement = csDef.render(combo);
28071
+ const variantResult = renderToNodeChanges(variantElement, {
28072
+ sessionID,
28073
+ parentGUID: componentSetGUID,
28074
+ startLocalID: container.localIDCounter
28075
+ });
28076
+ container.localIDCounter = variantResult.nextLocalID;
28077
+ if (variantResult.nodeChanges.length > 0) {
28078
+ const rootChange = variantResult.nodeChanges[0];
28079
+ const originalRootGUID = { ...rootChange.guid };
28080
+ rootChange.guid = variantGUID;
28081
+ rootChange.type = "SYMBOL";
28082
+ rootChange.name = variantName;
28083
+ rootChange.parentIndex = {
28084
+ guid: componentSetGUID,
28085
+ position: String.fromCharCode(33 + i3)
28086
+ };
28087
+ for (let j = 1;j < variantResult.nodeChanges.length; j++) {
28088
+ const child = variantResult.nodeChanges[j];
28089
+ if (child.parentIndex?.guid.localID === originalRootGUID.localID && child.parentIndex?.guid.sessionID === originalRootGUID.sessionID) {
28090
+ child.parentIndex.guid = variantGUID;
28091
+ }
28092
+ }
28093
+ result.push(...variantResult.nodeChanges);
28094
+ }
28095
+ });
28096
+ renderedComponentSetVariants.set(sym, variantComponentIds);
28097
+ const requestedVariantName = buildVariantName({
28098
+ ...getDefaultVariants(variants),
28099
+ ...variantProps
28100
+ });
28101
+ const style = instance.props.style || {};
28102
+ pendingComponentSetInstances.push({
28103
+ componentSetName: name,
28104
+ variantName: requestedVariantName,
28105
+ parentGUID,
28106
+ position: String.fromCharCode(34 + combinations.length),
28107
+ x: Number(style.x ?? 0),
28108
+ y: Number(style.y ?? 0)
28109
+ });
28110
+ } else {
28111
+ const requestedVariantName = buildVariantName({
28112
+ ...getDefaultVariants(csDef.variants),
28113
+ ...variantProps
28114
+ });
28115
+ const style = instance.props.style || {};
28116
+ pendingComponentSetInstances.push({
28117
+ componentSetName: name,
28118
+ variantName: requestedVariantName,
28119
+ parentGUID,
28120
+ position,
28121
+ x: Number(style.x ?? 0),
28122
+ y: Number(style.y ?? 0)
28123
+ });
28124
+ }
28125
+ return;
28126
+ }
27717
28127
  const nodeChange = styleToNodeChange(instance.type, instance.props, instance.localID, sessionID, parentGUID, position, instance.textContent);
27718
28128
  result.push(nodeChange);
27719
28129
  const thisGUID = { sessionID, localID: instance.localID };
27720
28130
  instance.children.forEach((child, i3) => {
27721
28131
  const childPosition = String.fromCharCode(33 + i3 % 90);
27722
- collectNodeChanges(child, sessionID, thisGUID, childPosition, result);
28132
+ collectNodeChanges(child, sessionID, thisGUID, childPosition, result, container);
27723
28133
  });
27724
28134
  }
27725
28135
  var hostConfig = {
@@ -27858,43 +28268,28 @@ function renderToNodeChanges(element, options) {
27858
28268
  const nodeChanges = [];
27859
28269
  container.children.forEach((child, i3) => {
27860
28270
  const position = String.fromCharCode(33 + i3 % 90);
27861
- collectNodeChanges(child, options.sessionID, options.parentGUID, position, nodeChanges);
28271
+ collectNodeChanges(child, options.sessionID, options.parentGUID, position, nodeChanges, container);
27862
28272
  });
27863
28273
  return {
27864
28274
  nodeChanges,
27865
28275
  nextLocalID: container.localIDCounter
27866
28276
  };
27867
28277
  }
27868
- // packages/cli/src/render/components.tsx
27869
- var React = __toESM(require_react(), 1);
27870
- var c6 = (type) => (props) => React.createElement(type, props);
27871
- var Frame = c6("frame");
27872
- var Rectangle = c6("rectangle");
27873
- var Ellipse = c6("ellipse");
27874
- var Text = c6("text");
27875
- var Line = c6("line");
27876
- var Star = c6("star");
27877
- var Polygon = c6("polygon");
27878
- var Vector = c6("vector");
27879
- var Component = c6("component");
27880
- var Instance = c6("instance");
27881
- var Group = c6("group");
27882
- var Page = c6("page");
27883
- var INTRINSIC_ELEMENTS = [
27884
- "Frame",
27885
- "Rectangle",
27886
- "Ellipse",
27887
- "Text",
27888
- "Line",
27889
- "Star",
27890
- "Polygon",
27891
- "Vector",
27892
- "Component",
27893
- "Instance",
27894
- "Group",
27895
- "Page",
27896
- "View"
27897
- ];
28278
+ function getDefaultVariants(variants) {
28279
+ const defaults = {};
28280
+ for (const [key, values] of Object.entries(variants)) {
28281
+ if (values.length > 0) {
28282
+ defaults[key] = values[0];
28283
+ }
28284
+ }
28285
+ return defaults;
28286
+ }
28287
+ function resetRenderedComponents() {
28288
+ renderedComponents.clear();
28289
+ renderedComponentSets.clear();
28290
+ renderedComponentSetVariants.clear();
28291
+ pendingComponentSetInstances.length = 0;
28292
+ }
27898
28293
  // packages/cli/src/commands/render.ts
27899
28294
  var import_esbuild = __toESM(require_main(), 1);
27900
28295
  var PROXY_URL2 = process.env.FIGMA_PROXY_URL || "http://localhost:38451";
@@ -27921,8 +28316,27 @@ function findNodeModulesDir() {
27921
28316
  var JSX_DEFINE = Object.fromEntries(INTRINSIC_ELEMENTS.map((name) => [name, JSON.stringify(name.toLowerCase())]));
27922
28317
  function transformJsxSnippet(code) {
27923
28318
  const snippet = code.trim();
27924
- const isModule = snippet.includes("import ") || snippet.includes("export ");
27925
- const fullCode = isModule ? snippet : `export default (React) => () => (${snippet});`;
28319
+ if (snippet.includes("import ") || snippet.includes("export ")) {
28320
+ return import_esbuild.transformSync(snippet, {
28321
+ loader: "tsx",
28322
+ jsx: "transform",
28323
+ jsxFactory: "React.createElement",
28324
+ jsxFragment: "React.Fragment",
28325
+ define: JSX_DEFINE
28326
+ }).code;
28327
+ }
28328
+ const jsxStart = snippet.search(/<[A-Z]/);
28329
+ const hasSetupCode = jsxStart > 0;
28330
+ const usesDefineVars = snippet.includes("defineVars");
28331
+ let fullCode;
28332
+ if (hasSetupCode) {
28333
+ const setupPart = snippet.slice(0, jsxStart).trim();
28334
+ const jsxPart = snippet.slice(jsxStart);
28335
+ const params = usesDefineVars ? "(React, { defineVars })" : "(React)";
28336
+ fullCode = `export default ${params} => { ${setupPart}; return () => (${jsxPart}); };`;
28337
+ } else {
28338
+ fullCode = `export default (React) => () => (${snippet});`;
28339
+ }
27926
28340
  const result = import_esbuild.transformSync(fullCode, {
27927
28341
  loader: "tsx",
27928
28342
  jsx: "transform",
@@ -27932,11 +28346,63 @@ function transformJsxSnippet(code) {
27932
28346
  });
27933
28347
  return result.code;
27934
28348
  }
28349
+ var HELP = `
28350
+ Render JSX to Figma (~100x faster than plugin API).
28351
+
28352
+ EXAMPLES
28353
+
28354
+ # From stdin
28355
+ echo '<Frame style={{padding: 24, backgroundColor: "#3B82F6"}}>
28356
+ <Text style={{fontSize: 18, color: "#FFF"}}>Hello</Text>
28357
+ </Frame>' | figma-use render --stdin
28358
+
28359
+ # From file
28360
+ figma-use render ./Card.figma.tsx
28361
+ figma-use render ./Card.figma.tsx --props '{"title": "Hello"}'
28362
+
28363
+ COMPONENTS
28364
+
28365
+ # defineComponent \u2014 first usage creates Component, rest create Instances
28366
+ const Card = defineComponent('Card', <Frame>...</Frame>)
28367
+ <Card /><Card /><Card />
28368
+
28369
+ # defineComponentSet \u2014 creates Figma ComponentSet with all variants
28370
+ const Button = defineComponentSet('Button', {
28371
+ variant: ['Primary', 'Secondary'] as const,
28372
+ size: ['Small', 'Large'] as const,
28373
+ }, ({ variant, size }) => <Frame>...</Frame>)
28374
+ <Button variant="Primary" size="Large" />
28375
+
28376
+ VARIABLE BINDINGS
28377
+
28378
+ const colors = defineVars({
28379
+ primary: { name: 'Colors/Blue/500', value: '#3B82F6' },
28380
+ })
28381
+ <Frame style={{ backgroundColor: colors.primary }} />
28382
+
28383
+ ELEMENTS
28384
+
28385
+ Frame, Rectangle, Ellipse, Text, Line, Star, Polygon, Vector, Group
28386
+
28387
+ STYLE PROPS
28388
+
28389
+ Layout: flexDirection, justifyContent, alignItems, gap, padding
28390
+ Size: width, height, x, y
28391
+ Appearance: backgroundColor, borderColor, borderWidth, borderRadius, opacity
28392
+ Text: fontSize, fontFamily, fontWeight, color, textAlign
28393
+
28394
+ SETUP
28395
+
28396
+ 1. Start Figma: figma --remote-debugging-port=9222
28397
+ 2. Start proxy: figma-use proxy
28398
+ 3. Open plugin: Plugins \u2192 Development \u2192 Figma Use
28399
+ `;
27935
28400
  var render_default = defineCommand({
27936
28401
  meta: {
27937
- description: "Render React component to Figma via WebSocket"
28402
+ description: "Render JSX to Figma. Use --examples for API reference."
27938
28403
  },
27939
28404
  args: {
28405
+ examples: { type: "boolean", description: "Show examples and API reference" },
27940
28406
  file: { type: "positional", description: "TSX/JSX file path", required: false },
27941
28407
  stdin: { type: "boolean", description: "Read TSX from stdin" },
27942
28408
  props: { type: "string", description: "JSON props to pass to component" },
@@ -27946,6 +28412,10 @@ var render_default = defineCommand({
27946
28412
  dryRun: { type: "boolean", description: "Output NodeChanges without sending to Figma" }
27947
28413
  },
27948
28414
  async run({ args }) {
28415
+ if (args.examples) {
28416
+ console.log(HELP);
28417
+ return;
28418
+ }
27949
28419
  let filePath;
27950
28420
  let tempFile = null;
27951
28421
  if (args.stdin) {
@@ -27973,8 +28443,9 @@ var render_default = defineCommand({
27973
28443
  const module = await import(filePath);
27974
28444
  const exportName = args.export || "default";
27975
28445
  let Component2 = module[exportName];
27976
- if (typeof Component2 === "function" && Component2.length === 1 && args.stdin) {
27977
- Component2 = Component2(React2);
28446
+ if (typeof Component2 === "function" && (Component2.length === 1 || Component2.length === 2) && args.stdin) {
28447
+ const { defineVars: defineVars2 } = await Promise.resolve().then(() => (init_vars(), exports_vars));
28448
+ Component2 = Component2(React3, { defineVars: defineVars2 });
27978
28449
  }
27979
28450
  if (!Component2) {
27980
28451
  console.error(fail(`Export "${exportName}" not found`));
@@ -27992,19 +28463,26 @@ var render_default = defineCommand({
27992
28463
  }
27993
28464
  const parentGUID = args.parent ? parseGUID(args.parent) : await getParentGUID();
27994
28465
  const sessionID = parentGUID.sessionID || Date.now() % 1e6;
27995
- const sendNodeChanges = async (changes) => {
28466
+ const sendNodeChanges = async (changes, pendingInstances2) => {
27996
28467
  const response = await fetch(`${PROXY_URL2}/render`, {
27997
28468
  method: "POST",
27998
28469
  headers: { "Content-Type": "application/json" },
27999
- body: JSON.stringify({ fileKey, nodeChanges: changes })
28470
+ body: JSON.stringify({ fileKey, nodeChanges: changes, pendingComponentSetInstances: pendingInstances2 })
28000
28471
  });
28001
28472
  const data = await response.json();
28002
28473
  if (data.error) {
28003
28474
  throw new Error(data.error);
28004
28475
  }
28005
28476
  };
28477
+ if (!isRegistryLoaded()) {
28478
+ try {
28479
+ const variables = await sendCommand("get-variables", { simple: true });
28480
+ loadVariablesIntoRegistry(variables);
28481
+ } catch {}
28482
+ }
28006
28483
  const props = args.props ? JSON.parse(args.props) : {};
28007
- const element = React2.createElement(Component2, props);
28484
+ const element = React3.createElement(Component2, props);
28485
+ resetRenderedComponents();
28008
28486
  const result = renderToNodeChanges(element, {
28009
28487
  sessionID,
28010
28488
  parentGUID,
@@ -28017,7 +28495,9 @@ var render_default = defineCommand({
28017
28495
  if (!args.json) {
28018
28496
  console.log(`Rendering ${result.nodeChanges.length} nodes...`);
28019
28497
  }
28020
- await sendNodeChanges(result.nodeChanges);
28498
+ const pendingInstances = getPendingComponentSetInstances();
28499
+ clearPendingComponentSetInstances();
28500
+ await sendNodeChanges(result.nodeChanges, pendingInstances);
28021
28501
  if (args.json) {
28022
28502
  const ids = result.nodeChanges.map((nc) => ({
28023
28503
  id: `${nc.guid.sessionID}:${nc.guid.localID}`,
@@ -30209,8 +30689,8 @@ var component_default2 = defineCommand({
30209
30689
  var main = defineCommand({
30210
30690
  meta: {
30211
30691
  name: "figma-use",
30212
- description: "Control Figma from the command line",
30213
- version: "0.2.0"
30692
+ description: "Control Figma from the command line. Supports JSX rendering with components and variants \u2014 see `figma-use render --examples`",
30693
+ version: "0.5.0"
30214
30694
  },
30215
30695
  subCommands: exports_commands
30216
30696
  });