@seed-design/figma 0.0.0-alpha-20260324091316

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 (181) hide show
  1. package/lib/codegen/index.cjs +23543 -0
  2. package/lib/codegen/index.d.ts +2957 -0
  3. package/lib/codegen/index.d.ts.map +1 -0
  4. package/lib/codegen/index.js +23514 -0
  5. package/lib/codegen/targets/react/index.cjs +31980 -0
  6. package/lib/codegen/targets/react/index.d.ts +308 -0
  7. package/lib/codegen/targets/react/index.d.ts.map +1 -0
  8. package/lib/codegen/targets/react/index.js +31961 -0
  9. package/lib/index.cjs +26905 -0
  10. package/lib/index.d.ts +221 -0
  11. package/lib/index.d.ts.map +1 -0
  12. package/lib/index.js +26884 -0
  13. package/package.json +56 -0
  14. package/src/codegen/component-properties.archive.ts +1019 -0
  15. package/src/codegen/component-properties.ts +369 -0
  16. package/src/codegen/core/codegen.ts +112 -0
  17. package/src/codegen/core/component-handler.ts +23 -0
  18. package/src/codegen/core/component-type-helper.ts +35 -0
  19. package/src/codegen/core/element-transformer.ts +13 -0
  20. package/src/codegen/core/index.ts +19 -0
  21. package/src/codegen/core/infer-layout.test.ts +286 -0
  22. package/src/codegen/core/infer-layout.ts +416 -0
  23. package/src/codegen/core/jsx.ts +174 -0
  24. package/src/codegen/core/props-converter.ts +78 -0
  25. package/src/codegen/core/value-resolver.ts +381 -0
  26. package/src/codegen/default-services.ts +44 -0
  27. package/src/codegen/index.ts +3 -0
  28. package/src/codegen/skip-components.ts +7 -0
  29. package/src/codegen/targets/figma/frame.ts +38 -0
  30. package/src/codegen/targets/figma/index.ts +6 -0
  31. package/src/codegen/targets/figma/instance.ts +36 -0
  32. package/src/codegen/targets/figma/pipeline.ts +106 -0
  33. package/src/codegen/targets/figma/props.ts +262 -0
  34. package/src/codegen/targets/figma/shape.ts +65 -0
  35. package/src/codegen/targets/figma/text.ts +30 -0
  36. package/src/codegen/targets/figma/value-resolver.ts +75 -0
  37. package/src/codegen/targets/index.ts +2 -0
  38. package/src/codegen/targets/react/component/deps.interface.ts +7 -0
  39. package/src/codegen/targets/react/component/handlers/action-button.ts +149 -0
  40. package/src/codegen/targets/react/component/handlers/alert-dialog.ts +120 -0
  41. package/src/codegen/targets/react/component/handlers/app-bar.ts +169 -0
  42. package/src/codegen/targets/react/component/handlers/archive/action-button.ts +144 -0
  43. package/src/codegen/targets/react/component/handlers/archive/alert-dialog.ts +122 -0
  44. package/src/codegen/targets/react/component/handlers/archive/app-bar.ts +149 -0
  45. package/src/codegen/targets/react/component/handlers/archive/avatar-stack.ts +35 -0
  46. package/src/codegen/targets/react/component/handlers/archive/avatar.ts +55 -0
  47. package/src/codegen/targets/react/component/handlers/archive/badge.ts +18 -0
  48. package/src/codegen/targets/react/component/handlers/archive/bottom-sheet.ts +70 -0
  49. package/src/codegen/targets/react/component/handlers/archive/callout.ts +88 -0
  50. package/src/codegen/targets/react/component/handlers/archive/checkbox.ts +43 -0
  51. package/src/codegen/targets/react/component/handlers/archive/checkmark.ts +29 -0
  52. package/src/codegen/targets/react/component/handlers/archive/chip.ts +90 -0
  53. package/src/codegen/targets/react/component/handlers/archive/contextual-floating-button.ts +52 -0
  54. package/src/codegen/targets/react/component/handlers/archive/divider.ts +25 -0
  55. package/src/codegen/targets/react/component/handlers/archive/field-button.ts +197 -0
  56. package/src/codegen/targets/react/component/handlers/archive/field.ts +167 -0
  57. package/src/codegen/targets/react/component/handlers/archive/floating-action-button.ts +48 -0
  58. package/src/codegen/targets/react/component/handlers/archive/help-bubble.ts +73 -0
  59. package/src/codegen/targets/react/component/handlers/archive/identity-placeholder.ts +21 -0
  60. package/src/codegen/targets/react/component/handlers/archive/index.ts +40 -0
  61. package/src/codegen/targets/react/component/handlers/archive/legacy-select-box.ts +89 -0
  62. package/src/codegen/targets/react/component/handlers/archive/legacy-text-field.ts +198 -0
  63. package/src/codegen/targets/react/component/handlers/archive/list-header.ts +20 -0
  64. package/src/codegen/targets/react/component/handlers/archive/list-item.ts +162 -0
  65. package/src/codegen/targets/react/component/handlers/archive/manner-temp-badge.ts +21 -0
  66. package/src/codegen/targets/react/component/handlers/archive/manner-temp.ts +18 -0
  67. package/src/codegen/targets/react/component/handlers/archive/menu-sheet.ts +108 -0
  68. package/src/codegen/targets/react/component/handlers/archive/page-banner.ts +101 -0
  69. package/src/codegen/targets/react/component/handlers/archive/progress-circle.ts +55 -0
  70. package/src/codegen/targets/react/component/handlers/archive/radio-group.ts +31 -0
  71. package/src/codegen/targets/react/component/handlers/archive/radiomark.ts +27 -0
  72. package/src/codegen/targets/react/component/handlers/archive/reaction-button.ts +37 -0
  73. package/src/codegen/targets/react/component/handlers/archive/result-section.ts +67 -0
  74. package/src/codegen/targets/react/component/handlers/archive/segmented-control.ts +64 -0
  75. package/src/codegen/targets/react/component/handlers/archive/skeleton.ts +26 -0
  76. package/src/codegen/targets/react/component/handlers/archive/slider.ts +114 -0
  77. package/src/codegen/targets/react/component/handlers/archive/snackbar.ts +25 -0
  78. package/src/codegen/targets/react/component/handlers/archive/switch.ts +39 -0
  79. package/src/codegen/targets/react/component/handlers/archive/switchmark.ts +26 -0
  80. package/src/codegen/targets/react/component/handlers/archive/tabs.ts +297 -0
  81. package/src/codegen/targets/react/component/handlers/archive/tag-group.ts +86 -0
  82. package/src/codegen/targets/react/component/handlers/archive/text-field.ts +264 -0
  83. package/src/codegen/targets/react/component/handlers/archive/toggle-button.ts +43 -0
  84. package/src/codegen/targets/react/component/handlers/avatar-stack.ts +38 -0
  85. package/src/codegen/targets/react/component/handlers/avatar.ts +58 -0
  86. package/src/codegen/targets/react/component/handlers/badge.ts +18 -0
  87. package/src/codegen/targets/react/component/handlers/bottom-sheet.ts +74 -0
  88. package/src/codegen/targets/react/component/handlers/callout.ts +88 -0
  89. package/src/codegen/targets/react/component/handlers/checkbox.ts +129 -0
  90. package/src/codegen/targets/react/component/handlers/checkmark.ts +29 -0
  91. package/src/codegen/targets/react/component/handlers/chip.ts +93 -0
  92. package/src/codegen/targets/react/component/handlers/content-placeholder.ts +20 -0
  93. package/src/codegen/targets/react/component/handlers/contextual-floating-button.ts +52 -0
  94. package/src/codegen/targets/react/component/handlers/divider.ts +25 -0
  95. package/src/codegen/targets/react/component/handlers/field-button.ts +192 -0
  96. package/src/codegen/targets/react/component/handlers/field.ts +164 -0
  97. package/src/codegen/targets/react/component/handlers/floating-action-button.ts +45 -0
  98. package/src/codegen/targets/react/component/handlers/help-bubble.ts +73 -0
  99. package/src/codegen/targets/react/component/handlers/identity-placeholder.ts +20 -0
  100. package/src/codegen/targets/react/component/handlers/image-frame.ts +147 -0
  101. package/src/codegen/targets/react/component/handlers/index.ts +43 -0
  102. package/src/codegen/targets/react/component/handlers/legacy-select-box.ts +87 -0
  103. package/src/codegen/targets/react/component/handlers/legacy-text-field.ts +196 -0
  104. package/src/codegen/targets/react/component/handlers/list-header.ts +20 -0
  105. package/src/codegen/targets/react/component/handlers/list-item.ts +163 -0
  106. package/src/codegen/targets/react/component/handlers/manner-temp-badge.ts +21 -0
  107. package/src/codegen/targets/react/component/handlers/manner-temp.ts +18 -0
  108. package/src/codegen/targets/react/component/handlers/menu-sheet.ts +111 -0
  109. package/src/codegen/targets/react/component/handlers/page-banner.ts +106 -0
  110. package/src/codegen/targets/react/component/handlers/progress-circle.ts +55 -0
  111. package/src/codegen/targets/react/component/handlers/radio-group.ts +109 -0
  112. package/src/codegen/targets/react/component/handlers/radiomark.ts +27 -0
  113. package/src/codegen/targets/react/component/handlers/reaction-button.ts +37 -0
  114. package/src/codegen/targets/react/component/handlers/result-section.ts +67 -0
  115. package/src/codegen/targets/react/component/handlers/segmented-control.ts +63 -0
  116. package/src/codegen/targets/react/component/handlers/select-box.ts +333 -0
  117. package/src/codegen/targets/react/component/handlers/skeleton.ts +26 -0
  118. package/src/codegen/targets/react/component/handlers/slider.ts +117 -0
  119. package/src/codegen/targets/react/component/handlers/snackbar.ts +25 -0
  120. package/src/codegen/targets/react/component/handlers/switch.ts +35 -0
  121. package/src/codegen/targets/react/component/handlers/switchmark.ts +26 -0
  122. package/src/codegen/targets/react/component/handlers/tabs.ts +298 -0
  123. package/src/codegen/targets/react/component/handlers/tag-group.ts +90 -0
  124. package/src/codegen/targets/react/component/handlers/text-field.ts +253 -0
  125. package/src/codegen/targets/react/component/handlers/toggle-button.ts +43 -0
  126. package/src/codegen/targets/react/component/index.ts +24 -0
  127. package/src/codegen/targets/react/component/size.ts +22 -0
  128. package/src/codegen/targets/react/element-factories.ts +59 -0
  129. package/src/codegen/targets/react/frame.ts +96 -0
  130. package/src/codegen/targets/react/icon.ts +55 -0
  131. package/src/codegen/targets/react/index.ts +7 -0
  132. package/src/codegen/targets/react/instance.ts +82 -0
  133. package/src/codegen/targets/react/pipeline.ts +133 -0
  134. package/src/codegen/targets/react/props.ts +417 -0
  135. package/src/codegen/targets/react/shape.ts +47 -0
  136. package/src/codegen/targets/react/text.ts +31 -0
  137. package/src/codegen/targets/react/value-resolver.ts +93 -0
  138. package/src/entities/component.interface.ts +7 -0
  139. package/src/entities/component.repository.ts +16 -0
  140. package/src/entities/data/__generated__/archive/component-sets/index.d.ts +2074 -0
  141. package/src/entities/data/__generated__/archive/component-sets/index.mjs +2074 -0
  142. package/src/entities/data/__generated__/archive/components/index.d.ts +116 -0
  143. package/src/entities/data/__generated__/archive/components/index.mjs +116 -0
  144. package/src/entities/data/__generated__/archive/styles/index.d.ts +3 -0
  145. package/src/entities/data/__generated__/archive/styles/index.mjs +429 -0
  146. package/src/entities/data/__generated__/archive/variable-collections/index.d.ts +3 -0
  147. package/src/entities/data/__generated__/archive/variable-collections/index.mjs +501 -0
  148. package/src/entities/data/__generated__/archive/variables/index.d.ts +3 -0
  149. package/src/entities/data/__generated__/archive/variables/index.mjs +7019 -0
  150. package/src/entities/data/__generated__/component-sets/index.d.ts +4325 -0
  151. package/src/entities/data/__generated__/component-sets/index.mjs +4325 -0
  152. package/src/entities/data/__generated__/components/index.d.ts +378 -0
  153. package/src/entities/data/__generated__/components/index.mjs +378 -0
  154. package/src/entities/data/__generated__/icons/index.d.ts +3 -0
  155. package/src/entities/data/__generated__/icons/index.mjs +3476 -0
  156. package/src/entities/data/__generated__/styles/index.d.ts +3 -0
  157. package/src/entities/data/__generated__/styles/index.mjs +436 -0
  158. package/src/entities/data/__generated__/variable-collections/index.d.ts +3 -0
  159. package/src/entities/data/__generated__/variable-collections/index.mjs +479 -0
  160. package/src/entities/data/__generated__/variables/index.d.ts +3 -0
  161. package/src/entities/data/__generated__/variables/index.mjs +6969 -0
  162. package/src/entities/icon.interface.ts +5 -0
  163. package/src/entities/icon.repository.ts +11 -0
  164. package/src/entities/icon.service.ts +26 -0
  165. package/src/entities/index.ts +60 -0
  166. package/src/entities/style.interface.ts +5 -0
  167. package/src/entities/style.repository.ts +27 -0
  168. package/src/entities/style.service.ts +36 -0
  169. package/src/entities/variable.interface.ts +18 -0
  170. package/src/entities/variable.repository.ts +57 -0
  171. package/src/entities/variable.service.ts +101 -0
  172. package/src/index.ts +3 -0
  173. package/src/normalizer/from-plugin.ts +602 -0
  174. package/src/normalizer/from-rest.ts +577 -0
  175. package/src/normalizer/index.ts +3 -0
  176. package/src/normalizer/types.ts +208 -0
  177. package/src/utils/common.ts +38 -0
  178. package/src/utils/css.ts +19 -0
  179. package/src/utils/figma-gradient.ts +72 -0
  180. package/src/utils/figma-node.ts +95 -0
  181. package/src/utils/figma-variable.ts +49 -0
@@ -0,0 +1,577 @@
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
+
12
+ import type * as FigmaRestSpec from "@figma/rest-api-spec";
13
+ import type {
14
+ NormalizedSceneNode,
15
+ NormalizedFrameNode,
16
+ NormalizedRectangleNode,
17
+ NormalizedTextNode,
18
+ NormalizedComponentNode,
19
+ NormalizedInstanceNode,
20
+ NormalizedTextSegment,
21
+ NormalizedVectorNode,
22
+ NormalizedBooleanOperationNode,
23
+ NormalizedShadow,
24
+ NormalizedCornerTrait,
25
+ NormalizedHasFramePropertiesTrait,
26
+ NormalizedPaint,
27
+ NormalizedDefaultShapeTrait,
28
+ NormalizedHasEffectsTrait,
29
+ NormalizedIsLayerTrait,
30
+ } from "./types";
31
+
32
+ export interface RestNormalizerContext {
33
+ /**
34
+ * A map of style **ID** to style data
35
+ */
36
+ styles: Record<string, FigmaRestSpec.Style>;
37
+ /**
38
+ * A map of component **ID** to component data
39
+ */
40
+ components: Record<string, FigmaRestSpec.Component>;
41
+ /**
42
+ * A map of component set **ID** to component set data
43
+ */
44
+ componentSets: Record<string, FigmaRestSpec.ComponentSet>;
45
+ }
46
+
47
+ export function createRestNormalizer(
48
+ ctx: RestNormalizerContext,
49
+ ): (node: FigmaRestSpec.Node) => NormalizedSceneNode {
50
+ function normalizeNodes(nodes: readonly FigmaRestSpec.Node[]): NormalizedSceneNode[] {
51
+ // Figma REST API omits default values for some fields, "visible" is one of them
52
+ return nodes.filter((node) => !("visible" in node) || node.visible).map(normalizeNode);
53
+ }
54
+
55
+ function normalizeNode(node: FigmaRestSpec.Node): NormalizedSceneNode {
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
+ };
79
+ }
80
+ }
81
+
82
+ function normalizeBoundVariables(
83
+ boundVariables: FigmaRestSpec.IsLayerTrait["boundVariables"] | undefined,
84
+ ) {
85
+ if (!boundVariables) return undefined;
86
+
87
+ return {
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,
108
+ };
109
+ }
110
+
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
+ // remove fallback when resolved: https://github.com/figma/rest-api-spec/issues/84
155
+ type: type ?? "INNER_SHADOW",
156
+ color,
157
+ offset,
158
+ radius,
159
+ spread,
160
+ boundVariables,
161
+ };
162
+ });
163
+
164
+ return {
165
+ effects,
166
+ effectStyleKey: node.styles?.["effect"] ? ctx.styles[node.styles["effect"]]?.key : undefined,
167
+ };
168
+ }
169
+
170
+ function normalizeShapeProps(
171
+ node: Pick<
172
+ FigmaRestSpec.FrameNode,
173
+ | "fills"
174
+ | "strokes"
175
+ | "strokeWeight"
176
+ | "styles"
177
+ | "layoutGrow"
178
+ | "layoutAlign"
179
+ | "layoutSizingHorizontal"
180
+ | "layoutSizingVertical"
181
+ | "absoluteBoundingBox"
182
+ | "relativeTransform"
183
+ | "layoutPositioning"
184
+ | "minHeight"
185
+ | "minWidth"
186
+ | "maxHeight"
187
+ | "maxWidth"
188
+ | "effects"
189
+ >,
190
+ ): Omit<NormalizedDefaultShapeTrait, keyof NormalizedIsLayerTrait> {
191
+ return {
192
+ // NormalizedHasLayoutTrait
193
+ layoutGrow: node.layoutGrow,
194
+ layoutAlign: node.layoutAlign,
195
+ layoutSizingHorizontal: node.layoutSizingHorizontal,
196
+ layoutSizingVertical: node.layoutSizingVertical,
197
+ absoluteBoundingBox: node.absoluteBoundingBox,
198
+ relativeTransform: node.relativeTransform,
199
+ layoutPositioning: node.layoutPositioning,
200
+ minHeight: node.minHeight,
201
+ minWidth: node.minWidth,
202
+ maxHeight: node.maxHeight,
203
+ maxWidth: node.maxWidth,
204
+
205
+ // NormalizedHasGeometryTrait
206
+ fills: normalizePaints(node.fills),
207
+ fillStyleKey: node.styles?.["fill"] ? ctx.styles[node.styles["fill"]]?.key : undefined,
208
+ strokes: normalizePaints(node.strokes),
209
+ strokeWeight: node.strokeWeight,
210
+
211
+ // NormalizedHasEffectsTrait
212
+ ...normalizeEffectProps(node),
213
+ };
214
+ }
215
+
216
+ function normalizeAutolayoutProps(
217
+ node: Pick<
218
+ FigmaRestSpec.FrameNode,
219
+ | "layoutMode"
220
+ | "layoutWrap"
221
+ | "paddingLeft"
222
+ | "paddingRight"
223
+ | "paddingTop"
224
+ | "paddingBottom"
225
+ | "primaryAxisAlignItems"
226
+ | "primaryAxisSizingMode"
227
+ | "counterAxisAlignItems"
228
+ | "counterAxisSizingMode"
229
+ | "itemSpacing"
230
+ | "counterAxisSpacing"
231
+ >,
232
+ ): NormalizedHasFramePropertiesTrait {
233
+ return {
234
+ layoutMode: node.layoutMode,
235
+ layoutWrap: node.layoutWrap,
236
+ paddingLeft: node.paddingLeft,
237
+ paddingRight: node.paddingRight,
238
+ paddingTop: node.paddingTop,
239
+ paddingBottom: node.paddingBottom,
240
+ primaryAxisAlignItems: node.primaryAxisAlignItems,
241
+ primaryAxisSizingMode: node.primaryAxisSizingMode,
242
+ counterAxisAlignItems: node.counterAxisAlignItems,
243
+ counterAxisSizingMode: node.counterAxisSizingMode,
244
+ itemSpacing: node.itemSpacing,
245
+ counterAxisSpacing: node.counterAxisSpacing,
246
+ };
247
+ }
248
+
249
+ function normalizeFrameNode(node: FigmaRestSpec.FrameNode): NormalizedFrameNode {
250
+ return {
251
+ // NormalizedIsLayerTrait
252
+ type: node.type,
253
+ id: node.id,
254
+ name: node.name,
255
+ boundVariables: normalizeBoundVariables(node.boundVariables),
256
+
257
+ // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait, NormalizedHasFramePropertiesTrait
258
+ ...normalizeShapeProps(node),
259
+
260
+ // NormalizedCornerTrait
261
+ ...normalizeRadiusProps(node),
262
+
263
+ // NormalizedHasFramePropertiesTrait
264
+ ...normalizeAutolayoutProps(node),
265
+
266
+ // NormalizedHasChildrenTrait
267
+ children: normalizeNodes(node.children),
268
+ };
269
+ }
270
+
271
+ function normalizeRectangleNode(node: FigmaRestSpec.RectangleNode): NormalizedRectangleNode {
272
+ return {
273
+ // NormalizedIsLayerTrait
274
+ type: node.type,
275
+ id: node.id,
276
+ name: node.name,
277
+ boundVariables: normalizeBoundVariables(node.boundVariables),
278
+
279
+ // NormalizedCornerTrait
280
+ ...normalizeRadiusProps(node),
281
+
282
+ // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait
283
+ ...normalizeShapeProps(node),
284
+ };
285
+ }
286
+
287
+ function normalizeTextNode(node: FigmaRestSpec.TextNode): NormalizedTextNode {
288
+ // Convert TypeStyle to NormalizedTextSegment.style format
289
+ function normalizeSegmentStyle(
290
+ typeStyle: FigmaRestSpec.TypeStyle,
291
+ ): NormalizedTextSegment["style"] {
292
+ return {
293
+ fontFamily: typeStyle.fontFamily,
294
+ fontWeight: typeStyle.fontWeight,
295
+ fontSize: typeStyle.fontSize,
296
+ italic: typeStyle.italic,
297
+ textDecoration: typeStyle.textDecoration,
298
+ letterSpacing: typeStyle.letterSpacing,
299
+ lineHeight: typeStyle.lineHeightPx,
300
+ };
301
+ }
302
+
303
+ // Function to segment a text node based on style overrides
304
+ function segmentTextNode(textNode: FigmaRestSpec.TextNode): NormalizedTextSegment[] {
305
+ const segments: NormalizedTextSegment[] = [];
306
+ const characters = textNode.characters;
307
+ const styleOverrides = textNode.characterStyleOverrides || [];
308
+ const styleTable = textNode.styleOverrideTable || {};
309
+
310
+ // If no style overrides, return the entire text as one segment
311
+ if (!styleOverrides.length) {
312
+ return [
313
+ {
314
+ characters: characters,
315
+ start: 0,
316
+ end: characters.length,
317
+ style: normalizeSegmentStyle(textNode.style),
318
+ },
319
+ ];
320
+ }
321
+
322
+ let currentSegment: NormalizedTextSegment = {
323
+ characters: "",
324
+ start: 0,
325
+ end: 0,
326
+ style: {},
327
+ };
328
+
329
+ let currentStyleId: string | null = null;
330
+
331
+ for (let i = 0; i < characters.length; i++) {
332
+ const styleId = styleOverrides[i]?.toString() || null;
333
+
334
+ // If style changes or it's the first character
335
+ if (styleId !== currentStyleId || i === 0) {
336
+ // Add the previous segment if it exists
337
+ if (i > 0) {
338
+ currentSegment.end = i;
339
+ currentSegment.characters = characters.substring(
340
+ currentSegment.start,
341
+ currentSegment.end,
342
+ );
343
+ segments.push({ ...currentSegment });
344
+ }
345
+
346
+ // Start a new segment
347
+ currentStyleId = styleId;
348
+ currentSegment = {
349
+ characters: "",
350
+ start: i,
351
+ end: 0,
352
+ style: styleId && styleTable[styleId] ? normalizeSegmentStyle(styleTable[styleId]) : {},
353
+ };
354
+ }
355
+ }
356
+
357
+ // Add the last segment
358
+ if (currentSegment.start < characters.length) {
359
+ currentSegment.end = characters.length;
360
+ currentSegment.characters = characters.substring(currentSegment.start, currentSegment.end);
361
+ segments.push(currentSegment);
362
+ }
363
+
364
+ return segments;
365
+ }
366
+
367
+ return {
368
+ // NormalizedIsLayerTrait
369
+ type: node.type,
370
+ id: node.id,
371
+ name: node.name,
372
+ boundVariables: normalizeBoundVariables(node.boundVariables),
373
+
374
+ // NormalizedTypePropertiesTrait
375
+ style: node.style, // this style is the style of the first segment
376
+ characters: node.characters,
377
+ textStyleKey: node.styles?.["text"] ? ctx.styles[node.styles["text"]]?.key : undefined,
378
+ segments: segmentTextNode(node),
379
+
380
+ // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait
381
+ ...normalizeShapeProps(node),
382
+ };
383
+ }
384
+
385
+ function normalizeComponentNode(node: FigmaRestSpec.ComponentNode): NormalizedComponentNode {
386
+ return {
387
+ // NormalizedIsLayerTrait
388
+ type: node.type,
389
+ id: node.id,
390
+ name: node.name,
391
+ boundVariables: normalizeBoundVariables(node.boundVariables),
392
+
393
+ // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait
394
+ ...normalizeShapeProps(node),
395
+
396
+ // NormalizedHasCornerTrait
397
+ ...normalizeRadiusProps(node),
398
+
399
+ // NormalizedHasFramePropertiesTrait
400
+ ...normalizeAutolayoutProps(node),
401
+
402
+ // NormalizedHasChildrenTrait
403
+ children: normalizeNodes(node.children),
404
+ };
405
+ }
406
+
407
+ function normalizeInstanceNode(node: FigmaRestSpec.InstanceNode): NormalizedInstanceNode {
408
+ const mainComponent = ctx.components[node.componentId];
409
+ if (!mainComponent) {
410
+ throw new Error(`Component ${node.componentId} not found`);
411
+ }
412
+
413
+ const componentSet = mainComponent.componentSetId
414
+ ? ctx.componentSets[mainComponent.componentSetId]
415
+ : undefined;
416
+
417
+ const componentProperties: NormalizedInstanceNode["componentProperties"] = {};
418
+
419
+ for (const [key, value] of Object.entries(node.componentProperties ?? {})) {
420
+ componentProperties[key] = value;
421
+
422
+ if (value.type === "INSTANCE_SWAP") {
423
+ // unless value.type === "BOOLEAN", value.value is string
424
+ const swappedComponent = ctx.components[value.value as string];
425
+
426
+ if (swappedComponent) {
427
+ componentProperties[key].componentKey = swappedComponent.key;
428
+
429
+ const swappedComponentSet = swappedComponent?.componentSetId
430
+ ? ctx.componentSets[swappedComponent.componentSetId]
431
+ : undefined;
432
+
433
+ if (swappedComponentSet) {
434
+ componentProperties[key].componentSetKey = swappedComponentSet.key;
435
+ }
436
+ }
437
+ }
438
+ }
439
+
440
+ return {
441
+ // NormalizedIsLayerTrait
442
+ type: node.type,
443
+ id: node.id,
444
+ name: node.name,
445
+ boundVariables: normalizeBoundVariables(node.boundVariables),
446
+
447
+ // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait
448
+ ...normalizeShapeProps(node),
449
+
450
+ // NormalizedCornerTrait
451
+ ...normalizeRadiusProps(node),
452
+
453
+ // NormalizedHasFramePropertiesTrait
454
+ ...normalizeAutolayoutProps(node),
455
+
456
+ // NormalizedHasChildrenTrait
457
+ children: normalizeNodes(node.children),
458
+
459
+ // NormalizedInstanceNode specific
460
+ componentProperties,
461
+ componentKey: mainComponent.key,
462
+ componentSetKey: componentSet?.key,
463
+ overrides: node.overrides,
464
+ };
465
+ }
466
+
467
+ function normalizeVectorNode(node: FigmaRestSpec.VectorNode): NormalizedVectorNode {
468
+ return {
469
+ // NormalizedIsLayerTrait
470
+ type: node.type,
471
+ id: node.id,
472
+ name: node.name,
473
+ boundVariables: normalizeBoundVariables(node.boundVariables),
474
+
475
+ // NormalizedCornerTrait
476
+ ...normalizeRadiusProps(node),
477
+
478
+ // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait
479
+ ...normalizeShapeProps(node),
480
+ };
481
+ }
482
+
483
+ function normalizeBooleanOperationNode(
484
+ node: FigmaRestSpec.BooleanOperationNode,
485
+ ): NormalizedBooleanOperationNode {
486
+ return {
487
+ // NormalizedIsLayerTrait
488
+ type: node.type,
489
+ id: node.id,
490
+ name: node.name,
491
+ boundVariables: normalizeBoundVariables(node.boundVariables),
492
+
493
+ // NormalizedHasLayoutTrait
494
+ layoutGrow: node.layoutGrow,
495
+ layoutAlign: node.layoutAlign,
496
+ layoutSizingHorizontal: node.layoutSizingHorizontal,
497
+ layoutSizingVertical: node.layoutSizingVertical,
498
+ absoluteBoundingBox: node.absoluteBoundingBox,
499
+ relativeTransform: node.relativeTransform,
500
+ layoutPositioning: node.layoutPositioning,
501
+ minHeight: node.minHeight,
502
+ minWidth: node.minWidth,
503
+ maxHeight: node.maxHeight,
504
+ maxWidth: node.maxWidth,
505
+
506
+ // NormalizedHasGeometryTrait
507
+ fills: normalizePaints(node.fills),
508
+ fillStyleKey: node.styles?.["fill"] ? ctx.styles[node.styles["fill"]]?.key : undefined,
509
+ strokes: normalizePaints(node.strokes),
510
+ strokeWeight: node.strokeWeight,
511
+
512
+ // NormalizedHasEffectsTrait
513
+ ...normalizeEffectProps(node),
514
+
515
+ // NormalizedHasChildrenTrait
516
+ children: normalizeNodes(node.children),
517
+ };
518
+ }
519
+
520
+ function normalizeGroupNodeAsFrameNode(node: FigmaRestSpec.GroupNode): NormalizedFrameNode {
521
+ return {
522
+ // NormalizedIsLayerTrait
523
+ type: "FRAME",
524
+ id: node.id,
525
+ name: node.name,
526
+ boundVariables: normalizeBoundVariables(node.boundVariables),
527
+
528
+ // NormalizedHasLayoutTrait
529
+ layoutGrow: node.layoutGrow,
530
+ layoutAlign: node.layoutAlign,
531
+ layoutSizingHorizontal: node.layoutSizingHorizontal,
532
+ layoutSizingVertical: node.layoutSizingVertical,
533
+ absoluteBoundingBox: node.absoluteBoundingBox,
534
+ relativeTransform: node.relativeTransform,
535
+ layoutPositioning: node.layoutPositioning,
536
+ minHeight: node.minHeight,
537
+ minWidth: node.minWidth,
538
+ maxHeight: node.maxHeight,
539
+ maxWidth: node.maxWidth,
540
+
541
+ // NormalizedHasGeometryTrait
542
+ fills: [],
543
+ fillStyleKey: undefined,
544
+ strokes: [],
545
+ strokeWeight: undefined,
546
+
547
+ // NormalizedHasEffectsTrait
548
+ effects: [],
549
+ effectStyleKey: undefined,
550
+
551
+ // NormalizedCornerTrait
552
+ cornerRadius: undefined,
553
+ rectangleCornerRadii: undefined,
554
+
555
+ // NormalizedHasFramePropertiesTrait
556
+ // these are undefined compared to from-plugin normalizer
557
+ // since inferredAutoLayout isn't available in REST API
558
+ layoutMode: undefined,
559
+ layoutWrap: undefined,
560
+ paddingLeft: undefined,
561
+ paddingRight: undefined,
562
+ paddingTop: undefined,
563
+ paddingBottom: undefined,
564
+ primaryAxisAlignItems: undefined,
565
+ primaryAxisSizingMode: undefined,
566
+ counterAxisAlignItems: undefined,
567
+ counterAxisSizingMode: undefined,
568
+ itemSpacing: undefined,
569
+ counterAxisSpacing: undefined,
570
+
571
+ // NormalizedHasChildrenTrait
572
+ children: normalizeNodes(node.children),
573
+ };
574
+ }
575
+
576
+ return normalizeNode;
577
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./types";
2
+ export { createRestNormalizer } from "./from-rest";
3
+ export { createPluginNormalizer } from "./from-plugin";