@dannote/figma-use 0.2.1 → 0.4.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/CHANGELOG.md +83 -1
- package/README.md +128 -10
- package/SKILL.md +51 -0
- package/dist/.figma-render-1768690678858.tsx +3 -0
- package/dist/.figma-render-1768690686527.tsx +3 -0
- package/dist/cli/index.js +25969 -2290
- package/dist/proxy/index.js +7531 -7
- package/package.json +4 -2
- package/packages/plugin/dist/main.js +338 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dannote/figma-use",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Control Figma from the command line. Full read/write access for AI agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -45,11 +45,13 @@
|
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"citty": "^0.1.6",
|
|
47
47
|
"consola": "^3.4.2",
|
|
48
|
-
"elysia": "^1.2.25"
|
|
48
|
+
"elysia": "^1.2.25",
|
|
49
|
+
"kiwi-schema": "^0.5.0"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"@types/bun": "^1.3.6",
|
|
52
53
|
"esbuild": "^0.25.4",
|
|
54
|
+
"react": "19",
|
|
53
55
|
"typescript": "^5.8.3"
|
|
54
56
|
}
|
|
55
57
|
}
|
|
@@ -43,10 +43,142 @@
|
|
|
43
43
|
// src/main.ts
|
|
44
44
|
console.log("[Figma Bridge] Plugin main loaded at", (/* @__PURE__ */ new Date()).toISOString());
|
|
45
45
|
figma.showUI(__html__, { width: 300, height: 200 });
|
|
46
|
+
var loadedFonts = /* @__PURE__ */ new Set();
|
|
47
|
+
var fontLoadPromises = /* @__PURE__ */ new Map();
|
|
48
|
+
var interPromise = figma.loadFontAsync({ family: "Inter", style: "Regular" });
|
|
49
|
+
fontLoadPromises.set("Inter:Regular", interPromise);
|
|
50
|
+
interPromise.then(() => loadedFonts.add("Inter:Regular"));
|
|
51
|
+
function loadFont(family, style) {
|
|
52
|
+
const key = `${family}:${style}`;
|
|
53
|
+
if (loadedFonts.has(key)) return;
|
|
54
|
+
const pending = fontLoadPromises.get(key);
|
|
55
|
+
if (pending) return pending;
|
|
56
|
+
const promise = figma.loadFontAsync({ family, style });
|
|
57
|
+
fontLoadPromises.set(key, promise);
|
|
58
|
+
promise.then(() => {
|
|
59
|
+
loadedFonts.add(key);
|
|
60
|
+
fontLoadPromises.delete(key);
|
|
61
|
+
});
|
|
62
|
+
return promise;
|
|
63
|
+
}
|
|
64
|
+
function createNodeFast(command, args, nodeCache, deferredLayouts) {
|
|
65
|
+
return __async(this, null, function* () {
|
|
66
|
+
if (!args) return null;
|
|
67
|
+
const {
|
|
68
|
+
x = 0,
|
|
69
|
+
y = 0,
|
|
70
|
+
width,
|
|
71
|
+
height,
|
|
72
|
+
name,
|
|
73
|
+
parentId,
|
|
74
|
+
fill,
|
|
75
|
+
stroke,
|
|
76
|
+
strokeWeight,
|
|
77
|
+
radius,
|
|
78
|
+
opacity,
|
|
79
|
+
layoutMode,
|
|
80
|
+
itemSpacing,
|
|
81
|
+
padding,
|
|
82
|
+
text,
|
|
83
|
+
fontSize,
|
|
84
|
+
fontFamily,
|
|
85
|
+
fontStyle
|
|
86
|
+
} = args;
|
|
87
|
+
let node = null;
|
|
88
|
+
switch (command) {
|
|
89
|
+
case "create-frame": {
|
|
90
|
+
const frame = figma.createFrame();
|
|
91
|
+
frame.x = x;
|
|
92
|
+
frame.y = y;
|
|
93
|
+
frame.resize(width || 100, height || 100);
|
|
94
|
+
if (name) frame.name = name;
|
|
95
|
+
if (fill) frame.fills = [{ type: "SOLID", color: hexToRgb(fill) }];
|
|
96
|
+
if (stroke) frame.strokes = [{ type: "SOLID", color: hexToRgb(stroke) }];
|
|
97
|
+
if (strokeWeight) frame.strokeWeight = strokeWeight;
|
|
98
|
+
if (typeof radius === "number") frame.cornerRadius = radius;
|
|
99
|
+
if (typeof opacity === "number") frame.opacity = opacity;
|
|
100
|
+
if (layoutMode && layoutMode !== "NONE") {
|
|
101
|
+
deferredLayouts == null ? void 0 : deferredLayouts.push({
|
|
102
|
+
frame,
|
|
103
|
+
layoutMode,
|
|
104
|
+
itemSpacing,
|
|
105
|
+
padding
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
node = frame;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
case "create-rectangle": {
|
|
112
|
+
const rect = figma.createRectangle();
|
|
113
|
+
rect.x = x;
|
|
114
|
+
rect.y = y;
|
|
115
|
+
rect.resize(width || 100, height || 100);
|
|
116
|
+
if (name) rect.name = name;
|
|
117
|
+
if (fill) rect.fills = [{ type: "SOLID", color: hexToRgb(fill) }];
|
|
118
|
+
if (stroke) rect.strokes = [{ type: "SOLID", color: hexToRgb(stroke) }];
|
|
119
|
+
if (strokeWeight) rect.strokeWeight = strokeWeight;
|
|
120
|
+
if (typeof radius === "number") rect.cornerRadius = radius;
|
|
121
|
+
if (typeof opacity === "number") rect.opacity = opacity;
|
|
122
|
+
node = rect;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case "create-ellipse": {
|
|
126
|
+
const ellipse = figma.createEllipse();
|
|
127
|
+
ellipse.x = x;
|
|
128
|
+
ellipse.y = y;
|
|
129
|
+
ellipse.resize(width || 100, height || 100);
|
|
130
|
+
if (name) ellipse.name = name;
|
|
131
|
+
if (fill) ellipse.fills = [{ type: "SOLID", color: hexToRgb(fill) }];
|
|
132
|
+
if (stroke) ellipse.strokes = [{ type: "SOLID", color: hexToRgb(stroke) }];
|
|
133
|
+
if (strokeWeight) ellipse.strokeWeight = strokeWeight;
|
|
134
|
+
if (typeof opacity === "number") ellipse.opacity = opacity;
|
|
135
|
+
node = ellipse;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case "create-text": {
|
|
139
|
+
const textNode = figma.createText();
|
|
140
|
+
const family = fontFamily || "Inter";
|
|
141
|
+
const style = fontStyle || "Regular";
|
|
142
|
+
yield loadFont(family, style);
|
|
143
|
+
textNode.fontName = { family, style };
|
|
144
|
+
textNode.characters = text || "";
|
|
145
|
+
textNode.x = x;
|
|
146
|
+
textNode.y = y;
|
|
147
|
+
if (name) textNode.name = name;
|
|
148
|
+
if (fontSize) textNode.fontSize = fontSize;
|
|
149
|
+
if (fill) textNode.fills = [{ type: "SOLID", color: hexToRgb(fill) }];
|
|
150
|
+
if (typeof opacity === "number") textNode.opacity = opacity;
|
|
151
|
+
node = textNode;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
default:
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
return node;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
var NEEDS_ALL_PAGES = /* @__PURE__ */ new Set([
|
|
161
|
+
"get-node-info",
|
|
162
|
+
"get-node-tree",
|
|
163
|
+
"get-node-children",
|
|
164
|
+
"set-parent",
|
|
165
|
+
"clone-node",
|
|
166
|
+
"delete-node",
|
|
167
|
+
"get-pages",
|
|
168
|
+
"set-current-page",
|
|
169
|
+
"get-components",
|
|
170
|
+
"get-styles",
|
|
171
|
+
"export-node",
|
|
172
|
+
"screenshot"
|
|
173
|
+
]);
|
|
174
|
+
var allPagesLoaded = false;
|
|
46
175
|
figma.ui.onmessage = (msg) => __async(null, null, function* () {
|
|
47
176
|
if (msg.type !== "command") return;
|
|
48
177
|
try {
|
|
49
|
-
|
|
178
|
+
if (!allPagesLoaded && NEEDS_ALL_PAGES.has(msg.command)) {
|
|
179
|
+
yield figma.loadAllPagesAsync();
|
|
180
|
+
allPagesLoaded = true;
|
|
181
|
+
}
|
|
50
182
|
const result = yield handleCommand(msg.command, msg.args);
|
|
51
183
|
figma.ui.postMessage({ type: "result", id: msg.id, result });
|
|
52
184
|
} catch (error) {
|
|
@@ -55,7 +187,81 @@
|
|
|
55
187
|
});
|
|
56
188
|
function handleCommand(command, args) {
|
|
57
189
|
return __async(this, null, function* () {
|
|
190
|
+
var _a, _b, _c, _d;
|
|
58
191
|
switch (command) {
|
|
192
|
+
// ==================== BATCH ====================
|
|
193
|
+
case "batch": {
|
|
194
|
+
const { commands } = args;
|
|
195
|
+
const results = [];
|
|
196
|
+
const refMap = /* @__PURE__ */ new Map();
|
|
197
|
+
const nodeCache = /* @__PURE__ */ new Map();
|
|
198
|
+
const deferredLayouts = [];
|
|
199
|
+
const internalAttachments = [];
|
|
200
|
+
const externalAttachments = [];
|
|
201
|
+
const rootNodes = [];
|
|
202
|
+
for (const cmd of commands) {
|
|
203
|
+
if (((_a = cmd.args) == null ? void 0 : _a.parentRef) && refMap.has(cmd.args.parentRef)) {
|
|
204
|
+
cmd.args.parentId = refMap.get(cmd.args.parentRef);
|
|
205
|
+
delete cmd.args.parentRef;
|
|
206
|
+
}
|
|
207
|
+
const node = yield createNodeFast(cmd.command, cmd.args, nodeCache, deferredLayouts);
|
|
208
|
+
if (node) {
|
|
209
|
+
results.push({ id: node.id, name: node.name });
|
|
210
|
+
nodeCache.set(node.id, node);
|
|
211
|
+
if ((_b = cmd.args) == null ? void 0 : _b.ref) {
|
|
212
|
+
refMap.set(cmd.args.ref, node.id);
|
|
213
|
+
}
|
|
214
|
+
const parentId = (_c = cmd.args) == null ? void 0 : _c.parentId;
|
|
215
|
+
if (parentId) {
|
|
216
|
+
if (nodeCache.has(parentId)) {
|
|
217
|
+
internalAttachments.push({ node, parentId });
|
|
218
|
+
} else {
|
|
219
|
+
externalAttachments.push({ node, parentId });
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
rootNodes.push(node);
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
const result = yield handleCommand(cmd.command, cmd.args);
|
|
226
|
+
results.push(result);
|
|
227
|
+
if ((_d = cmd.args) == null ? void 0 : _d.ref) {
|
|
228
|
+
refMap.set(cmd.args.ref, result.id);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
for (const attachment of internalAttachments) {
|
|
233
|
+
const parent = nodeCache.get(attachment.parentId);
|
|
234
|
+
if (parent && "appendChild" in parent) {
|
|
235
|
+
parent.appendChild(attachment.node);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
for (const layout of deferredLayouts) {
|
|
239
|
+
layout.frame.layoutMode = layout.layoutMode;
|
|
240
|
+
layout.frame.primaryAxisSizingMode = "AUTO";
|
|
241
|
+
layout.frame.counterAxisSizingMode = "AUTO";
|
|
242
|
+
if (layout.itemSpacing) layout.frame.itemSpacing = layout.itemSpacing;
|
|
243
|
+
if (layout.padding) {
|
|
244
|
+
layout.frame.paddingTop = layout.padding.top;
|
|
245
|
+
layout.frame.paddingRight = layout.padding.right;
|
|
246
|
+
layout.frame.paddingBottom = layout.padding.bottom;
|
|
247
|
+
layout.frame.paddingLeft = layout.padding.left;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
for (const node of rootNodes) {
|
|
251
|
+
figma.currentPage.appendChild(node);
|
|
252
|
+
}
|
|
253
|
+
for (const attachment of externalAttachments) {
|
|
254
|
+
let parent = nodeCache.get(attachment.parentId);
|
|
255
|
+
if (!parent) {
|
|
256
|
+
parent = yield figma.getNodeByIdAsync(attachment.parentId);
|
|
257
|
+
}
|
|
258
|
+
if (parent && "appendChild" in parent) {
|
|
259
|
+
parent.appendChild(attachment.node);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
figma.commitUndo();
|
|
263
|
+
return results;
|
|
264
|
+
}
|
|
59
265
|
// ==================== READ ====================
|
|
60
266
|
case "get-selection":
|
|
61
267
|
return figma.currentPage.selection.map(serializeNode);
|
|
@@ -70,14 +276,52 @@
|
|
|
70
276
|
const { id } = args;
|
|
71
277
|
const node = yield figma.getNodeByIdAsync(id);
|
|
72
278
|
if (!node) throw new Error("Node not found");
|
|
73
|
-
const
|
|
74
|
-
const base =
|
|
279
|
+
const serializeTreeNode = (n) => {
|
|
280
|
+
const base = {
|
|
281
|
+
id: n.id,
|
|
282
|
+
name: n.name,
|
|
283
|
+
type: n.type
|
|
284
|
+
};
|
|
285
|
+
if ("x" in n) base.x = Math.round(n.x);
|
|
286
|
+
if ("y" in n) base.y = Math.round(n.y);
|
|
287
|
+
if ("width" in n) base.width = Math.round(n.width);
|
|
288
|
+
if ("height" in n) base.height = Math.round(n.height);
|
|
289
|
+
if ("fills" in n && Array.isArray(n.fills)) {
|
|
290
|
+
const solid = n.fills.find((f) => f.type === "SOLID");
|
|
291
|
+
if (solid) base.fills = [{ type: "SOLID", color: rgbToHex(solid.color) }];
|
|
292
|
+
}
|
|
293
|
+
if ("strokes" in n && Array.isArray(n.strokes) && n.strokes.length > 0) {
|
|
294
|
+
const solid = n.strokes.find((s) => s.type === "SOLID");
|
|
295
|
+
if (solid) base.strokes = [{ type: "SOLID", color: rgbToHex(solid.color) }];
|
|
296
|
+
}
|
|
297
|
+
if ("strokeWeight" in n && typeof n.strokeWeight === "number" && n.strokeWeight > 0) {
|
|
298
|
+
base.strokeWeight = n.strokeWeight;
|
|
299
|
+
}
|
|
300
|
+
if ("cornerRadius" in n && typeof n.cornerRadius === "number" && n.cornerRadius > 0) {
|
|
301
|
+
base.cornerRadius = n.cornerRadius;
|
|
302
|
+
}
|
|
303
|
+
if ("opacity" in n && n.opacity !== 1) base.opacity = n.opacity;
|
|
304
|
+
if ("visible" in n && !n.visible) base.visible = false;
|
|
305
|
+
if ("locked" in n && n.locked) base.locked = true;
|
|
306
|
+
if ("layoutMode" in n && n.layoutMode !== "NONE") {
|
|
307
|
+
base.layoutMode = n.layoutMode;
|
|
308
|
+
if ("itemSpacing" in n) base.itemSpacing = n.itemSpacing;
|
|
309
|
+
}
|
|
310
|
+
if (n.type === "TEXT") {
|
|
311
|
+
const t = n;
|
|
312
|
+
base.characters = t.characters;
|
|
313
|
+
if (typeof t.fontSize === "number") base.fontSize = t.fontSize;
|
|
314
|
+
if (typeof t.fontName === "object") {
|
|
315
|
+
base.fontFamily = t.fontName.family;
|
|
316
|
+
base.fontStyle = t.fontName.style;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
75
319
|
if ("children" in n && n.children) {
|
|
76
|
-
base.children = n.children.map(
|
|
320
|
+
base.children = n.children.map(serializeTreeNode);
|
|
77
321
|
}
|
|
78
322
|
return base;
|
|
79
323
|
};
|
|
80
|
-
return
|
|
324
|
+
return serializeTreeNode(node);
|
|
81
325
|
}
|
|
82
326
|
case "get-all-components": {
|
|
83
327
|
const { name, limit = 50, page } = args || {};
|
|
@@ -97,7 +341,7 @@
|
|
|
97
341
|
}
|
|
98
342
|
return components.length < limit;
|
|
99
343
|
};
|
|
100
|
-
const pages = page ? figma.root.children.filter((p) => p.id === page || p.name === page) : figma.root.children;
|
|
344
|
+
const pages = page ? figma.root.children.filter((p) => p.id === page || p.name === page || p.name.includes(page)) : figma.root.children;
|
|
101
345
|
for (const pageNode of pages) {
|
|
102
346
|
if (components.length >= limit) break;
|
|
103
347
|
for (const child of pageNode.children) {
|
|
@@ -115,9 +359,15 @@
|
|
|
115
359
|
return { id: page.id, name: page.name };
|
|
116
360
|
}
|
|
117
361
|
case "set-current-page": {
|
|
118
|
-
const {
|
|
119
|
-
|
|
120
|
-
|
|
362
|
+
const { page: pageArg } = args;
|
|
363
|
+
let page = null;
|
|
364
|
+
const byId = yield figma.getNodeByIdAsync(pageArg);
|
|
365
|
+
if (byId && byId.type === "PAGE") {
|
|
366
|
+
page = byId;
|
|
367
|
+
} else {
|
|
368
|
+
page = figma.root.children.find((p) => p.name === pageArg || p.name.includes(pageArg)) || null;
|
|
369
|
+
}
|
|
370
|
+
if (!page) throw new Error("Page not found");
|
|
121
371
|
yield figma.setCurrentPageAsync(page);
|
|
122
372
|
return { id: page.id, name: page.name };
|
|
123
373
|
}
|
|
@@ -297,7 +547,7 @@
|
|
|
297
547
|
const textNode = figma.createText();
|
|
298
548
|
const family = fontFamily || "Inter";
|
|
299
549
|
const style = fontStyle || "Regular";
|
|
300
|
-
yield
|
|
550
|
+
yield loadFont(family, style);
|
|
301
551
|
textNode.x = x;
|
|
302
552
|
textNode.y = y;
|
|
303
553
|
textNode.fontName = { family, style };
|
|
@@ -346,7 +596,7 @@
|
|
|
346
596
|
const { name, fontFamily, fontStyle, fontSize } = args;
|
|
347
597
|
const style = figma.createTextStyle();
|
|
348
598
|
style.name = name;
|
|
349
|
-
yield
|
|
599
|
+
yield loadFont(fontFamily || "Inter", fontStyle || "Regular");
|
|
350
600
|
style.fontName = { family: fontFamily || "Inter", style: fontStyle || "Regular" };
|
|
351
601
|
if (fontSize) style.fontSize = fontSize;
|
|
352
602
|
return { id: style.id, name: style.name, key: style.key };
|
|
@@ -491,7 +741,7 @@
|
|
|
491
741
|
const node = yield figma.getNodeByIdAsync(id);
|
|
492
742
|
if (!node || node.type !== "TEXT") throw new Error("Text node not found");
|
|
493
743
|
const fontName = node.fontName;
|
|
494
|
-
yield
|
|
744
|
+
yield loadFont(fontName.family, fontName.style);
|
|
495
745
|
node.characters = text;
|
|
496
746
|
return serializeNode(node);
|
|
497
747
|
}
|
|
@@ -511,7 +761,7 @@
|
|
|
511
761
|
const currentFont = node.fontName;
|
|
512
762
|
const family = fontFamily || currentFont.family;
|
|
513
763
|
const style = fontStyle || currentFont.style;
|
|
514
|
-
yield
|
|
764
|
+
yield loadFont(family, style);
|
|
515
765
|
node.fontName = { family, style };
|
|
516
766
|
if (fontSize !== void 0) node.fontSize = fontSize;
|
|
517
767
|
return serializeNode(node);
|
|
@@ -532,14 +782,24 @@
|
|
|
532
782
|
return node.children.map((c) => serializeWithDepth(c, 1));
|
|
533
783
|
}
|
|
534
784
|
case "find-by-name": {
|
|
535
|
-
const { name, type, exact } = args;
|
|
785
|
+
const { name, type, exact, limit = 100 } = args;
|
|
536
786
|
const results = [];
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
787
|
+
const nameLower = name == null ? void 0 : name.toLowerCase();
|
|
788
|
+
const searchNode = (node) => {
|
|
789
|
+
if (results.length >= limit) return false;
|
|
790
|
+
const nameMatch = !nameLower || (exact ? node.name === name : node.name.toLowerCase().includes(nameLower));
|
|
791
|
+
const typeMatch = !type || node.type === type;
|
|
792
|
+
if (nameMatch && typeMatch) results.push(serializeNode(node));
|
|
793
|
+
if ("children" in node) {
|
|
794
|
+
for (const child of node.children) {
|
|
795
|
+
if (!searchNode(child)) return false;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return results.length < limit;
|
|
799
|
+
};
|
|
800
|
+
for (const child of figma.currentPage.children) {
|
|
801
|
+
if (!searchNode(child)) break;
|
|
802
|
+
}
|
|
543
803
|
return results;
|
|
544
804
|
}
|
|
545
805
|
case "select-nodes": {
|
|
@@ -610,7 +870,7 @@
|
|
|
610
870
|
const node = yield figma.getNodeByIdAsync(id);
|
|
611
871
|
if (!node || node.type !== "TEXT") throw new Error("Text node not found");
|
|
612
872
|
const fontName = node.fontName;
|
|
613
|
-
yield
|
|
873
|
+
yield loadFont(fontName.family, fontName.style);
|
|
614
874
|
if (lineHeight !== void 0) {
|
|
615
875
|
node.lineHeight = lineHeight === "auto" ? { unit: "AUTO" } : { unit: "PIXELS", value: lineHeight };
|
|
616
876
|
}
|
|
@@ -924,10 +1184,34 @@
|
|
|
924
1184
|
const fn = new AsyncFunction("figma", wrappedCode);
|
|
925
1185
|
return yield fn(figma);
|
|
926
1186
|
}
|
|
1187
|
+
// ==================== LAYOUT ====================
|
|
1188
|
+
case "trigger-layout": {
|
|
1189
|
+
const { nodeId } = args;
|
|
1190
|
+
const root = yield figma.getNodeByIdAsync(nodeId);
|
|
1191
|
+
if (!root) return null;
|
|
1192
|
+
const triggerRecursive = (node) => {
|
|
1193
|
+
if ("layoutMode" in node && node.layoutMode !== "NONE" && "resize" in node) {
|
|
1194
|
+
const w = node.width;
|
|
1195
|
+
const h = node.height;
|
|
1196
|
+
node.resize(w + 0.01, h + 0.01);
|
|
1197
|
+
node.resize(w, h);
|
|
1198
|
+
}
|
|
1199
|
+
if ("children" in node) {
|
|
1200
|
+
for (const child of node.children) {
|
|
1201
|
+
triggerRecursive(child);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
triggerRecursive(root);
|
|
1206
|
+
return { triggered: true };
|
|
1207
|
+
}
|
|
927
1208
|
// ==================== VARIABLES ====================
|
|
928
1209
|
case "get-variables": {
|
|
929
|
-
const { type } = args;
|
|
1210
|
+
const { type, simple } = args;
|
|
930
1211
|
const variables = yield figma.variables.getLocalVariablesAsync(type);
|
|
1212
|
+
if (simple) {
|
|
1213
|
+
return variables.map((v) => ({ id: v.id, name: v.name }));
|
|
1214
|
+
}
|
|
931
1215
|
return variables.map((v) => serializeVariable(v));
|
|
932
1216
|
}
|
|
933
1217
|
case "get-variable": {
|
|
@@ -974,6 +1258,32 @@
|
|
|
974
1258
|
}
|
|
975
1259
|
return serializeNode(node);
|
|
976
1260
|
}
|
|
1261
|
+
case "bind-fill-variable": {
|
|
1262
|
+
const { nodeId, variableId, paintIndex = 0 } = args;
|
|
1263
|
+
const node = yield figma.getNodeByIdAsync(nodeId);
|
|
1264
|
+
if (!node) throw new Error("Node not found");
|
|
1265
|
+
if (!("fills" in node)) throw new Error("Node does not have fills");
|
|
1266
|
+
const variable = yield figma.variables.getVariableByIdAsync(variableId);
|
|
1267
|
+
if (!variable) throw new Error("Variable not found");
|
|
1268
|
+
const fills = node.fills;
|
|
1269
|
+
if (!fills[paintIndex]) throw new Error("Paint not found at index " + paintIndex);
|
|
1270
|
+
const newFill = figma.variables.setBoundVariableForPaint(fills[paintIndex], "color", variable);
|
|
1271
|
+
node.fills = [...fills.slice(0, paintIndex), newFill, ...fills.slice(paintIndex + 1)];
|
|
1272
|
+
return serializeNode(node);
|
|
1273
|
+
}
|
|
1274
|
+
case "bind-stroke-variable": {
|
|
1275
|
+
const { nodeId, variableId, paintIndex = 0 } = args;
|
|
1276
|
+
const node = yield figma.getNodeByIdAsync(nodeId);
|
|
1277
|
+
if (!node) throw new Error("Node not found");
|
|
1278
|
+
if (!("strokes" in node)) throw new Error("Node does not have strokes");
|
|
1279
|
+
const variable = yield figma.variables.getVariableByIdAsync(variableId);
|
|
1280
|
+
if (!variable) throw new Error("Variable not found");
|
|
1281
|
+
const strokes = node.strokes;
|
|
1282
|
+
if (!strokes[paintIndex]) throw new Error("Paint not found at index " + paintIndex);
|
|
1283
|
+
const newStroke = figma.variables.setBoundVariableForPaint(strokes[paintIndex], "color", variable);
|
|
1284
|
+
node.strokes = [...strokes.slice(0, paintIndex), newStroke, ...strokes.slice(paintIndex + 1)];
|
|
1285
|
+
return serializeNode(node);
|
|
1286
|
+
}
|
|
977
1287
|
// ==================== VARIABLE COLLECTIONS ====================
|
|
978
1288
|
case "get-variable-collections": {
|
|
979
1289
|
const collections = yield figma.variables.getLocalVariableCollectionsAsync();
|
|
@@ -1020,6 +1330,9 @@
|
|
|
1020
1330
|
name: node.name,
|
|
1021
1331
|
type: node.type
|
|
1022
1332
|
};
|
|
1333
|
+
if (node.parent && node.parent.type !== "PAGE") {
|
|
1334
|
+
base.parentId = node.parent.id;
|
|
1335
|
+
}
|
|
1023
1336
|
if ("x" in node) base.x = Math.round(node.x);
|
|
1024
1337
|
if ("y" in node) base.y = Math.round(node.y);
|
|
1025
1338
|
if ("width" in node) base.width = Math.round(node.width);
|
|
@@ -1036,6 +1349,9 @@
|
|
|
1036
1349
|
if ("strokeWeight" in node && typeof node.strokeWeight === "number" && node.strokeWeight > 0) {
|
|
1037
1350
|
base.strokeWeight = node.strokeWeight;
|
|
1038
1351
|
}
|
|
1352
|
+
if ("cornerRadius" in node && typeof node.cornerRadius === "number" && node.cornerRadius > 0) {
|
|
1353
|
+
base.cornerRadius = node.cornerRadius;
|
|
1354
|
+
}
|
|
1039
1355
|
if ("componentPropertyDefinitions" in node) {
|
|
1040
1356
|
try {
|
|
1041
1357
|
base.componentPropertyDefinitions = node.componentPropertyDefinitions;
|