@seed-design/figma 1.1.13 → 1.1.14
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/lib/codegen/index.cjs +636 -114
- package/lib/codegen/index.d.ts +136 -96
- package/lib/codegen/index.d.ts.map +1 -1
- package/lib/codegen/index.js +636 -114
- package/lib/codegen/targets/react/index.cjs +682 -134
- package/lib/codegen/targets/react/index.d.ts +31 -11
- package/lib/codegen/targets/react/index.d.ts.map +1 -1
- package/lib/codegen/targets/react/index.js +682 -135
- package/lib/index.cjs +1254 -433
- package/lib/index.d.ts +46 -10
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1254 -433
- package/package.json +1 -1
- package/src/codegen/component-properties.ts +5 -5
- package/src/codegen/core/value-resolver.ts +49 -12
- package/src/codegen/targets/figma/frame.ts +1 -0
- package/src/codegen/targets/figma/pipeline.ts +5 -0
- package/src/codegen/targets/figma/props.ts +30 -1
- package/src/codegen/targets/figma/shape.ts +1 -0
- package/src/codegen/targets/figma/value-resolver.ts +20 -0
- package/src/codegen/targets/react/component/handlers/menu-sheet.ts +1 -1
- package/src/codegen/targets/react/component/handlers/page-banner.ts +2 -2
- package/src/codegen/targets/react/component/handlers/{radio-mark.ts → radiomark.ts} +4 -4
- package/src/codegen/targets/react/component/handlers/result-section.ts +1 -1
- package/src/codegen/targets/react/component/handlers/{switch-mark.ts → switchmark.ts} +4 -4
- package/src/codegen/targets/react/component/index.ts +4 -4
- package/src/codegen/targets/react/frame.ts +16 -2
- package/src/codegen/targets/react/pipeline.ts +6 -1
- package/src/codegen/targets/react/props.ts +26 -0
- package/src/codegen/targets/react/shape.ts +5 -1
- package/src/codegen/targets/react/value-resolver.ts +26 -0
- package/src/entities/data/__generated__/component-sets/index.d.ts +84 -89
- package/src/entities/data/__generated__/component-sets/index.mjs +84 -89
- package/src/entities/data/__generated__/components/index.d.ts +2 -2
- package/src/entities/data/__generated__/components/index.mjs +2 -2
- package/src/entities/data/__generated__/icons/index.mjs +14 -0
- package/src/entities/data/__generated__/styles/index.mjs +190 -1
- package/src/entities/data/__generated__/variable-collections/index.mjs +11 -1
- package/src/entities/data/__generated__/variables/index.mjs +280 -0
- package/src/normalizer/from-plugin.ts +427 -258
- package/src/normalizer/from-rest.ts +428 -58
- package/src/normalizer/types.ts +63 -10
- package/src/utils/figma-node.ts +15 -10
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* from-plugin is guaranteed to be run in the Figma Plugin environment
|
|
3
|
+
* so we can use the Plugin API types directly (figma.getNodeByIdAsync, node.getMainComponentAsync etc)
|
|
4
|
+
* however it could be better to make users can DI later
|
|
5
|
+
*/
|
|
6
|
+
|
|
2
7
|
import type {
|
|
3
8
|
NormalizedSceneNode,
|
|
4
9
|
NormalizedFrameNode,
|
|
@@ -8,129 +13,303 @@ import type {
|
|
|
8
13
|
NormalizedInstanceNode,
|
|
9
14
|
NormalizedVectorNode,
|
|
10
15
|
NormalizedBooleanOperationNode,
|
|
16
|
+
NormalizedHasEffectsTrait,
|
|
17
|
+
NormalizedShadow,
|
|
18
|
+
NormalizedDefaultShapeTrait,
|
|
19
|
+
NormalizedHasFramePropertiesTrait,
|
|
20
|
+
NormalizedCornerTrait,
|
|
21
|
+
NormalizedIsLayerTrait,
|
|
22
|
+
NormalizedPaint,
|
|
23
|
+
NormalizedTextSegment,
|
|
11
24
|
} from "./types";
|
|
12
25
|
import { convertTransformToGradientHandles } from "@/utils/figma-gradient";
|
|
13
26
|
|
|
14
|
-
export function createPluginNormalizer() {
|
|
27
|
+
export function createPluginNormalizer(): (node: SceneNode) => Promise<NormalizedSceneNode> {
|
|
15
28
|
async function normalizeNodes(nodes: readonly SceneNode[]): Promise<NormalizedSceneNode[]> {
|
|
16
29
|
return Promise.all(nodes.filter((node) => node.visible).map(normalizeNode));
|
|
17
30
|
}
|
|
18
31
|
|
|
19
32
|
async function normalizeNode(node: SceneNode): Promise<NormalizedSceneNode> {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
switch (node.type) {
|
|
34
|
+
case "FRAME":
|
|
35
|
+
return normalizeFrameNode(node);
|
|
36
|
+
case "RECTANGLE":
|
|
37
|
+
return normalizeRectangleNode(node);
|
|
38
|
+
case "TEXT":
|
|
39
|
+
return normalizeTextNode(node);
|
|
40
|
+
case "COMPONENT":
|
|
41
|
+
return normalizeComponentNode(node);
|
|
42
|
+
case "INSTANCE":
|
|
43
|
+
return normalizeInstanceNode(node);
|
|
44
|
+
case "VECTOR":
|
|
45
|
+
return normalizeVectorNode(node);
|
|
46
|
+
case "BOOLEAN_OPERATION":
|
|
47
|
+
return normalizeBooleanOperationNode(node);
|
|
48
|
+
case "GROUP":
|
|
49
|
+
return normalizeGroupNodeAsFrameNode(node);
|
|
50
|
+
default:
|
|
51
|
+
return {
|
|
52
|
+
type: "UNHANDLED",
|
|
53
|
+
id: node.id,
|
|
54
|
+
original: node,
|
|
55
|
+
};
|
|
37
56
|
}
|
|
38
|
-
|
|
39
|
-
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Pick specific fields from boundVariables
|
|
61
|
+
*/
|
|
62
|
+
function normalizeBoundVariables({
|
|
63
|
+
boundVariables,
|
|
64
|
+
}: Pick<FrameNode, "boundVariables">): NormalizedIsLayerTrait["boundVariables"] {
|
|
65
|
+
if (!boundVariables) return undefined;
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
fills: boundVariables.fills,
|
|
69
|
+
strokes: boundVariables.strokes,
|
|
70
|
+
itemSpacing: boundVariables.itemSpacing,
|
|
71
|
+
counterAxisSpacing: boundVariables.counterAxisSpacing,
|
|
72
|
+
topLeftRadius: boundVariables.topLeftRadius,
|
|
73
|
+
topRightRadius: boundVariables.topRightRadius,
|
|
74
|
+
bottomLeftRadius: boundVariables.bottomLeftRadius,
|
|
75
|
+
bottomRightRadius: boundVariables.bottomRightRadius,
|
|
76
|
+
paddingTop: boundVariables.paddingTop,
|
|
77
|
+
paddingRight: boundVariables.paddingRight,
|
|
78
|
+
paddingBottom: boundVariables.paddingBottom,
|
|
79
|
+
paddingLeft: boundVariables.paddingLeft,
|
|
80
|
+
minWidth: boundVariables.minWidth,
|
|
81
|
+
maxWidth: boundVariables.maxWidth,
|
|
82
|
+
minHeight: boundVariables.minHeight,
|
|
83
|
+
maxHeight: boundVariables.maxHeight,
|
|
84
|
+
fontSize: boundVariables.fontSize,
|
|
85
|
+
fontWeight: boundVariables.fontWeight,
|
|
86
|
+
lineHeight: boundVariables.lineHeight,
|
|
87
|
+
size: {
|
|
88
|
+
x: boundVariables.width,
|
|
89
|
+
y: boundVariables.height,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function normalizeSolidPaint(paint: SolidPaint): NormalizedPaint {
|
|
95
|
+
return {
|
|
96
|
+
type: paint.type,
|
|
97
|
+
color: {
|
|
98
|
+
r: paint.color.r,
|
|
99
|
+
g: paint.color.g,
|
|
100
|
+
b: paint.color.b,
|
|
101
|
+
a: paint.opacity ?? 1,
|
|
102
|
+
},
|
|
103
|
+
visible: paint.visible,
|
|
104
|
+
blendMode: paint.blendMode ?? "NORMAL",
|
|
105
|
+
opacity: paint.opacity,
|
|
106
|
+
boundVariables: paint.boundVariables,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function normalizePaint(paint: Paint): NormalizedPaint {
|
|
111
|
+
switch (paint.type) {
|
|
112
|
+
case "SOLID":
|
|
113
|
+
return normalizeSolidPaint(paint);
|
|
114
|
+
case "IMAGE":
|
|
115
|
+
return {
|
|
116
|
+
type: "IMAGE",
|
|
117
|
+
scaleMode: paint.scaleMode === "CROP" ? "STRETCH" : paint.scaleMode,
|
|
118
|
+
imageTransform: paint.imageTransform,
|
|
119
|
+
scalingFactor: paint.scalingFactor,
|
|
120
|
+
filters: paint.filters,
|
|
121
|
+
rotation: paint.rotation,
|
|
122
|
+
imageRef: paint.imageHash ?? "",
|
|
123
|
+
blendMode: paint.blendMode ?? "NORMAL",
|
|
124
|
+
visible: paint.visible,
|
|
125
|
+
opacity: paint.opacity,
|
|
126
|
+
};
|
|
127
|
+
case "GRADIENT_LINEAR":
|
|
128
|
+
case "GRADIENT_RADIAL":
|
|
129
|
+
case "GRADIENT_ANGULAR":
|
|
130
|
+
case "GRADIENT_DIAMOND":
|
|
131
|
+
return {
|
|
132
|
+
type: paint.type,
|
|
133
|
+
gradientStops: [...paint.gradientStops],
|
|
134
|
+
visible: paint.visible,
|
|
135
|
+
opacity: paint.opacity,
|
|
136
|
+
blendMode: paint.blendMode ?? "NORMAL",
|
|
137
|
+
gradientHandlePositions: convertTransformToGradientHandles(paint.gradientTransform),
|
|
138
|
+
};
|
|
139
|
+
default:
|
|
140
|
+
throw new Error(`Unimplemented paint type: ${paint.type}`);
|
|
40
141
|
}
|
|
41
|
-
|
|
42
|
-
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function normalizePaints(fills: readonly Paint[] | PluginAPI["mixed"]): NormalizedPaint[] {
|
|
145
|
+
if (fills === figma.mixed) {
|
|
146
|
+
console.warn("Mixed fills are not supported");
|
|
147
|
+
|
|
148
|
+
return [];
|
|
43
149
|
}
|
|
44
150
|
|
|
151
|
+
return fills.map(normalizePaint);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function normalizeRadiusProps(
|
|
155
|
+
node: Pick<
|
|
156
|
+
RectangleNode,
|
|
157
|
+
"cornerRadius" | "topLeftRadius" | "topRightRadius" | "bottomRightRadius" | "bottomLeftRadius"
|
|
158
|
+
>,
|
|
159
|
+
): NormalizedCornerTrait {
|
|
45
160
|
return {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
161
|
+
cornerRadius: node.cornerRadius === figma.mixed ? undefined : node.cornerRadius,
|
|
162
|
+
rectangleCornerRadii: [
|
|
163
|
+
node.topLeftRadius,
|
|
164
|
+
node.topRightRadius,
|
|
165
|
+
node.bottomRightRadius,
|
|
166
|
+
node.bottomLeftRadius,
|
|
167
|
+
],
|
|
49
168
|
};
|
|
50
169
|
}
|
|
51
170
|
|
|
52
|
-
async function
|
|
171
|
+
async function normalizeEffectProps(
|
|
172
|
+
node: Pick<RectangleNode, "effects" | "effectStyleId">,
|
|
173
|
+
): Promise<NormalizedHasEffectsTrait> {
|
|
174
|
+
const effectStyleKey =
|
|
175
|
+
typeof node.effectStyleId === "string"
|
|
176
|
+
? (await figma.getStyleByIdAsync(node.effectStyleId))?.key
|
|
177
|
+
: undefined;
|
|
178
|
+
|
|
179
|
+
const effects = node.effects
|
|
180
|
+
.filter((effect): effect is DropShadowEffect | InnerShadowEffect => {
|
|
181
|
+
if (!effect.visible) return false;
|
|
182
|
+
|
|
183
|
+
return effect.type === "DROP_SHADOW" || effect.type === "INNER_SHADOW";
|
|
184
|
+
})
|
|
185
|
+
.map(({ blendMode, visible, ...rest }): NormalizedShadow => rest);
|
|
186
|
+
|
|
53
187
|
return {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
name: node.name,
|
|
57
|
-
boundVariables: await normalizeBoundVariables(node),
|
|
58
|
-
...normalizeRadiusProps(node),
|
|
59
|
-
...(await normalizeAutolayoutProps(node)),
|
|
60
|
-
children: await normalizeNodes(node.children),
|
|
188
|
+
...(effectStyleKey ? { effectStyleKey } : {}),
|
|
189
|
+
effects,
|
|
61
190
|
};
|
|
62
191
|
}
|
|
63
192
|
|
|
64
|
-
async function
|
|
65
|
-
node:
|
|
66
|
-
|
|
193
|
+
async function normalizeShapeProps(
|
|
194
|
+
node: Pick<
|
|
195
|
+
RectangleNode,
|
|
196
|
+
| "fills"
|
|
197
|
+
| "fillStyleId"
|
|
198
|
+
| "strokes"
|
|
199
|
+
| "strokeWeight"
|
|
200
|
+
| "layoutGrow"
|
|
201
|
+
| "layoutAlign"
|
|
202
|
+
| "layoutSizingHorizontal"
|
|
203
|
+
| "layoutSizingVertical"
|
|
204
|
+
| "absoluteBoundingBox"
|
|
205
|
+
| "relativeTransform"
|
|
206
|
+
| "layoutPositioning"
|
|
207
|
+
| "minHeight"
|
|
208
|
+
| "minWidth"
|
|
209
|
+
| "maxHeight"
|
|
210
|
+
| "maxWidth"
|
|
211
|
+
| "effects"
|
|
212
|
+
| "effectStyleId"
|
|
213
|
+
> &
|
|
214
|
+
Partial<Pick<FrameNode, "inferredAutoLayout">>,
|
|
215
|
+
): Promise<Omit<NormalizedDefaultShapeTrait, keyof NormalizedIsLayerTrait>> {
|
|
216
|
+
const fillStyleKey =
|
|
217
|
+
typeof node.fillStyleId === "string"
|
|
218
|
+
? (await figma.getStyleByIdAsync(node.fillStyleId))?.key
|
|
219
|
+
: undefined;
|
|
220
|
+
|
|
67
221
|
return {
|
|
68
|
-
|
|
69
|
-
id: node.id,
|
|
70
|
-
name: node.name,
|
|
71
|
-
boundVariables: await normalizeBoundVariables(node),
|
|
222
|
+
// NormalizedHasLayoutTrait
|
|
72
223
|
layoutGrow: (node.inferredAutoLayout?.layoutGrow ?? node.layoutGrow) as 0 | 1 | undefined,
|
|
73
224
|
layoutAlign: node.inferredAutoLayout?.layoutAlign ?? node.layoutAlign,
|
|
74
225
|
layoutSizingHorizontal: node.layoutSizingHorizontal,
|
|
75
226
|
layoutSizingVertical: node.layoutSizingVertical,
|
|
76
227
|
absoluteBoundingBox: node.absoluteBoundingBox,
|
|
77
228
|
relativeTransform: node.relativeTransform,
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
children: await normalizeNodes(node.children),
|
|
229
|
+
layoutPositioning: node.layoutPositioning,
|
|
230
|
+
minHeight: node.minHeight ?? undefined,
|
|
231
|
+
minWidth: node.minWidth ?? undefined,
|
|
232
|
+
maxHeight: node.maxHeight ?? undefined,
|
|
233
|
+
maxWidth: node.maxWidth ?? undefined,
|
|
234
|
+
|
|
235
|
+
// NormalizedHasGeometryTrait
|
|
236
|
+
fills: await normalizePaints(node.fills),
|
|
237
|
+
fillStyleKey,
|
|
238
|
+
strokes: await normalizePaints(node.strokes),
|
|
239
|
+
strokeWeight: node.strokeWeight === figma.mixed ? undefined : node.strokeWeight,
|
|
240
|
+
|
|
241
|
+
// NormalizedHasEffectsTrait
|
|
242
|
+
...(await normalizeEffectProps(node)),
|
|
93
243
|
};
|
|
94
244
|
}
|
|
95
245
|
|
|
96
|
-
async function
|
|
246
|
+
async function normalizeAutolayoutProps(
|
|
247
|
+
node: Omit<FrameNode, "type" | "clone">,
|
|
248
|
+
): Promise<NormalizedHasFramePropertiesTrait> {
|
|
97
249
|
return {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
250
|
+
layoutMode: node.inferredAutoLayout?.layoutMode ?? node.layoutMode,
|
|
251
|
+
layoutWrap: node.inferredAutoLayout?.layoutWrap ?? node.layoutWrap,
|
|
252
|
+
paddingLeft: node.inferredAutoLayout?.paddingLeft ?? node.paddingLeft,
|
|
253
|
+
paddingRight: node.inferredAutoLayout?.paddingRight ?? node.paddingRight,
|
|
254
|
+
paddingTop: node.inferredAutoLayout?.paddingTop ?? node.paddingTop,
|
|
255
|
+
paddingBottom: node.inferredAutoLayout?.paddingBottom ?? node.paddingBottom,
|
|
256
|
+
primaryAxisAlignItems:
|
|
257
|
+
node.inferredAutoLayout?.primaryAxisAlignItems ?? node.primaryAxisAlignItems,
|
|
258
|
+
counterAxisAlignItems:
|
|
259
|
+
node.inferredAutoLayout?.counterAxisAlignItems ?? node.counterAxisAlignItems,
|
|
260
|
+
primaryAxisSizingMode:
|
|
261
|
+
node.inferredAutoLayout?.primaryAxisSizingMode ?? node.primaryAxisSizingMode,
|
|
262
|
+
counterAxisSizingMode:
|
|
263
|
+
node.inferredAutoLayout?.counterAxisSizingMode ?? node.counterAxisSizingMode,
|
|
264
|
+
itemSpacing: node.inferredAutoLayout?.itemSpacing ?? node.itemSpacing,
|
|
265
|
+
counterAxisSpacing:
|
|
266
|
+
node.inferredAutoLayout?.counterAxisSpacing ?? node.counterAxisSpacing ?? undefined,
|
|
104
267
|
};
|
|
105
268
|
}
|
|
106
269
|
|
|
107
|
-
async function
|
|
270
|
+
async function normalizeFrameNode(node: FrameNode): Promise<NormalizedFrameNode> {
|
|
108
271
|
return {
|
|
272
|
+
// NormalizedIsLayerTrait
|
|
109
273
|
type: node.type,
|
|
110
274
|
id: node.id,
|
|
111
275
|
name: node.name,
|
|
112
|
-
boundVariables:
|
|
276
|
+
boundVariables: normalizeBoundVariables(node),
|
|
277
|
+
|
|
278
|
+
// NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait
|
|
113
279
|
...(await normalizeShapeProps(node)),
|
|
280
|
+
|
|
281
|
+
// NormalizedCornerTrait
|
|
282
|
+
...normalizeRadiusProps(node),
|
|
283
|
+
|
|
284
|
+
// NormalizedHasFramePropertiesTrait
|
|
285
|
+
...(await normalizeAutolayoutProps(node)),
|
|
286
|
+
|
|
287
|
+
// NormalizedHasChildrenTrait
|
|
288
|
+
children: await normalizeNodes(node.children),
|
|
114
289
|
};
|
|
115
290
|
}
|
|
116
291
|
|
|
117
|
-
async function
|
|
118
|
-
node: BooleanOperationNode,
|
|
119
|
-
): Promise<NormalizedBooleanOperationNode> {
|
|
292
|
+
async function normalizeRectangleNode(node: RectangleNode): Promise<NormalizedRectangleNode> {
|
|
120
293
|
return {
|
|
294
|
+
// NormalizedIsLayerTrait
|
|
121
295
|
type: node.type,
|
|
122
296
|
id: node.id,
|
|
123
297
|
name: node.name,
|
|
124
|
-
boundVariables:
|
|
125
|
-
|
|
298
|
+
boundVariables: normalizeBoundVariables(node),
|
|
299
|
+
|
|
300
|
+
// NormalizedCornerTrait
|
|
301
|
+
...normalizeRadiusProps(node),
|
|
302
|
+
|
|
303
|
+
// NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait
|
|
126
304
|
...(await normalizeShapeProps(node)),
|
|
127
305
|
};
|
|
128
306
|
}
|
|
307
|
+
|
|
129
308
|
async function normalizeTextNode(node: TextNode): Promise<NormalizedTextNode> {
|
|
130
309
|
const segments = node.getStyledTextSegments([
|
|
131
|
-
"fontSize",
|
|
132
|
-
"fontWeight",
|
|
133
310
|
"fontName",
|
|
311
|
+
"fontWeight",
|
|
312
|
+
"fontSize",
|
|
134
313
|
"letterSpacing",
|
|
135
314
|
"lineHeight",
|
|
136
315
|
"paragraphSpacing",
|
|
@@ -139,31 +318,74 @@ export function createPluginNormalizer() {
|
|
|
139
318
|
"boundVariables",
|
|
140
319
|
"textDecoration",
|
|
141
320
|
]);
|
|
142
|
-
const first = segments[0]
|
|
321
|
+
const first = segments[0];
|
|
143
322
|
|
|
144
323
|
const textStyleKey =
|
|
145
324
|
typeof node.textStyleId === "string"
|
|
146
325
|
? (await figma.getStyleByIdAsync(node.textStyleId))?.key
|
|
147
326
|
: undefined;
|
|
148
327
|
|
|
328
|
+
const normalizeLetterSpacing = (
|
|
329
|
+
letterSpacing: LetterSpacing,
|
|
330
|
+
fontSize: number,
|
|
331
|
+
): NormalizedTextSegment["style"]["letterSpacing"] => {
|
|
332
|
+
if (letterSpacing.unit === "PIXELS") return letterSpacing.value;
|
|
333
|
+
if (letterSpacing.unit === "PERCENT") return (fontSize * letterSpacing.value) / 100;
|
|
334
|
+
|
|
335
|
+
return undefined;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const normalizeLineHeight = (
|
|
339
|
+
lineHeight: LineHeight,
|
|
340
|
+
fontSize: number,
|
|
341
|
+
): NormalizedTextSegment["style"]["lineHeight"] => {
|
|
342
|
+
if (lineHeight.unit === "PIXELS") return lineHeight.value;
|
|
343
|
+
if (lineHeight.unit === "PERCENT") return (fontSize * lineHeight.value) / 100;
|
|
344
|
+
|
|
345
|
+
return undefined;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const isItalic = (fontName: FontName): boolean => {
|
|
349
|
+
// {
|
|
350
|
+
// family: "SF Mono",
|
|
351
|
+
// style: "Bold Italic"
|
|
352
|
+
// }
|
|
353
|
+
return fontName.style.toLowerCase().includes("italic");
|
|
354
|
+
};
|
|
355
|
+
|
|
149
356
|
return {
|
|
357
|
+
// NormalizedIsLayerTrait
|
|
150
358
|
type: node.type,
|
|
151
359
|
id: node.id,
|
|
152
360
|
name: node.name,
|
|
153
|
-
boundVariables:
|
|
361
|
+
boundVariables: normalizeBoundVariables(node),
|
|
362
|
+
|
|
363
|
+
// NormalizedTypePropertiesTrait
|
|
364
|
+
// NOTE: this normalization is incomplete compared to from-rest.ts normalizer
|
|
154
365
|
style: {
|
|
155
|
-
fontSize: first.fontSize,
|
|
156
|
-
fontWeight: first.fontWeight,
|
|
157
366
|
fontFamily: first.fontName.family,
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
367
|
+
fontPostScriptName: null,
|
|
368
|
+
fontStyle: first.fontName.style,
|
|
369
|
+
italic: isItalic(first.fontName),
|
|
370
|
+
fontWeight: first.fontWeight,
|
|
371
|
+
fontSize: first.fontSize,
|
|
163
372
|
textAlignHorizontal: node.textAlignHorizontal,
|
|
373
|
+
textAlignVertical: node.textAlignVertical,
|
|
374
|
+
letterSpacing: normalizeLetterSpacing(first.letterSpacing, first.fontSize),
|
|
375
|
+
paragraphSpacing: first.paragraphSpacing,
|
|
376
|
+
textDecoration: first.textDecoration,
|
|
377
|
+
lineHeightPx: normalizeLineHeight(first.lineHeight, first.fontSize),
|
|
378
|
+
lineHeightUnit:
|
|
379
|
+
first.lineHeight.unit === "PIXELS"
|
|
380
|
+
? "PIXELS"
|
|
381
|
+
: first.lineHeight.unit === "PERCENT"
|
|
382
|
+
? "FONT_SIZE_%"
|
|
383
|
+
: undefined,
|
|
384
|
+
boundVariables: first.boundVariables,
|
|
385
|
+
maxLines: node.maxLines ?? undefined,
|
|
164
386
|
},
|
|
165
|
-
...(textStyleKey ? { textStyleKey } : {}),
|
|
166
387
|
characters: node.characters,
|
|
388
|
+
textStyleKey,
|
|
167
389
|
segments: segments.map((segment) => ({
|
|
168
390
|
characters: segment.characters,
|
|
169
391
|
start: segment.start,
|
|
@@ -172,23 +394,36 @@ export function createPluginNormalizer() {
|
|
|
172
394
|
fontSize: segment.fontSize,
|
|
173
395
|
fontWeight: segment.fontWeight,
|
|
174
396
|
fontFamily: segment.fontName.family,
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
397
|
+
italic: isItalic(segment.fontName),
|
|
398
|
+
letterSpacing: normalizeLetterSpacing(segment.letterSpacing, segment.fontSize),
|
|
399
|
+
lineHeight: normalizeLineHeight(segment.lineHeight, segment.fontSize),
|
|
400
|
+
textDecoration: segment.textDecoration,
|
|
178
401
|
},
|
|
179
402
|
})),
|
|
403
|
+
|
|
404
|
+
// NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait
|
|
180
405
|
...(await normalizeShapeProps(node)),
|
|
181
406
|
};
|
|
182
407
|
}
|
|
183
408
|
|
|
184
409
|
async function normalizeComponentNode(node: ComponentNode): Promise<NormalizedComponentNode> {
|
|
185
410
|
return {
|
|
411
|
+
// NormalizedIsLayerTrait
|
|
186
412
|
type: node.type,
|
|
187
413
|
id: node.id,
|
|
188
414
|
name: node.name,
|
|
189
|
-
boundVariables:
|
|
415
|
+
boundVariables: normalizeBoundVariables(node),
|
|
416
|
+
|
|
417
|
+
// NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait
|
|
418
|
+
...(await normalizeShapeProps(node)),
|
|
419
|
+
|
|
420
|
+
// NormalizedCornerTrait
|
|
190
421
|
...normalizeRadiusProps(node),
|
|
422
|
+
|
|
423
|
+
// NormalizedHasFramePropertiesTrait
|
|
191
424
|
...(await normalizeAutolayoutProps(node)),
|
|
425
|
+
|
|
426
|
+
// NormalizedHasChildrenTrait
|
|
192
427
|
children: await normalizeNodes(node.children),
|
|
193
428
|
};
|
|
194
429
|
}
|
|
@@ -200,232 +435,166 @@ export function createPluginNormalizer() {
|
|
|
200
435
|
}
|
|
201
436
|
|
|
202
437
|
const componentProperties: NormalizedInstanceNode["componentProperties"] = {};
|
|
438
|
+
|
|
203
439
|
for (const [key, value] of Object.entries(node.componentProperties)) {
|
|
204
440
|
componentProperties[key] = value;
|
|
441
|
+
|
|
205
442
|
if (value.type === "INSTANCE_SWAP") {
|
|
206
|
-
|
|
443
|
+
// unless value.type === "BOOLEAN", value.value is string
|
|
444
|
+
const swappedComponent = (await figma.getNodeByIdAsync(
|
|
207
445
|
value.value as string,
|
|
208
446
|
)) as ComponentNode;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
447
|
+
|
|
448
|
+
if (swappedComponent) {
|
|
449
|
+
componentProperties[key].componentKey = swappedComponent.key;
|
|
450
|
+
|
|
451
|
+
if (swappedComponent.parent?.type === "COMPONENT_SET") {
|
|
452
|
+
componentProperties[key].componentSetKey = swappedComponent.parent.key;
|
|
213
453
|
}
|
|
214
454
|
}
|
|
215
455
|
}
|
|
216
456
|
}
|
|
217
457
|
|
|
218
458
|
return {
|
|
459
|
+
// NormalizedIsLayerTrait
|
|
219
460
|
type: node.type,
|
|
220
461
|
id: node.id,
|
|
221
462
|
name: node.name,
|
|
222
|
-
boundVariables:
|
|
463
|
+
boundVariables: normalizeBoundVariables(node),
|
|
464
|
+
|
|
465
|
+
// NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait
|
|
466
|
+
...(await normalizeShapeProps(node)),
|
|
467
|
+
|
|
468
|
+
// NormalizedCornerTrait
|
|
223
469
|
...normalizeRadiusProps(node),
|
|
470
|
+
|
|
471
|
+
// NormalizedHasFramePropertiesTrait
|
|
224
472
|
...(await normalizeAutolayoutProps(node)),
|
|
473
|
+
|
|
474
|
+
// NormalizedHasChildrenTrait
|
|
225
475
|
children: await normalizeNodes(node.children),
|
|
476
|
+
|
|
477
|
+
// NormalizedInstanceNode specific
|
|
478
|
+
componentProperties,
|
|
226
479
|
componentKey: mainComponent.key,
|
|
227
480
|
componentSetKey:
|
|
228
481
|
mainComponent.parent?.type === "COMPONENT_SET" ? mainComponent.parent.key : undefined,
|
|
229
|
-
componentProperties,
|
|
230
482
|
overrides: node.overrides,
|
|
231
483
|
};
|
|
232
484
|
}
|
|
233
485
|
|
|
234
|
-
function
|
|
486
|
+
async function normalizeVectorNode(node: VectorNode): Promise<NormalizedVectorNode> {
|
|
235
487
|
return {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
a: paint.opacity ?? 1,
|
|
242
|
-
},
|
|
243
|
-
visible: paint.visible,
|
|
244
|
-
blendMode: paint.blendMode ?? "NORMAL",
|
|
245
|
-
boundVariables: paint.boundVariables,
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function normalizePaint(paint: Paint): FigmaRestSpec.Paint {
|
|
250
|
-
switch (paint.type) {
|
|
251
|
-
case "SOLID":
|
|
252
|
-
return normalizeSolidPaint(paint);
|
|
253
|
-
case "IMAGE":
|
|
254
|
-
return {
|
|
255
|
-
type: "IMAGE",
|
|
256
|
-
scaleMode: paint.scaleMode === "CROP" ? "STRETCH" : paint.scaleMode,
|
|
257
|
-
imageTransform: paint.imageTransform,
|
|
258
|
-
scalingFactor: paint.scalingFactor,
|
|
259
|
-
filters: paint.filters,
|
|
260
|
-
rotation: paint.rotation,
|
|
261
|
-
imageRef: paint.imageHash ?? "",
|
|
262
|
-
blendMode: paint.blendMode ?? "NORMAL",
|
|
263
|
-
visible: paint.visible,
|
|
264
|
-
opacity: paint.opacity,
|
|
265
|
-
};
|
|
266
|
-
case "GRADIENT_LINEAR":
|
|
267
|
-
case "GRADIENT_RADIAL":
|
|
268
|
-
case "GRADIENT_ANGULAR":
|
|
269
|
-
case "GRADIENT_DIAMOND":
|
|
270
|
-
return {
|
|
271
|
-
type: paint.type,
|
|
272
|
-
gradientStops: [...paint.gradientStops],
|
|
273
|
-
visible: paint.visible,
|
|
274
|
-
opacity: paint.opacity,
|
|
275
|
-
blendMode: paint.blendMode ?? "NORMAL",
|
|
276
|
-
gradientHandlePositions: convertTransformToGradientHandles(paint.gradientTransform),
|
|
277
|
-
};
|
|
278
|
-
default:
|
|
279
|
-
throw new Error(`Unimplemented paint type: ${paint.type}`);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function normalizePaints(fills: readonly Paint[] | PluginAPI["mixed"]): FigmaRestSpec.Paint[] {
|
|
284
|
-
if (fills === figma.mixed) {
|
|
285
|
-
console.warn("Mixed fills are not supported");
|
|
286
|
-
|
|
287
|
-
return [];
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return fills.map(normalizePaint);
|
|
291
|
-
}
|
|
488
|
+
// NormalizedIsLayerTrait
|
|
489
|
+
type: node.type,
|
|
490
|
+
id: node.id,
|
|
491
|
+
name: node.name,
|
|
492
|
+
boundVariables: normalizeBoundVariables(node),
|
|
292
493
|
|
|
293
|
-
|
|
294
|
-
node: Pick<
|
|
295
|
-
RectangleNode,
|
|
296
|
-
"cornerRadius" | "topLeftRadius" | "topRightRadius" | "bottomRightRadius" | "bottomLeftRadius"
|
|
297
|
-
>,
|
|
298
|
-
) {
|
|
299
|
-
return {
|
|
494
|
+
// NormalizedCornerTrait
|
|
300
495
|
cornerRadius: node.cornerRadius === figma.mixed ? undefined : node.cornerRadius,
|
|
301
|
-
rectangleCornerRadii:
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
node.bottomLeftRadius,
|
|
306
|
-
],
|
|
496
|
+
rectangleCornerRadii: undefined, // VectorNode does not have individual corner radii
|
|
497
|
+
|
|
498
|
+
// NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait
|
|
499
|
+
...(await normalizeShapeProps(node)),
|
|
307
500
|
};
|
|
308
501
|
}
|
|
309
502
|
|
|
310
|
-
async function
|
|
311
|
-
node:
|
|
312
|
-
|
|
313
|
-
| "fills"
|
|
314
|
-
| "fillStyleId"
|
|
315
|
-
| "strokes"
|
|
316
|
-
| "strokeWeight"
|
|
317
|
-
| "layoutGrow"
|
|
318
|
-
| "layoutAlign"
|
|
319
|
-
| "layoutSizingHorizontal"
|
|
320
|
-
| "layoutSizingVertical"
|
|
321
|
-
| "absoluteBoundingBox"
|
|
322
|
-
| "relativeTransform"
|
|
323
|
-
| "minHeight"
|
|
324
|
-
| "minWidth"
|
|
325
|
-
| "maxHeight"
|
|
326
|
-
| "maxWidth"
|
|
327
|
-
> &
|
|
328
|
-
Partial<Pick<FrameNode, "inferredAutoLayout">>,
|
|
329
|
-
) {
|
|
503
|
+
async function normalizeBooleanOperationNode(
|
|
504
|
+
node: BooleanOperationNode,
|
|
505
|
+
): Promise<NormalizedBooleanOperationNode> {
|
|
330
506
|
const fillStyleKey =
|
|
331
507
|
typeof node.fillStyleId === "string"
|
|
332
508
|
? (await figma.getStyleByIdAsync(node.fillStyleId))?.key
|
|
333
509
|
: undefined;
|
|
334
510
|
|
|
335
511
|
return {
|
|
336
|
-
|
|
337
|
-
|
|
512
|
+
// NormalizedIsLayerTrait
|
|
513
|
+
type: node.type,
|
|
514
|
+
id: node.id,
|
|
515
|
+
name: node.name,
|
|
516
|
+
boundVariables: normalizeBoundVariables(node),
|
|
517
|
+
|
|
518
|
+
// NormalizedHasLayoutTrait
|
|
519
|
+
layoutGrow: node.layoutGrow as 0 | 1 | undefined,
|
|
520
|
+
layoutAlign: node.layoutAlign,
|
|
338
521
|
layoutSizingHorizontal: node.layoutSizingHorizontal,
|
|
339
522
|
layoutSizingVertical: node.layoutSizingVertical,
|
|
340
523
|
absoluteBoundingBox: node.absoluteBoundingBox,
|
|
341
524
|
relativeTransform: node.relativeTransform,
|
|
342
|
-
|
|
343
|
-
...(fillStyleKey ? { fillStyleKey } : {}),
|
|
344
|
-
strokes: normalizePaints(node.strokes),
|
|
345
|
-
strokeWeight: node.strokeWeight === figma.mixed ? undefined : node.strokeWeight,
|
|
525
|
+
layoutPositioning: node.layoutPositioning,
|
|
346
526
|
minHeight: node.minHeight ?? undefined,
|
|
347
527
|
minWidth: node.minWidth ?? undefined,
|
|
348
528
|
maxHeight: node.maxHeight ?? undefined,
|
|
349
529
|
maxWidth: node.maxWidth ?? undefined,
|
|
530
|
+
|
|
531
|
+
// NormalizedHasGeometryTrait
|
|
532
|
+
fills: await normalizePaints(node.fills),
|
|
533
|
+
fillStyleKey,
|
|
534
|
+
strokes: await normalizePaints(node.strokes),
|
|
535
|
+
strokeWeight: node.strokeWeight === figma.mixed ? undefined : node.strokeWeight,
|
|
536
|
+
|
|
537
|
+
// NormalizedHasEffectsTrait
|
|
538
|
+
...(await normalizeEffectProps(node)),
|
|
539
|
+
|
|
540
|
+
// NormalizedHasChildrenTrait
|
|
541
|
+
children: await normalizeNodes(node.children),
|
|
350
542
|
};
|
|
351
543
|
}
|
|
352
544
|
|
|
353
|
-
async function
|
|
545
|
+
async function normalizeGroupNodeAsFrameNode(
|
|
546
|
+
node: GroupNode & { inferredAutoLayout?: FrameNode["inferredAutoLayout"] },
|
|
547
|
+
): Promise<NormalizedFrameNode> {
|
|
354
548
|
return {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
paddingTop: node.inferredAutoLayout?.paddingTop ?? node.paddingTop,
|
|
361
|
-
paddingBottom: node.inferredAutoLayout?.paddingBottom ?? node.paddingBottom,
|
|
362
|
-
primaryAxisAlignItems:
|
|
363
|
-
node.inferredAutoLayout?.primaryAxisAlignItems ?? node.primaryAxisAlignItems,
|
|
364
|
-
counterAxisAlignItems:
|
|
365
|
-
node.inferredAutoLayout?.counterAxisAlignItems ?? node.counterAxisAlignItems,
|
|
366
|
-
primaryAxisSizingMode:
|
|
367
|
-
node.inferredAutoLayout?.primaryAxisSizingMode ?? node.primaryAxisSizingMode,
|
|
368
|
-
counterAxisSizingMode:
|
|
369
|
-
node.inferredAutoLayout?.counterAxisSizingMode ?? node.counterAxisSizingMode,
|
|
370
|
-
itemSpacing: node.inferredAutoLayout?.itemSpacing ?? node.itemSpacing,
|
|
371
|
-
counterAxisSpacing:
|
|
372
|
-
node.inferredAutoLayout?.counterAxisSpacing ?? node.counterAxisSpacing ?? undefined,
|
|
373
|
-
};
|
|
374
|
-
}
|
|
549
|
+
// NormalizedIsLayerTrait
|
|
550
|
+
type: "FRAME",
|
|
551
|
+
id: node.id,
|
|
552
|
+
name: node.name,
|
|
553
|
+
boundVariables: normalizeBoundVariables(node),
|
|
375
554
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
555
|
+
// NormalizedHasLayoutTrait
|
|
556
|
+
layoutGrow: (node.inferredAutoLayout?.layoutGrow ?? node.layoutGrow) as 0 | 1 | undefined,
|
|
557
|
+
layoutAlign: node.inferredAutoLayout?.layoutAlign ?? node.layoutAlign,
|
|
558
|
+
layoutSizingHorizontal: node.layoutSizingHorizontal,
|
|
559
|
+
layoutSizingVertical: node.layoutSizingVertical,
|
|
560
|
+
absoluteBoundingBox: node.absoluteBoundingBox,
|
|
561
|
+
relativeTransform: node.relativeTransform,
|
|
562
|
+
layoutPositioning: node.layoutPositioning,
|
|
563
|
+
minHeight: node.minHeight ?? undefined,
|
|
564
|
+
minWidth: node.minWidth ?? undefined,
|
|
565
|
+
maxHeight: node.maxHeight ?? undefined,
|
|
566
|
+
maxWidth: node.maxWidth ?? undefined,
|
|
380
567
|
|
|
381
|
-
|
|
568
|
+
// NormalizedHasGeometryTrait
|
|
569
|
+
fills: [],
|
|
570
|
+
fillStyleKey: undefined,
|
|
571
|
+
strokes: [],
|
|
572
|
+
strokeWeight: undefined,
|
|
382
573
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
id: (await figma.variables.getVariableByIdAsync(variable.id))?.key ?? variable.id,
|
|
387
|
-
});
|
|
574
|
+
// NormalizedHasEffectsTrait
|
|
575
|
+
effects: [],
|
|
576
|
+
effectStyleKey: undefined,
|
|
388
577
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
"counterAxisSpacing",
|
|
393
|
-
"bottomLeftRadius",
|
|
394
|
-
"bottomRightRadius",
|
|
395
|
-
"topLeftRadius",
|
|
396
|
-
"topRightRadius",
|
|
397
|
-
"paddingBottom",
|
|
398
|
-
"paddingLeft",
|
|
399
|
-
"paddingRight",
|
|
400
|
-
"paddingTop",
|
|
401
|
-
"maxHeight",
|
|
402
|
-
"minHeight",
|
|
403
|
-
"maxWidth",
|
|
404
|
-
"minWidth",
|
|
405
|
-
];
|
|
406
|
-
|
|
407
|
-
// Process all properties in parallel
|
|
408
|
-
const resolvedEntries = await Promise.all(
|
|
409
|
-
Object.entries(rest).map(async ([key, value]) => {
|
|
410
|
-
if (!value || !needsResolution.includes(key)) return [key, value];
|
|
411
|
-
|
|
412
|
-
if (Array.isArray(value)) {
|
|
413
|
-
return [key, await Promise.all(value.map(resolveVariableId))];
|
|
414
|
-
}
|
|
578
|
+
// NormalizedCornerTrait
|
|
579
|
+
cornerRadius: undefined,
|
|
580
|
+
rectangleCornerRadii: undefined,
|
|
415
581
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
582
|
+
// NormalizedHasFramePropertiesTrait
|
|
583
|
+
layoutMode: node.inferredAutoLayout?.layoutMode,
|
|
584
|
+
layoutWrap: node.inferredAutoLayout?.layoutWrap,
|
|
585
|
+
paddingLeft: node.inferredAutoLayout?.paddingLeft,
|
|
586
|
+
paddingRight: node.inferredAutoLayout?.paddingRight,
|
|
587
|
+
paddingTop: node.inferredAutoLayout?.paddingTop,
|
|
588
|
+
paddingBottom: node.inferredAutoLayout?.paddingBottom,
|
|
589
|
+
primaryAxisAlignItems: node.inferredAutoLayout?.primaryAxisAlignItems,
|
|
590
|
+
counterAxisAlignItems: node.inferredAutoLayout?.counterAxisAlignItems,
|
|
591
|
+
primaryAxisSizingMode: node.inferredAutoLayout?.primaryAxisSizingMode,
|
|
592
|
+
counterAxisSizingMode: node.inferredAutoLayout?.counterAxisSizingMode,
|
|
593
|
+
itemSpacing: node.inferredAutoLayout?.itemSpacing,
|
|
594
|
+
counterAxisSpacing: node.inferredAutoLayout?.counterAxisSpacing ?? undefined,
|
|
419
595
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
...(width &&
|
|
423
|
-
height && {
|
|
424
|
-
size: {
|
|
425
|
-
x: width,
|
|
426
|
-
y: height,
|
|
427
|
-
},
|
|
428
|
-
}),
|
|
596
|
+
// NormalizedHasChildrenTrait
|
|
597
|
+
children: await normalizeNodes(node.children),
|
|
429
598
|
};
|
|
430
599
|
}
|
|
431
600
|
|