@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.
Files changed (43) hide show
  1. package/lib/codegen/index.cjs +636 -114
  2. package/lib/codegen/index.d.ts +136 -96
  3. package/lib/codegen/index.d.ts.map +1 -1
  4. package/lib/codegen/index.js +636 -114
  5. package/lib/codegen/targets/react/index.cjs +682 -134
  6. package/lib/codegen/targets/react/index.d.ts +31 -11
  7. package/lib/codegen/targets/react/index.d.ts.map +1 -1
  8. package/lib/codegen/targets/react/index.js +682 -135
  9. package/lib/index.cjs +1254 -433
  10. package/lib/index.d.ts +46 -10
  11. package/lib/index.d.ts.map +1 -1
  12. package/lib/index.js +1254 -433
  13. package/package.json +1 -1
  14. package/src/codegen/component-properties.ts +5 -5
  15. package/src/codegen/core/value-resolver.ts +49 -12
  16. package/src/codegen/targets/figma/frame.ts +1 -0
  17. package/src/codegen/targets/figma/pipeline.ts +5 -0
  18. package/src/codegen/targets/figma/props.ts +30 -1
  19. package/src/codegen/targets/figma/shape.ts +1 -0
  20. package/src/codegen/targets/figma/value-resolver.ts +20 -0
  21. package/src/codegen/targets/react/component/handlers/menu-sheet.ts +1 -1
  22. package/src/codegen/targets/react/component/handlers/page-banner.ts +2 -2
  23. package/src/codegen/targets/react/component/handlers/{radio-mark.ts → radiomark.ts} +4 -4
  24. package/src/codegen/targets/react/component/handlers/result-section.ts +1 -1
  25. package/src/codegen/targets/react/component/handlers/{switch-mark.ts → switchmark.ts} +4 -4
  26. package/src/codegen/targets/react/component/index.ts +4 -4
  27. package/src/codegen/targets/react/frame.ts +16 -2
  28. package/src/codegen/targets/react/pipeline.ts +6 -1
  29. package/src/codegen/targets/react/props.ts +26 -0
  30. package/src/codegen/targets/react/shape.ts +5 -1
  31. package/src/codegen/targets/react/value-resolver.ts +26 -0
  32. package/src/entities/data/__generated__/component-sets/index.d.ts +84 -89
  33. package/src/entities/data/__generated__/component-sets/index.mjs +84 -89
  34. package/src/entities/data/__generated__/components/index.d.ts +2 -2
  35. package/src/entities/data/__generated__/components/index.mjs +2 -2
  36. package/src/entities/data/__generated__/icons/index.mjs +14 -0
  37. package/src/entities/data/__generated__/styles/index.mjs +190 -1
  38. package/src/entities/data/__generated__/variable-collections/index.mjs +11 -1
  39. package/src/entities/data/__generated__/variables/index.mjs +280 -0
  40. package/src/normalizer/from-plugin.ts +427 -258
  41. package/src/normalizer/from-rest.ts +428 -58
  42. package/src/normalizer/types.ts +63 -10
  43. package/src/utils/figma-node.ts +15 -10
@@ -1,3 +1,14 @@
1
+ /**
2
+ * from-rest could be run outside of the Figma Plugin environment
3
+ * so we cannot use the Plugin API types directly e.g. getNodeByIdAsync
4
+ */
5
+
6
+ /**
7
+ * NOTE: types of MinimalFillsTrait["styles"] can be found here:
8
+ * https://developers.figma.com/docs/rest-api/component-types/#style-type
9
+ * Record<"text" | "fill" | "stroke" | "effect" | "grid", string>
10
+ */
11
+
1
12
  import type * as FigmaRestSpec from "@figma/rest-api-spec";
2
13
  import type {
3
14
  NormalizedSceneNode,
@@ -9,86 +20,285 @@ import type {
9
20
  NormalizedTextSegment,
10
21
  NormalizedVectorNode,
11
22
  NormalizedBooleanOperationNode,
23
+ NormalizedShadow,
24
+ NormalizedCornerTrait,
25
+ NormalizedHasFramePropertiesTrait,
26
+ NormalizedPaint,
27
+ NormalizedDefaultShapeTrait,
28
+ NormalizedHasEffectsTrait,
29
+ NormalizedIsLayerTrait,
12
30
  } from "./types";
13
31
 
14
32
  export interface RestNormalizerContext {
33
+ /**
34
+ * A map of style **ID** to style data
35
+ */
15
36
  styles: Record<string, FigmaRestSpec.Style>;
37
+ /**
38
+ * A map of component **ID** to component data
39
+ */
16
40
  components: Record<string, FigmaRestSpec.Component>;
41
+ /**
42
+ * A map of component set **ID** to component set data
43
+ */
17
44
  componentSets: Record<string, FigmaRestSpec.ComponentSet>;
18
45
  }
19
46
 
20
- export function createRestNormalizer(ctx: RestNormalizerContext) {
47
+ export function createRestNormalizer(
48
+ ctx: RestNormalizerContext,
49
+ ): (node: FigmaRestSpec.Node) => NormalizedSceneNode {
21
50
  function normalizeNodes(nodes: readonly FigmaRestSpec.Node[]): NormalizedSceneNode[] {
22
51
  // Figma REST API omits default values for some fields, "visible" is one of them
23
52
  return nodes.filter((node) => !("visible" in node) || node.visible).map(normalizeNode);
24
53
  }
25
54
 
26
55
  function normalizeNode(node: FigmaRestSpec.Node): NormalizedSceneNode {
27
- if (node.type === "FRAME") {
28
- return normalizeFrameNode(node);
29
- }
30
- if (node.type === "GROUP") {
31
- return normalizeGroupNode(node);
32
- }
33
- if (node.type === "RECTANGLE") {
34
- return normalizeRectangleNode(node);
35
- }
36
- if (node.type === "VECTOR") {
37
- return normalizeVectorNode(node);
38
- }
39
- if (node.type === "BOOLEAN_OPERATION") {
40
- return normalizeBooleanOperationNode(node);
41
- }
42
- if (node.type === "TEXT") {
43
- return normalizeTextNode(node);
44
- }
45
- if (node.type === "COMPONENT") {
46
- return normalizeComponentNode(node);
47
- }
48
- if (node.type === "INSTANCE") {
49
- return normalizeInstanceNode(node);
56
+ switch (node.type) {
57
+ case "FRAME":
58
+ return normalizeFrameNode(node);
59
+ case "RECTANGLE":
60
+ return normalizeRectangleNode(node);
61
+ case "TEXT":
62
+ return normalizeTextNode(node);
63
+ case "COMPONENT":
64
+ return normalizeComponentNode(node);
65
+ case "INSTANCE":
66
+ return normalizeInstanceNode(node);
67
+ case "VECTOR":
68
+ return normalizeVectorNode(node);
69
+ case "BOOLEAN_OPERATION":
70
+ return normalizeBooleanOperationNode(node);
71
+ case "GROUP":
72
+ return normalizeGroupNodeAsFrameNode(node);
73
+ default:
74
+ return {
75
+ type: "UNHANDLED",
76
+ id: node.id,
77
+ original: node,
78
+ };
50
79
  }
80
+ }
81
+
82
+ function normalizeBoundVariables(
83
+ boundVariables: FigmaRestSpec.IsLayerTrait["boundVariables"] | undefined,
84
+ ) {
85
+ if (!boundVariables) return undefined;
51
86
 
52
87
  return {
53
- type: "UNHANDLED",
54
- id: node.id,
55
- original: node,
88
+ fills: boundVariables.fills,
89
+ strokes: boundVariables.strokes,
90
+ itemSpacing: boundVariables.itemSpacing,
91
+ counterAxisSpacing: boundVariables.counterAxisSpacing,
92
+ topLeftRadius: boundVariables.topLeftRadius,
93
+ topRightRadius: boundVariables.topRightRadius,
94
+ bottomLeftRadius: boundVariables.bottomLeftRadius,
95
+ bottomRightRadius: boundVariables.bottomRightRadius,
96
+ paddingTop: boundVariables.paddingTop,
97
+ paddingRight: boundVariables.paddingRight,
98
+ paddingBottom: boundVariables.paddingBottom,
99
+ paddingLeft: boundVariables.paddingLeft,
100
+ minWidth: boundVariables.minWidth,
101
+ maxWidth: boundVariables.maxWidth,
102
+ minHeight: boundVariables.minHeight,
103
+ maxHeight: boundVariables.maxHeight,
104
+ fontSize: boundVariables.fontSize,
105
+ fontWeight: boundVariables.fontWeight,
106
+ lineHeight: boundVariables.lineHeight,
107
+ size: boundVariables.size,
56
108
  };
57
109
  }
58
110
 
59
- function normalizeFrameNode(node: FigmaRestSpec.FrameNode): NormalizedFrameNode {
111
+ function normalizePaint(paint: FigmaRestSpec.Paint): NormalizedPaint {
112
+ switch (paint.type) {
113
+ case "SOLID":
114
+ case "IMAGE":
115
+ case "GRADIENT_LINEAR":
116
+ case "GRADIENT_RADIAL":
117
+ case "GRADIENT_ANGULAR":
118
+ case "GRADIENT_DIAMOND":
119
+ return paint;
120
+ default:
121
+ throw new Error(`Unimplemented paint type: ${paint.type}`);
122
+ }
123
+ }
124
+
125
+ function normalizePaints(paints: FigmaRestSpec.Paint[] | undefined): NormalizedPaint[] {
126
+ if (!paints) return [];
127
+
128
+ return paints.map(normalizePaint);
129
+ }
130
+
131
+ function normalizeRadiusProps({
132
+ cornerRadius,
133
+ rectangleCornerRadii,
134
+ }: Pick<
135
+ FigmaRestSpec.RectangleNode,
136
+ "cornerRadius" | "rectangleCornerRadii"
137
+ >): NormalizedCornerTrait {
138
+ return { cornerRadius, rectangleCornerRadii };
139
+ }
140
+
141
+ function normalizeEffectProps(
142
+ node: Pick<FigmaRestSpec.FrameNode, "effects" | "styles">,
143
+ ): NormalizedHasEffectsTrait {
144
+ const effects = (node.effects ?? [])
145
+ .filter(
146
+ (effect): effect is FigmaRestSpec.DropShadowEffect | FigmaRestSpec.InnerShadowEffect =>
147
+ effect.visible !== false &&
148
+ (effect.type === "DROP_SHADOW" || effect.type === "INNER_SHADOW"),
149
+ )
150
+ .map((effect): NormalizedShadow => {
151
+ const { type, color, offset, radius, spread, boundVariables } = effect;
152
+
153
+ return {
154
+ type,
155
+ color,
156
+ offset,
157
+ radius,
158
+ spread,
159
+ boundVariables,
160
+ };
161
+ });
162
+
60
163
  return {
61
- ...node,
62
- children: normalizeNodes(node.children),
164
+ effects,
165
+ effectStyleKey: node.styles?.["effect"] ? ctx.styles[node.styles["effect"]]?.key : undefined,
63
166
  };
64
167
  }
65
168
 
66
- function normalizeGroupNode(node: FigmaRestSpec.GroupNode): NormalizedFrameNode {
169
+ function normalizeShapeProps(
170
+ node: Pick<
171
+ FigmaRestSpec.FrameNode,
172
+ | "fills"
173
+ | "strokes"
174
+ | "strokeWeight"
175
+ | "styles"
176
+ | "layoutGrow"
177
+ | "layoutAlign"
178
+ | "layoutSizingHorizontal"
179
+ | "layoutSizingVertical"
180
+ | "absoluteBoundingBox"
181
+ | "relativeTransform"
182
+ | "layoutPositioning"
183
+ | "minHeight"
184
+ | "minWidth"
185
+ | "maxHeight"
186
+ | "maxWidth"
187
+ | "effects"
188
+ >,
189
+ ): Omit<NormalizedDefaultShapeTrait, keyof NormalizedIsLayerTrait> {
67
190
  return {
68
- ...node,
69
- type: "FRAME",
70
- children: normalizeNodes(node.children),
191
+ // NormalizedHasLayoutTrait
192
+ layoutGrow: node.layoutGrow,
193
+ layoutAlign: node.layoutAlign,
194
+ layoutSizingHorizontal: node.layoutSizingHorizontal,
195
+ layoutSizingVertical: node.layoutSizingVertical,
196
+ absoluteBoundingBox: node.absoluteBoundingBox,
197
+ relativeTransform: node.relativeTransform,
198
+ layoutPositioning: node.layoutPositioning,
199
+ minHeight: node.minHeight,
200
+ minWidth: node.minWidth,
201
+ maxHeight: node.maxHeight,
202
+ maxWidth: node.maxWidth,
203
+
204
+ // NormalizedHasGeometryTrait
205
+ fills: normalizePaints(node.fills),
206
+ fillStyleKey: node.styles?.["fill"] ? ctx.styles[node.styles["fill"]]?.key : undefined,
207
+ strokes: normalizePaints(node.strokes),
208
+ strokeWeight: node.strokeWeight,
209
+
210
+ // NormalizedHasEffectsTrait
211
+ ...normalizeEffectProps(node),
71
212
  };
72
213
  }
73
214
 
74
- function normalizeRectangleNode(node: FigmaRestSpec.RectangleNode): NormalizedRectangleNode {
75
- return node;
215
+ function normalizeAutolayoutProps(
216
+ node: Pick<
217
+ FigmaRestSpec.FrameNode,
218
+ | "layoutMode"
219
+ | "layoutWrap"
220
+ | "paddingLeft"
221
+ | "paddingRight"
222
+ | "paddingTop"
223
+ | "paddingBottom"
224
+ | "primaryAxisAlignItems"
225
+ | "primaryAxisSizingMode"
226
+ | "counterAxisAlignItems"
227
+ | "counterAxisSizingMode"
228
+ | "itemSpacing"
229
+ | "counterAxisSpacing"
230
+ >,
231
+ ): NormalizedHasFramePropertiesTrait {
232
+ return {
233
+ layoutMode: node.layoutMode,
234
+ layoutWrap: node.layoutWrap,
235
+ paddingLeft: node.paddingLeft,
236
+ paddingRight: node.paddingRight,
237
+ paddingTop: node.paddingTop,
238
+ paddingBottom: node.paddingBottom,
239
+ primaryAxisAlignItems: node.primaryAxisAlignItems,
240
+ primaryAxisSizingMode: node.primaryAxisSizingMode,
241
+ counterAxisAlignItems: node.counterAxisAlignItems,
242
+ counterAxisSizingMode: node.counterAxisSizingMode,
243
+ itemSpacing: node.itemSpacing,
244
+ counterAxisSpacing: node.counterAxisSpacing,
245
+ };
76
246
  }
77
247
 
78
- function normalizeVectorNode(node: FigmaRestSpec.VectorNode): NormalizedVectorNode {
79
- return node;
248
+ function normalizeFrameNode(node: FigmaRestSpec.FrameNode): NormalizedFrameNode {
249
+ return {
250
+ // NormalizedIsLayerTrait
251
+ type: node.type,
252
+ id: node.id,
253
+ name: node.name,
254
+ boundVariables: normalizeBoundVariables(node.boundVariables),
255
+
256
+ // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait, NormalizedHasFramePropertiesTrait
257
+ ...normalizeShapeProps(node),
258
+
259
+ // NormalizedCornerTrait
260
+ ...normalizeRadiusProps(node),
261
+
262
+ // NormalizedHasFramePropertiesTrait
263
+ ...normalizeAutolayoutProps(node),
264
+
265
+ // NormalizedHasChildrenTrait
266
+ children: normalizeNodes(node.children),
267
+ };
80
268
  }
81
269
 
82
- function normalizeBooleanOperationNode(
83
- node: FigmaRestSpec.BooleanOperationNode,
84
- ): NormalizedBooleanOperationNode {
270
+ function normalizeRectangleNode(node: FigmaRestSpec.RectangleNode): NormalizedRectangleNode {
85
271
  return {
86
- ...node,
87
- children: normalizeNodes(node.children),
272
+ // NormalizedIsLayerTrait
273
+ type: node.type,
274
+ id: node.id,
275
+ name: node.name,
276
+ boundVariables: normalizeBoundVariables(node.boundVariables),
277
+
278
+ // NormalizedCornerTrait
279
+ ...normalizeRadiusProps(node),
280
+
281
+ // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait
282
+ ...normalizeShapeProps(node),
88
283
  };
89
284
  }
90
285
 
91
286
  function normalizeTextNode(node: FigmaRestSpec.TextNode): NormalizedTextNode {
287
+ // Convert TypeStyle to NormalizedTextSegment.style format
288
+ function normalizeSegmentStyle(
289
+ typeStyle: FigmaRestSpec.TypeStyle,
290
+ ): NormalizedTextSegment["style"] {
291
+ return {
292
+ fontFamily: typeStyle.fontFamily,
293
+ fontWeight: typeStyle.fontWeight,
294
+ fontSize: typeStyle.fontSize,
295
+ italic: typeStyle.italic,
296
+ textDecoration: typeStyle.textDecoration,
297
+ letterSpacing: typeStyle.letterSpacing,
298
+ lineHeight: typeStyle.lineHeightPx,
299
+ };
300
+ }
301
+
92
302
  // Function to segment a text node based on style overrides
93
303
  function segmentTextNode(textNode: FigmaRestSpec.TextNode): NormalizedTextSegment[] {
94
304
  const segments: NormalizedTextSegment[] = [];
@@ -103,7 +313,7 @@ export function createRestNormalizer(ctx: RestNormalizerContext) {
103
313
  characters: characters,
104
314
  start: 0,
105
315
  end: characters.length,
106
- style: textNode.style || {},
316
+ style: normalizeSegmentStyle(textNode.style),
107
317
  },
108
318
  ];
109
319
  }
@@ -138,7 +348,7 @@ export function createRestNormalizer(ctx: RestNormalizerContext) {
138
348
  characters: "",
139
349
  start: i,
140
350
  end: 0,
141
- style: styleId ? styleTable[styleId] || {} : {},
351
+ style: styleId ? normalizeSegmentStyle(styleTable[styleId]) : {},
142
352
  };
143
353
  }
144
354
  }
@@ -154,15 +364,41 @@ export function createRestNormalizer(ctx: RestNormalizerContext) {
154
364
  }
155
365
 
156
366
  return {
157
- ...node,
367
+ // NormalizedIsLayerTrait
368
+ type: node.type,
369
+ id: node.id,
370
+ name: node.name,
371
+ boundVariables: normalizeBoundVariables(node.boundVariables),
372
+
373
+ // NormalizedTypePropertiesTrait
374
+ style: node.style, // this style is the style of the first segment
375
+ characters: node.characters,
158
376
  textStyleKey: node.styles?.["text"] ? ctx.styles[node.styles["text"]]?.key : undefined,
159
377
  segments: segmentTextNode(node),
378
+
379
+ // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait
380
+ ...normalizeShapeProps(node),
160
381
  };
161
382
  }
162
383
 
163
384
  function normalizeComponentNode(node: FigmaRestSpec.ComponentNode): NormalizedComponentNode {
164
385
  return {
165
- ...node,
386
+ // NormalizedIsLayerTrait
387
+ type: node.type,
388
+ id: node.id,
389
+ name: node.name,
390
+ boundVariables: normalizeBoundVariables(node.boundVariables),
391
+
392
+ // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait
393
+ ...normalizeShapeProps(node),
394
+
395
+ // NormalizedHasCornerTrait
396
+ ...normalizeRadiusProps(node),
397
+
398
+ // NormalizedHasFramePropertiesTrait
399
+ ...normalizeAutolayoutProps(node),
400
+
401
+ // NormalizedHasChildrenTrait
166
402
  children: normalizeNodes(node.children),
167
403
  };
168
404
  }
@@ -172,33 +408,167 @@ export function createRestNormalizer(ctx: RestNormalizerContext) {
172
408
  if (!mainComponent) {
173
409
  throw new Error(`Component ${node.componentId} not found`);
174
410
  }
411
+
175
412
  const componentSet = mainComponent.componentSetId
176
413
  ? ctx.componentSets[mainComponent.componentSetId]
177
414
  : undefined;
415
+
178
416
  const componentProperties: NormalizedInstanceNode["componentProperties"] = {};
179
417
 
180
418
  for (const [key, value] of Object.entries(node.componentProperties ?? {})) {
181
419
  componentProperties[key] = value;
420
+
182
421
  if (value.type === "INSTANCE_SWAP") {
183
- const mainComponent = ctx.components[value.value as string];
184
- if (mainComponent) {
185
- componentProperties[key].componentKey = mainComponent.key;
186
- }
187
- const mainComponentSet = mainComponent?.componentSetId
188
- ? ctx.componentSets[mainComponent.componentSetId]
189
- : undefined;
190
- if (mainComponentSet) {
191
- componentProperties[key].componentSetKey = mainComponentSet.key;
422
+ // unless value.type === "BOOLEAN", value.value is string
423
+ const swappedComponent = ctx.components[value.value as string];
424
+
425
+ if (swappedComponent) {
426
+ componentProperties[key].componentKey = swappedComponent.key;
427
+
428
+ const swappedComponentSet = swappedComponent?.componentSetId
429
+ ? ctx.componentSets[swappedComponent.componentSetId]
430
+ : undefined;
431
+
432
+ if (swappedComponentSet) {
433
+ componentProperties[key].componentSetKey = swappedComponentSet.key;
434
+ }
192
435
  }
193
436
  }
194
437
  }
195
438
 
196
439
  return {
197
- ...node,
440
+ // NormalizedIsLayerTrait
441
+ type: node.type,
442
+ id: node.id,
443
+ name: node.name,
444
+ boundVariables: normalizeBoundVariables(node.boundVariables),
445
+
446
+ // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait
447
+ ...normalizeShapeProps(node),
448
+
449
+ // NormalizedCornerTrait
450
+ ...normalizeRadiusProps(node),
451
+
452
+ // NormalizedHasFramePropertiesTrait
453
+ ...normalizeAutolayoutProps(node),
454
+
455
+ // NormalizedHasChildrenTrait
198
456
  children: normalizeNodes(node.children),
457
+
458
+ // NormalizedInstanceNode specific
459
+ componentProperties,
199
460
  componentKey: mainComponent.key,
200
461
  componentSetKey: componentSet?.key,
201
- componentProperties,
462
+ overrides: node.overrides,
463
+ };
464
+ }
465
+
466
+ function normalizeVectorNode(node: FigmaRestSpec.VectorNode): NormalizedVectorNode {
467
+ return {
468
+ // NormalizedIsLayerTrait
469
+ type: node.type,
470
+ id: node.id,
471
+ name: node.name,
472
+ boundVariables: normalizeBoundVariables(node.boundVariables),
473
+
474
+ // NormalizedCornerTrait
475
+ ...normalizeRadiusProps(node),
476
+
477
+ // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait
478
+ ...normalizeShapeProps(node),
479
+ };
480
+ }
481
+
482
+ function normalizeBooleanOperationNode(
483
+ node: FigmaRestSpec.BooleanOperationNode,
484
+ ): NormalizedBooleanOperationNode {
485
+ return {
486
+ // NormalizedIsLayerTrait
487
+ type: node.type,
488
+ id: node.id,
489
+ name: node.name,
490
+ boundVariables: normalizeBoundVariables(node.boundVariables),
491
+
492
+ // NormalizedHasLayoutTrait
493
+ layoutGrow: node.layoutGrow,
494
+ layoutAlign: node.layoutAlign,
495
+ layoutSizingHorizontal: node.layoutSizingHorizontal,
496
+ layoutSizingVertical: node.layoutSizingVertical,
497
+ absoluteBoundingBox: node.absoluteBoundingBox,
498
+ relativeTransform: node.relativeTransform,
499
+ layoutPositioning: node.layoutPositioning,
500
+ minHeight: node.minHeight,
501
+ minWidth: node.minWidth,
502
+ maxHeight: node.maxHeight,
503
+ maxWidth: node.maxWidth,
504
+
505
+ // NormalizedHasGeometryTrait
506
+ fills: normalizePaints(node.fills),
507
+ fillStyleKey: node.styles?.["fill"] ? ctx.styles[node.styles["fill"]]?.key : undefined,
508
+ strokes: normalizePaints(node.strokes),
509
+ strokeWeight: node.strokeWeight,
510
+
511
+ // NormalizedHasEffectsTrait
512
+ ...normalizeEffectProps(node),
513
+
514
+ // NormalizedHasChildrenTrait
515
+ children: normalizeNodes(node.children),
516
+ };
517
+ }
518
+
519
+ function normalizeGroupNodeAsFrameNode(node: FigmaRestSpec.GroupNode): NormalizedFrameNode {
520
+ return {
521
+ // NormalizedIsLayerTrait
522
+ type: "FRAME",
523
+ id: node.id,
524
+ name: node.name,
525
+ boundVariables: normalizeBoundVariables(node.boundVariables),
526
+
527
+ // NormalizedHasLayoutTrait
528
+ layoutGrow: node.layoutGrow,
529
+ layoutAlign: node.layoutAlign,
530
+ layoutSizingHorizontal: node.layoutSizingHorizontal,
531
+ layoutSizingVertical: node.layoutSizingVertical,
532
+ absoluteBoundingBox: node.absoluteBoundingBox,
533
+ relativeTransform: node.relativeTransform,
534
+ layoutPositioning: node.layoutPositioning,
535
+ minHeight: node.minHeight,
536
+ minWidth: node.minWidth,
537
+ maxHeight: node.maxHeight,
538
+ maxWidth: node.maxWidth,
539
+
540
+ // NormalizedHasGeometryTrait
541
+ fills: [],
542
+ fillStyleKey: undefined,
543
+ strokes: [],
544
+ strokeWeight: undefined,
545
+
546
+ // NormalizedHasEffectsTrait
547
+ effects: [],
548
+ effectStyleKey: undefined,
549
+
550
+ // NormalizedCornerTrait
551
+ cornerRadius: undefined,
552
+ rectangleCornerRadii: undefined,
553
+
554
+ // NormalizedHasFramePropertiesTrait
555
+ // these are undefined compared to from-plugin normalizer
556
+ // since inferredAutoLayout isn't available in REST API
557
+ layoutMode: undefined,
558
+ layoutWrap: undefined,
559
+ paddingLeft: undefined,
560
+ paddingRight: undefined,
561
+ paddingTop: undefined,
562
+ paddingBottom: undefined,
563
+ primaryAxisAlignItems: undefined,
564
+ primaryAxisSizingMode: undefined,
565
+ counterAxisAlignItems: undefined,
566
+ counterAxisSizingMode: undefined,
567
+ itemSpacing: undefined,
568
+ counterAxisSpacing: undefined,
569
+
570
+ // NormalizedHasChildrenTrait
571
+ children: normalizeNodes(node.children),
202
572
  };
203
573
  }
204
574
 
@@ -1,9 +1,30 @@
1
1
  import type * as FigmaRestSpec from "@figma/rest-api-spec";
2
2
 
3
- export type NormalizedIsLayerTrait = Pick<
4
- FigmaRestSpec.IsLayerTrait,
5
- "type" | "id" | "name" | "boundVariables"
6
- >;
3
+ export type NormalizedIsLayerTrait = Pick<FigmaRestSpec.IsLayerTrait, "type" | "id" | "name"> & {
4
+ boundVariables?: Pick<
5
+ NonNullable<FigmaRestSpec.IsLayerTrait["boundVariables"]>,
6
+ | "fills"
7
+ | "strokes"
8
+ | "itemSpacing"
9
+ | "counterAxisSpacing"
10
+ | "bottomLeftRadius"
11
+ | "bottomRightRadius"
12
+ | "topLeftRadius"
13
+ | "topRightRadius"
14
+ | "paddingBottom"
15
+ | "paddingLeft"
16
+ | "paddingRight"
17
+ | "paddingTop"
18
+ | "maxHeight"
19
+ | "minHeight"
20
+ | "maxWidth"
21
+ | "minWidth"
22
+ | "fontSize"
23
+ | "fontWeight"
24
+ | "lineHeight"
25
+ | "size"
26
+ >;
27
+ };
7
28
 
8
29
  export type NormalizedCornerTrait = Pick<
9
30
  FigmaRestSpec.CornerTrait,
@@ -29,13 +50,39 @@ export type NormalizedHasLayoutTrait = Pick<
29
50
  | "maxWidth"
30
51
  >;
31
52
 
32
- export type NormalizedHasGeometryTrait = Pick<
33
- FigmaRestSpec.HasGeometryTrait,
34
- "fills" | "strokes" | "strokeWeight" | "styles"
53
+ export type NormalizedSolidPaint = FigmaRestSpec.SolidPaint;
54
+
55
+ export type NormalizedPaint =
56
+ | NormalizedSolidPaint
57
+ | FigmaRestSpec.GradientPaint
58
+ | FigmaRestSpec.ImagePaint;
59
+
60
+ export type NormalizedHasGeometryTrait = Omit<
61
+ Pick<FigmaRestSpec.HasGeometryTrait, "fills" | "strokes" | "strokeWeight">,
62
+ "fills" | "strokes"
35
63
  > & {
64
+ fills: NormalizedPaint[];
65
+ strokes: NormalizedPaint[];
36
66
  fillStyleKey?: string;
37
67
  };
38
68
 
69
+ export type NormalizedShadow =
70
+ | (Pick<
71
+ FigmaRestSpec.DropShadowEffect,
72
+ "color" | "offset" | "radius" | "spread" | "boundVariables"
73
+ > &
74
+ Required<Pick<FigmaRestSpec.DropShadowEffect, "type">>)
75
+ | (Pick<
76
+ FigmaRestSpec.InnerShadowEffect,
77
+ "color" | "offset" | "radius" | "spread" | "boundVariables"
78
+ > &
79
+ Required<Pick<FigmaRestSpec.InnerShadowEffect, "type">>);
80
+
81
+ export type NormalizedHasEffectsTrait = Omit<FigmaRestSpec.HasEffectsTrait, "effects"> & {
82
+ effects: NormalizedShadow[];
83
+ effectStyleKey?: string;
84
+ };
85
+
39
86
  export type NormalizedHasFramePropertiesTrait = Pick<
40
87
  FigmaRestSpec.HasFramePropertiesTrait,
41
88
  | "layoutMode"
@@ -63,7 +110,10 @@ export interface NormalizedTextSegment {
63
110
  italic?: boolean;
64
111
  textDecoration?: string;
65
112
  letterSpacing?: number;
66
- lineHeight?: number | { unit: string; value: number };
113
+ /**
114
+ * in pixels
115
+ */
116
+ lineHeight?: number;
67
117
  };
68
118
  }
69
119
 
@@ -78,11 +128,13 @@ export type NormalizedTypePropertiesTrait = Pick<
78
128
 
79
129
  export type NormalizedDefaultShapeTrait = NormalizedIsLayerTrait &
80
130
  NormalizedHasLayoutTrait &
81
- NormalizedHasGeometryTrait;
131
+ NormalizedHasGeometryTrait &
132
+ NormalizedHasEffectsTrait;
82
133
 
83
134
  export type NormalizedFrameTrait = NormalizedIsLayerTrait &
84
135
  NormalizedHasLayoutTrait &
85
136
  NormalizedHasGeometryTrait &
137
+ NormalizedHasEffectsTrait &
86
138
  NormalizedHasChildrenTrait &
87
139
  NormalizedCornerTrait &
88
140
  NormalizedHasFramePropertiesTrait;
@@ -134,7 +186,8 @@ export interface NormalizedBooleanOperationNode
134
186
  extends NormalizedIsLayerTrait,
135
187
  NormalizedHasChildrenTrait,
136
188
  NormalizedHasLayoutTrait,
137
- NormalizedHasGeometryTrait {
189
+ NormalizedHasGeometryTrait,
190
+ NormalizedHasEffectsTrait {
138
191
  type: FigmaRestSpec.BooleanOperationNode["type"];
139
192
  }
140
193