@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,208 @@
1
+ import type * as FigmaRestSpec from "@figma/rest-api-spec";
2
+
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
+ };
28
+
29
+ export type NormalizedCornerTrait = Pick<
30
+ FigmaRestSpec.CornerTrait,
31
+ "cornerRadius" | "rectangleCornerRadii"
32
+ >;
33
+
34
+ export type NormalizedHasChildrenTrait = {
35
+ children: NormalizedSceneNode[];
36
+ };
37
+
38
+ export type NormalizedHasLayoutTrait = Pick<
39
+ FigmaRestSpec.HasLayoutTrait,
40
+ | "layoutAlign"
41
+ | "layoutGrow"
42
+ | "absoluteBoundingBox"
43
+ | "relativeTransform"
44
+ | "layoutPositioning"
45
+ | "layoutSizingHorizontal"
46
+ | "layoutSizingVertical"
47
+ | "minHeight"
48
+ | "minWidth"
49
+ | "maxHeight"
50
+ | "maxWidth"
51
+ >;
52
+
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"
63
+ > & {
64
+ fills: NormalizedPaint[];
65
+ strokes: NormalizedPaint[];
66
+ fillStyleKey?: string;
67
+ };
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
+
86
+ export type NormalizedHasFramePropertiesTrait = Pick<
87
+ FigmaRestSpec.HasFramePropertiesTrait,
88
+ | "layoutMode"
89
+ | "layoutWrap"
90
+ | "paddingLeft"
91
+ | "paddingRight"
92
+ | "paddingTop"
93
+ | "paddingBottom"
94
+ | "primaryAxisAlignItems"
95
+ | "primaryAxisSizingMode"
96
+ | "counterAxisAlignItems"
97
+ | "counterAxisSizingMode"
98
+ | "itemSpacing"
99
+ | "counterAxisSpacing"
100
+ >;
101
+
102
+ export interface NormalizedTextSegment {
103
+ characters: string;
104
+ start: number;
105
+ end: number;
106
+ style: {
107
+ fontFamily?: string;
108
+ fontWeight?: number;
109
+ fontSize?: number;
110
+ italic?: boolean;
111
+ textDecoration?: string;
112
+ letterSpacing?: number;
113
+ /**
114
+ * in pixels
115
+ */
116
+ lineHeight?: number;
117
+ };
118
+ }
119
+
120
+ export type NormalizedTypePropertiesTrait = Pick<
121
+ FigmaRestSpec.TypePropertiesTrait,
122
+ "style" | "characters"
123
+ > & {
124
+ segments: NormalizedTextSegment[];
125
+
126
+ textStyleKey?: string;
127
+ };
128
+
129
+ export type NormalizedDefaultShapeTrait = NormalizedIsLayerTrait &
130
+ NormalizedHasLayoutTrait &
131
+ NormalizedHasGeometryTrait &
132
+ NormalizedHasEffectsTrait;
133
+
134
+ export type NormalizedFrameTrait = NormalizedIsLayerTrait &
135
+ NormalizedHasLayoutTrait &
136
+ NormalizedHasGeometryTrait &
137
+ NormalizedHasEffectsTrait &
138
+ NormalizedHasChildrenTrait &
139
+ NormalizedCornerTrait &
140
+ NormalizedHasFramePropertiesTrait;
141
+
142
+ export interface NormalizedFrameNode extends NormalizedFrameTrait {
143
+ type: FigmaRestSpec.FrameNode["type"];
144
+ }
145
+
146
+ export interface NormalizedRectangleNode
147
+ extends NormalizedDefaultShapeTrait,
148
+ NormalizedCornerTrait {
149
+ type: FigmaRestSpec.RectangleNode["type"];
150
+ }
151
+
152
+ export interface NormalizedTextNode
153
+ extends NormalizedDefaultShapeTrait,
154
+ NormalizedTypePropertiesTrait {
155
+ type: FigmaRestSpec.TextNode["type"];
156
+ }
157
+
158
+ export interface NormalizedComponentNode extends NormalizedFrameTrait {
159
+ type: FigmaRestSpec.ComponentNode["type"];
160
+ }
161
+
162
+ export interface NormalizedInstanceNode extends NormalizedFrameTrait {
163
+ type: FigmaRestSpec.InstanceNode["type"];
164
+
165
+ componentProperties: {
166
+ [key: string]: FigmaRestSpec.ComponentProperty & {
167
+ componentKey?: string;
168
+ componentSetKey?: string;
169
+ };
170
+ };
171
+
172
+ componentKey: string;
173
+
174
+ componentSetKey?: string;
175
+
176
+ overrides?: FigmaRestSpec.InstanceNode["overrides"];
177
+
178
+ children: NormalizedSceneNode[];
179
+ }
180
+
181
+ export interface NormalizedVectorNode extends NormalizedDefaultShapeTrait, NormalizedCornerTrait {
182
+ type: FigmaRestSpec.VectorNode["type"];
183
+ }
184
+
185
+ export interface NormalizedBooleanOperationNode
186
+ extends NormalizedIsLayerTrait,
187
+ NormalizedHasChildrenTrait,
188
+ NormalizedHasLayoutTrait,
189
+ NormalizedHasGeometryTrait,
190
+ NormalizedHasEffectsTrait {
191
+ type: FigmaRestSpec.BooleanOperationNode["type"];
192
+ }
193
+
194
+ export interface NormalizedUnhandledNode {
195
+ type: "UNHANDLED";
196
+ id: string;
197
+ original: FigmaRestSpec.Node | SceneNode;
198
+ }
199
+
200
+ export type NormalizedSceneNode =
201
+ | NormalizedFrameNode
202
+ | NormalizedRectangleNode
203
+ | NormalizedTextNode
204
+ | NormalizedComponentNode
205
+ | NormalizedInstanceNode
206
+ | NormalizedVectorNode
207
+ | NormalizedBooleanOperationNode
208
+ | NormalizedUnhandledNode;
@@ -0,0 +1,38 @@
1
+ import { camelCase } from "change-case";
2
+
3
+ export function ensureArray<T>(maybeArray: T | T[]): T[] {
4
+ if (Array.isArray(maybeArray)) {
5
+ return maybeArray;
6
+ }
7
+
8
+ return [maybeArray];
9
+ }
10
+
11
+ export function exists<T>(value: T | null | undefined): value is T {
12
+ return value != null;
13
+ }
14
+
15
+ export function compactObject<T extends Record<string, unknown>>(obj: T): T {
16
+ return Object.fromEntries(Object.entries(obj).filter(([, value]) => value != null)) as T;
17
+ }
18
+
19
+ export function objectEntries<T extends Record<string, unknown>>(obj: T) {
20
+ return Object.entries(obj) as [keyof T, T[keyof T]][];
21
+ }
22
+
23
+ export function identity<T>(value: T) {
24
+ return value;
25
+ }
26
+
27
+ /**
28
+ * camelCase but preserve underscore between numbers.
29
+ * temporary workaround to avoid x1_5 -> x15
30
+ * @example "color-1_5" -> "color1_5"
31
+ */
32
+ export function camelCasePreserveUnderscoreBetweenNumbers(input: string) {
33
+ return camelCase(input, {
34
+ mergeAmbiguousCharacters: false,
35
+ })
36
+ .replaceAll(/(\D)_(\d)/g, "$1$2")
37
+ .replaceAll(/(\d)_(\D)/g, "$1$2");
38
+ }
@@ -0,0 +1,19 @@
1
+ import type { RGBA } from "@figma/rest-api-spec";
2
+
3
+ export function toCssPixel(value: number): `${number}px` {
4
+ return `${value}px`;
5
+ }
6
+
7
+ export function toCssRem(value: number): `${number}rem` {
8
+ return `${value / 16}rem`;
9
+ }
10
+
11
+ export function toCssRgba(
12
+ color: RGBA,
13
+ ): `rgba(${number}, ${number}, ${number}, ${number})` | `rgb(${number}, ${number}, ${number})` {
14
+ if (color.a === 1) {
15
+ return `rgb(${Math.round(color.r * 255)}, ${Math.round(color.g * 255)}, ${Math.round(color.b * 255)})`;
16
+ }
17
+
18
+ return `rgba(${Math.round(color.r * 255)}, ${Math.round(color.g * 255)}, ${Math.round(color.b * 255)}, ${color.a})`;
19
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @see https://gist.github.com/yagudaev/0c2b89674c6aee8b38cd379752ef58d0
3
+ */
4
+
5
+ const identityMatrixHandlePositions = [
6
+ [0, 1, 0],
7
+ [0.5, 0.5, 1],
8
+ [1, 1, 1],
9
+ ];
10
+
11
+ /**
12
+ * Inverts a 2x3 affine transformation matrix
13
+ * For a 2x3 matrix [[a, b, c], [d, e, f]], we treat it as a 3x3 matrix with [0, 0, 1] as the third row
14
+ */
15
+ function inv(matrix: number[][]): number[][] {
16
+ const [[a, b, c], [d, e, f]] = matrix;
17
+
18
+ // Calculate determinant of the 2x2 linear part
19
+ const det = a * e - b * d;
20
+
21
+ if (Math.abs(det) < 1e-10) {
22
+ throw new Error("Matrix is not invertible");
23
+ }
24
+
25
+ // Invert the 2x2 linear part
26
+ const invDet = 1 / det;
27
+ const a_inv = e * invDet;
28
+ const b_inv = -b * invDet;
29
+ const d_inv = -d * invDet;
30
+ const e_inv = a * invDet;
31
+
32
+ // Calculate the inverted translation
33
+ const c_inv = -(a_inv * c + b_inv * f);
34
+ const f_inv = -(d_inv * c + e_inv * f);
35
+
36
+ return [
37
+ [a_inv, b_inv, c_inv],
38
+ [d_inv, e_inv, f_inv],
39
+ ];
40
+ }
41
+
42
+ /**
43
+ * Multiplies a 2x3 matrix with a 3x3 matrix
44
+ * Result is a 2x3 matrix
45
+ */
46
+ function multiply(matrix1: number[][], matrix2: number[][]): number[][] {
47
+ const [[a, b, c], [d, e, f]] = matrix1;
48
+ const result: number[][] = [[], []];
49
+
50
+ // For each column in matrix2
51
+ for (let col = 0; col < matrix2[0].length; col++) {
52
+ // First row of result
53
+ result[0][col] = a * matrix2[0][col] + b * matrix2[1][col] + c * matrix2[2][col];
54
+ // Second row of result
55
+ result[1][col] = d * matrix2[0][col] + e * matrix2[1][col] + f * matrix2[2][col];
56
+ }
57
+
58
+ return result;
59
+ }
60
+
61
+ export function convertTransformToGradientHandles(transform: number[][]) {
62
+ const inverseTransform = inv(transform);
63
+
64
+ // point matrix
65
+ const mp = multiply(inverseTransform, identityMatrixHandlePositions);
66
+
67
+ return [
68
+ { x: mp[0][0], y: mp[1][0] },
69
+ { x: mp[0][1], y: mp[1][1] },
70
+ { x: mp[0][2], y: mp[1][2] },
71
+ ];
72
+ }
@@ -0,0 +1,95 @@
1
+ import type {
2
+ NormalizedHasGeometryTrait,
3
+ NormalizedInstanceNode,
4
+ NormalizedSceneNode,
5
+ NormalizedSolidPaint,
6
+ } from "../normalizer";
7
+
8
+ export function traverseNode(
9
+ node: NormalizedSceneNode,
10
+ callback: (node: NormalizedSceneNode) => void,
11
+ ) {
12
+ callback(node);
13
+
14
+ if (!("children" in node)) return;
15
+
16
+ for (const child of node.children) {
17
+ traverseNode(child, callback);
18
+ }
19
+ }
20
+
21
+ export function findOne(
22
+ node: NormalizedSceneNode,
23
+ callback: (node: NormalizedSceneNode) => boolean,
24
+ ) {
25
+ let result: NormalizedSceneNode | undefined;
26
+
27
+ traverseNode(node, (n) => {
28
+ if (callback(n)) {
29
+ result = n;
30
+ }
31
+ });
32
+
33
+ return result;
34
+ }
35
+
36
+ export function findAll(
37
+ node: NormalizedSceneNode,
38
+ callback: (node: NormalizedSceneNode) => boolean,
39
+ ) {
40
+ const result: NormalizedSceneNode[] = [];
41
+
42
+ traverseNode(node, (n) => {
43
+ if (callback(n)) {
44
+ result.push(n);
45
+ }
46
+ });
47
+
48
+ return result;
49
+ }
50
+
51
+ export function findAllInstances<T>({ node, key }: { node: NormalizedSceneNode; key: string }) {
52
+ return findAll(
53
+ node,
54
+ (n) => n.type === "INSTANCE" && (n.componentKey === key || n.componentSetKey === key),
55
+ ) as (Omit<NormalizedInstanceNode, "componentProperties"> & { componentProperties: T })[];
56
+ }
57
+
58
+ export function getFirstSolidFill(node: NormalizedHasGeometryTrait) {
59
+ const fills = node.fills.filter(
60
+ (fill): fill is NormalizedSolidPaint =>
61
+ fill.type === "SOLID" && (!("visible" in fill) || fill.visible === true),
62
+ );
63
+
64
+ if (fills.length === 0) {
65
+ return undefined;
66
+ }
67
+
68
+ return fills[0];
69
+ }
70
+
71
+ export function getFirstFillVariable(node: NormalizedHasGeometryTrait) {
72
+ const fill = getFirstSolidFill(node);
73
+
74
+ return fill?.boundVariables?.color;
75
+ }
76
+
77
+ export function getFirstStroke(node: NormalizedHasGeometryTrait) {
78
+ const strokes =
79
+ node.strokes?.filter(
80
+ (stroke): stroke is NormalizedSolidPaint =>
81
+ stroke.type === "SOLID" && (!("visible" in stroke) || stroke.visible === true),
82
+ ) ?? [];
83
+
84
+ if (strokes.length === 0) {
85
+ return undefined;
86
+ }
87
+
88
+ return strokes[0];
89
+ }
90
+
91
+ export function getFirstStrokeVariable(node: NormalizedHasGeometryTrait) {
92
+ const stroke = getFirstStroke(node);
93
+
94
+ return stroke?.boundVariables?.color;
95
+ }
@@ -0,0 +1,49 @@
1
+ import type { VariableScope } from "@figma/rest-api-spec";
2
+
3
+ // boundVariable.id is formatted as "VariableID:{key}/{localId}", we have to extract the key
4
+ export function sanitizeVariableId(id: string) {
5
+ return id.replace("VariableID:", "").split("/")[0]!;
6
+ }
7
+
8
+ export function isVariableAlias(value: unknown): value is VariableAlias {
9
+ return (
10
+ typeof value === "object" &&
11
+ value !== null &&
12
+ "type" in value &&
13
+ value.type === "VARIABLE_ALIAS"
14
+ );
15
+ }
16
+
17
+ export function isIdenticalVariableValue(
18
+ value1: string | number | boolean | RGBA,
19
+ value2: string | number | boolean | RGBA,
20
+ ) {
21
+ if (typeof value1 !== typeof value2) {
22
+ return false;
23
+ }
24
+
25
+ if (typeof value1 === "string" || typeof value1 === "number" || typeof value1 === "boolean") {
26
+ return value1 === value2;
27
+ }
28
+
29
+ return (
30
+ value1.r === (value2 as RGBA).r &&
31
+ value1.g === (value2 as RGBA).g &&
32
+ value1.b === (value2 as RGBA).b &&
33
+ value1.a === (value2 as RGBA).a
34
+ );
35
+ }
36
+
37
+ export function isInsideScope(variable: { scopes: VariableScope[] }, scope: VariableScope) {
38
+ if (variable.scopes.includes("ALL_SCOPES")) {
39
+ return true;
40
+ }
41
+
42
+ if (variable.scopes.includes("ALL_FILLS")) {
43
+ if (scope === "FRAME_FILL" || scope === "SHAPE_FILL" || scope === "TEXT_FILL") {
44
+ return true;
45
+ }
46
+ }
47
+
48
+ return variable.scopes.includes(scope);
49
+ }