@seed-design/figma 1.3.8 → 1.3.10

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/index.cjs CHANGED
@@ -24,6 +24,8 @@ var index_cjs = require('./codegen/index.cjs');
24
24
  return normalizeComponentNode(node);
25
25
  case "INSTANCE":
26
26
  return normalizeInstanceNode(node);
27
+ case "SLOT":
28
+ return normalizeSlotNode(node);
27
29
  case "VECTOR":
28
30
  return normalizeVectorNode(node);
29
31
  case "BOOLEAN_OPERATION":
@@ -78,7 +80,7 @@ var index_cjs = require('./codegen/index.cjs');
78
80
  }
79
81
  function normalizePaints(paints) {
80
82
  if (!paints) return [];
81
- return paints.map(normalizePaint);
83
+ return paints.filter((paint)=>!("visible" in paint) || paint.visible !== false).map(normalizePaint);
82
84
  }
83
85
  function normalizeRadiusProps({ cornerRadius, rectangleCornerRadii }) {
84
86
  return {
@@ -160,6 +162,27 @@ var index_cjs = require('./codegen/index.cjs');
160
162
  children: normalizeNodes(node.children)
161
163
  };
162
164
  }
165
+ function normalizeSlotNode(node) {
166
+ return {
167
+ // NormalizedIsLayerTrait
168
+ type: node.type,
169
+ id: node.id,
170
+ name: node.name,
171
+ boundVariables: normalizeBoundVariables(node.boundVariables),
172
+ // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait, NormalizedHasFramePropertiesTrait
173
+ ...normalizeShapeProps(node),
174
+ // NormalizedCornerTrait
175
+ ...normalizeRadiusProps(node),
176
+ // NormalizedHasFramePropertiesTrait
177
+ ...normalizeAutolayoutProps(node),
178
+ // NormalizedHasChildrenTrait
179
+ children: normalizeNodes(node.children),
180
+ // NormalizedSlotNode specific
181
+ ...node.componentPropertyReferences?.["slotContentId"] && {
182
+ componentPropertyReferences: node.componentPropertyReferences
183
+ }
184
+ };
185
+ }
163
186
  function normalizeRectangleNode(node) {
164
187
  return {
165
188
  // NormalizedIsLayerTrait
@@ -514,6 +537,8 @@ function createPluginNormalizer() {
514
537
  return normalizeComponentNode(node);
515
538
  case "INSTANCE":
516
539
  return normalizeInstanceNode(node);
540
+ case "SLOT":
541
+ return normalizeSlotNode(node);
517
542
  case "VECTOR":
518
543
  return normalizeVectorNode(node);
519
544
  case "BOOLEAN_OPERATION":
@@ -613,7 +638,7 @@ function createPluginNormalizer() {
613
638
  console.warn("Mixed fills are not supported");
614
639
  return [];
615
640
  }
616
- return fills.map(normalizePaint);
641
+ return fills.filter((paint)=>paint.visible !== false).map(normalizePaint);
617
642
  }
618
643
  function normalizeRadiusProps(node) {
619
644
  return {
@@ -696,6 +721,28 @@ function createPluginNormalizer() {
696
721
  children: await normalizeNodes(node.children)
697
722
  };
698
723
  }
724
+ async function normalizeSlotNode(node) {
725
+ return {
726
+ // NormalizedIsLayerTrait
727
+ type: node.type,
728
+ id: node.id,
729
+ name: node.name,
730
+ boundVariables: normalizeBoundVariables(node),
731
+ // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait
732
+ ...await normalizeShapeProps(node),
733
+ // NormalizedCornerTrait
734
+ ...normalizeRadiusProps(node),
735
+ // NormalizedHasFramePropertiesTrait
736
+ ...await normalizeAutolayoutProps(node),
737
+ // NormalizedHasChildrenTrait
738
+ children: await normalizeNodes(node.children),
739
+ // NormalizedSlotNode specific
740
+ // Plugin API types don't include "slotContentId" in componentPropertyReferences yet
741
+ ...node.componentPropertyReferences?.["slotContentId"] && {
742
+ componentPropertyReferences: node.componentPropertyReferences
743
+ }
744
+ };
745
+ }
699
746
  async function normalizeRectangleNode(node) {
700
747
  return {
701
748
  // NormalizedIsLayerTrait
@@ -24308,6 +24355,7 @@ const componentAvatar = {
24308
24355
  "36",
24309
24356
  "42",
24310
24357
  "48",
24358
+ "56",
24311
24359
  "64",
24312
24360
  "80",
24313
24361
  "96",
@@ -24361,6 +24409,7 @@ const componentAvatarStack = {
24361
24409
  "36",
24362
24410
  "42",
24363
24411
  "48",
24412
+ "56",
24364
24413
  "64",
24365
24414
  "80",
24366
24415
  "96",
@@ -24453,6 +24502,9 @@ const componentBottomSheet = {
24453
24502
  "name": "componentBottomSheet",
24454
24503
  "key": "ffe99a21452831c28bd9375aac0aaf37d7ee6a0d",
24455
24504
  "componentPropertyDefinitions": {
24505
+ "Contents Slot#6752:0": {
24506
+ "type": "SLOT"
24507
+ },
24456
24508
  "Title#19787:3": {
24457
24509
  "type": "TEXT"
24458
24510
  },
@@ -24468,9 +24520,6 @@ const componentBottomSheet = {
24468
24520
  "Show Description#25192:0": {
24469
24521
  "type": "BOOLEAN"
24470
24522
  },
24471
- "Contents#25320:0": {
24472
- "type": "INSTANCE_SWAP"
24473
- },
24474
24523
  "Show Safe Area#25488:8": {
24475
24524
  "type": "BOOLEAN"
24476
24525
  },
@@ -24797,6 +24846,9 @@ const componentChlid = {
24797
24846
  "name": "componentChlid",
24798
24847
  "key": "ef79a21a39ceb4ce24b2fb93c9b430c1980a3e71",
24799
24848
  "componentPropertyDefinitions": {
24849
+ "Slot#6081:0": {
24850
+ "type": "SLOT"
24851
+ },
24800
24852
  "Type": {
24801
24853
  "type": "VARIANT",
24802
24854
  "variantOptions": [
@@ -26484,6 +26536,9 @@ const componentSelectBoxItemHorizontal = {
26484
26536
  "name": "componentSelectBoxItemHorizontal",
26485
26537
  "key": "8174af8ef3654dad996723883f5b84f44f791513",
26486
26538
  "componentPropertyDefinitions": {
26539
+ "Content Slot#6752:6": {
26540
+ "type": "SLOT"
26541
+ },
26487
26542
  "Title#28452:21": {
26488
26543
  "type": "TEXT"
26489
26544
  },
@@ -26531,6 +26586,9 @@ const componentSelectBoxItemVertical = {
26531
26586
  "name": "componentSelectBoxItemVertical",
26532
26587
  "key": "ccc88f0aae500c64e7d43be63c4f1a70baf76bfe",
26533
26588
  "componentPropertyDefinitions": {
26589
+ "Content Slot#6765:0": {
26590
+ "type": "SLOT"
26591
+ },
26534
26592
  "Title#58766:114": {
26535
26593
  "type": "TEXT"
26536
26594
  },
package/lib/index.d.ts CHANGED
@@ -73,6 +73,12 @@ interface NormalizedInstanceNode extends NormalizedFrameTrait {
73
73
  overrides?: FigmaRestSpec.InstanceNode["overrides"];
74
74
  children: NormalizedSceneNode[];
75
75
  }
76
+ interface NormalizedSlotNode extends NormalizedFrameTrait {
77
+ type: FigmaRestSpec.SlotNode["type"];
78
+ componentPropertyReferences?: FigmaRestSpec.IsLayerTrait["componentPropertyReferences"] & {
79
+ slotContentId?: string;
80
+ };
81
+ }
76
82
  interface NormalizedVectorNode extends NormalizedDefaultShapeTrait, NormalizedCornerTrait {
77
83
  type: FigmaRestSpec.VectorNode["type"];
78
84
  }
@@ -84,7 +90,7 @@ interface NormalizedUnhandledNode {
84
90
  id: string;
85
91
  original: FigmaRestSpec.Node | SceneNode;
86
92
  }
87
- type NormalizedSceneNode = NormalizedFrameNode | NormalizedRectangleNode | NormalizedTextNode | NormalizedComponentNode | NormalizedInstanceNode | NormalizedVectorNode | NormalizedBooleanOperationNode | NormalizedUnhandledNode;
93
+ type NormalizedSceneNode = NormalizedFrameNode | NormalizedRectangleNode | NormalizedTextNode | NormalizedComponentNode | NormalizedInstanceNode | NormalizedSlotNode | NormalizedVectorNode | NormalizedBooleanOperationNode | NormalizedUnhandledNode;
88
94
 
89
95
  /**
90
96
  * from-rest could be run outside of the Figma Plugin environment
@@ -217,5 +223,5 @@ declare function getFigmaStyleKey(name: string): string | undefined;
217
223
  declare function getFigmaColorVariableNames(scopes: Array<"fg" | "bg" | "stroke" | "palette">): string[];
218
224
 
219
225
  export { componentRepository, createIconService, createPluginNormalizer, createRestNormalizer, createStaticComponentRepository, createStaticIconRepository, createStaticStyleRepository, createStaticVariableRepository, createStyleService, createVariableService, getFigmaColorVariableNames, getFigmaStyleKey, getFigmaVariableKey, iconRepository, styleRepository, variableRepository };
220
- export type { ComponentMetadata, ComponentRepository, IconData, IconRepository, IconService, NormalizedBooleanOperationNode, NormalizedComponentNode, NormalizedCornerTrait, NormalizedDefaultShapeTrait, NormalizedFrameNode, NormalizedFrameTrait, NormalizedHasChildrenTrait, NormalizedHasEffectsTrait, NormalizedHasFramePropertiesTrait, NormalizedHasGeometryTrait, NormalizedHasLayoutTrait, NormalizedInstanceNode, NormalizedIsLayerTrait, NormalizedPaint, NormalizedRectangleNode, NormalizedSceneNode, NormalizedShadow, NormalizedSolidPaint, NormalizedTextNode, NormalizedTextSegment, NormalizedTypePropertiesTrait, NormalizedUnhandledNode, NormalizedVectorNode, Style, StyleRepository, StyleService, StyleType, Variable, VariableCollection, VariableRepository, VariableService, VariableServiceDeps, VariableType, VariableValue, VariableValueResolved };
226
+ export type { ComponentMetadata, ComponentRepository, IconData, IconRepository, IconService, NormalizedBooleanOperationNode, NormalizedComponentNode, NormalizedCornerTrait, NormalizedDefaultShapeTrait, NormalizedFrameNode, NormalizedFrameTrait, NormalizedHasChildrenTrait, NormalizedHasEffectsTrait, NormalizedHasFramePropertiesTrait, NormalizedHasGeometryTrait, NormalizedHasLayoutTrait, NormalizedInstanceNode, NormalizedIsLayerTrait, NormalizedPaint, NormalizedRectangleNode, NormalizedSceneNode, NormalizedShadow, NormalizedSlotNode, NormalizedSolidPaint, NormalizedTextNode, NormalizedTextSegment, NormalizedTypePropertiesTrait, NormalizedUnhandledNode, NormalizedVectorNode, Style, StyleRepository, StyleService, StyleType, Variable, VariableCollection, VariableRepository, VariableService, VariableServiceDeps, VariableType, VariableValue, VariableValueResolved };
221
227
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sources":["../src/normalizer/types.ts","../src/normalizer/from-rest.ts","../src/normalizer/from-plugin.ts","../src/entities/component.interface.ts","../src/entities/icon.interface.ts","../src/entities/variable.interface.ts","../src/entities/variable.repository.ts","../src/entities/style.interface.ts","../src/entities/style.repository.ts","../src/entities/icon.repository.ts","../src/entities/icon.service.ts","../src/entities/style.service.ts","../src/entities/variable.service.ts","../src/entities/component.repository.ts","../src/entities/index.ts"],"sourcesContent":["import type * as FigmaRestSpec from \"@figma/rest-api-spec\";\n\nexport type NormalizedIsLayerTrait = Pick<FigmaRestSpec.IsLayerTrait, \"type\" | \"id\" | \"name\"> & {\n boundVariables?: Pick<\n NonNullable<FigmaRestSpec.IsLayerTrait[\"boundVariables\"]>,\n | \"fills\"\n | \"strokes\"\n | \"itemSpacing\"\n | \"counterAxisSpacing\"\n | \"bottomLeftRadius\"\n | \"bottomRightRadius\"\n | \"topLeftRadius\"\n | \"topRightRadius\"\n | \"paddingBottom\"\n | \"paddingLeft\"\n | \"paddingRight\"\n | \"paddingTop\"\n | \"maxHeight\"\n | \"minHeight\"\n | \"maxWidth\"\n | \"minWidth\"\n | \"fontSize\"\n | \"fontWeight\"\n | \"lineHeight\"\n | \"size\"\n >;\n};\n\nexport type NormalizedCornerTrait = Pick<\n FigmaRestSpec.CornerTrait,\n \"cornerRadius\" | \"rectangleCornerRadii\"\n>;\n\nexport type NormalizedHasChildrenTrait = {\n children: NormalizedSceneNode[];\n};\n\nexport type NormalizedHasLayoutTrait = Pick<\n FigmaRestSpec.HasLayoutTrait,\n | \"layoutAlign\"\n | \"layoutGrow\"\n | \"absoluteBoundingBox\"\n | \"relativeTransform\"\n | \"layoutPositioning\"\n | \"layoutSizingHorizontal\"\n | \"layoutSizingVertical\"\n | \"minHeight\"\n | \"minWidth\"\n | \"maxHeight\"\n | \"maxWidth\"\n>;\n\nexport type NormalizedSolidPaint = FigmaRestSpec.SolidPaint;\n\nexport type NormalizedPaint =\n | NormalizedSolidPaint\n | FigmaRestSpec.GradientPaint\n | FigmaRestSpec.ImagePaint;\n\nexport type NormalizedHasGeometryTrait = Omit<\n Pick<FigmaRestSpec.HasGeometryTrait, \"fills\" | \"strokes\" | \"strokeWeight\">,\n \"fills\" | \"strokes\"\n> & {\n fills: NormalizedPaint[];\n strokes: NormalizedPaint[];\n fillStyleKey?: string;\n};\n\nexport type NormalizedShadow =\n | (Pick<\n FigmaRestSpec.DropShadowEffect,\n \"color\" | \"offset\" | \"radius\" | \"spread\" | \"boundVariables\"\n > &\n Required<Pick<FigmaRestSpec.DropShadowEffect, \"type\">>)\n | (Pick<\n FigmaRestSpec.InnerShadowEffect,\n \"color\" | \"offset\" | \"radius\" | \"spread\" | \"boundVariables\"\n > &\n Required<Pick<FigmaRestSpec.InnerShadowEffect, \"type\">>);\n\nexport type NormalizedHasEffectsTrait = Omit<FigmaRestSpec.HasEffectsTrait, \"effects\"> & {\n effects: NormalizedShadow[];\n effectStyleKey?: string;\n};\n\nexport type NormalizedHasFramePropertiesTrait = Pick<\n FigmaRestSpec.HasFramePropertiesTrait,\n | \"layoutMode\"\n | \"layoutWrap\"\n | \"paddingLeft\"\n | \"paddingRight\"\n | \"paddingTop\"\n | \"paddingBottom\"\n | \"primaryAxisAlignItems\"\n | \"primaryAxisSizingMode\"\n | \"counterAxisAlignItems\"\n | \"counterAxisSizingMode\"\n | \"itemSpacing\"\n | \"counterAxisSpacing\"\n>;\n\nexport interface NormalizedTextSegment {\n characters: string;\n start: number;\n end: number;\n style: {\n fontFamily?: string;\n fontWeight?: number;\n fontSize?: number;\n italic?: boolean;\n textDecoration?: string;\n letterSpacing?: number;\n /**\n * in pixels\n */\n lineHeight?: number;\n };\n}\n\nexport type NormalizedTypePropertiesTrait = Pick<\n FigmaRestSpec.TypePropertiesTrait,\n \"style\" | \"characters\"\n> & {\n segments: NormalizedTextSegment[];\n\n textStyleKey?: string;\n};\n\nexport type NormalizedDefaultShapeTrait = NormalizedIsLayerTrait &\n NormalizedHasLayoutTrait &\n NormalizedHasGeometryTrait &\n NormalizedHasEffectsTrait;\n\nexport type NormalizedFrameTrait = NormalizedIsLayerTrait &\n NormalizedHasLayoutTrait &\n NormalizedHasGeometryTrait &\n NormalizedHasEffectsTrait &\n NormalizedHasChildrenTrait &\n NormalizedCornerTrait &\n NormalizedHasFramePropertiesTrait;\n\nexport interface NormalizedFrameNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.FrameNode[\"type\"];\n}\n\nexport interface NormalizedRectangleNode\n extends NormalizedDefaultShapeTrait,\n NormalizedCornerTrait {\n type: FigmaRestSpec.RectangleNode[\"type\"];\n}\n\nexport interface NormalizedTextNode\n extends NormalizedDefaultShapeTrait,\n NormalizedTypePropertiesTrait {\n type: FigmaRestSpec.TextNode[\"type\"];\n}\n\nexport interface NormalizedComponentNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.ComponentNode[\"type\"];\n}\n\nexport interface NormalizedInstanceNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.InstanceNode[\"type\"];\n\n componentProperties: {\n [key: string]: FigmaRestSpec.ComponentProperty & {\n componentKey?: string;\n componentSetKey?: string;\n };\n };\n\n componentKey: string;\n\n componentSetKey?: string;\n\n overrides?: FigmaRestSpec.InstanceNode[\"overrides\"];\n\n children: NormalizedSceneNode[];\n}\n\nexport interface NormalizedVectorNode extends NormalizedDefaultShapeTrait, NormalizedCornerTrait {\n type: FigmaRestSpec.VectorNode[\"type\"];\n}\n\nexport interface NormalizedBooleanOperationNode\n extends NormalizedIsLayerTrait,\n NormalizedHasChildrenTrait,\n NormalizedHasLayoutTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasEffectsTrait {\n type: FigmaRestSpec.BooleanOperationNode[\"type\"];\n}\n\nexport interface NormalizedUnhandledNode {\n type: \"UNHANDLED\";\n id: string;\n original: FigmaRestSpec.Node | SceneNode;\n}\n\nexport type NormalizedSceneNode =\n | NormalizedFrameNode\n | NormalizedRectangleNode\n | NormalizedTextNode\n | NormalizedComponentNode\n | NormalizedInstanceNode\n | NormalizedVectorNode\n | NormalizedBooleanOperationNode\n | NormalizedUnhandledNode;\n","/**\n * from-rest could be run outside of the Figma Plugin environment\n * so we cannot use the Plugin API types directly e.g. getNodeByIdAsync\n */\n\n/**\n * NOTE: types of MinimalFillsTrait[\"styles\"] can be found here:\n * https://developers.figma.com/docs/rest-api/component-types/#style-type\n * Record<\"text\" | \"fill\" | \"stroke\" | \"effect\" | \"grid\", string>\n */\n\nimport type * as FigmaRestSpec from \"@figma/rest-api-spec\";\nimport type {\n NormalizedSceneNode,\n NormalizedFrameNode,\n NormalizedRectangleNode,\n NormalizedTextNode,\n NormalizedComponentNode,\n NormalizedInstanceNode,\n NormalizedTextSegment,\n NormalizedVectorNode,\n NormalizedBooleanOperationNode,\n NormalizedShadow,\n NormalizedCornerTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedPaint,\n NormalizedDefaultShapeTrait,\n NormalizedHasEffectsTrait,\n NormalizedIsLayerTrait,\n} from \"./types\";\n\nexport interface RestNormalizerContext {\n /**\n * A map of style **ID** to style data\n */\n styles: Record<string, FigmaRestSpec.Style>;\n /**\n * A map of component **ID** to component data\n */\n components: Record<string, FigmaRestSpec.Component>;\n /**\n * A map of component set **ID** to component set data\n */\n componentSets: Record<string, FigmaRestSpec.ComponentSet>;\n}\n\nexport function createRestNormalizer(\n ctx: RestNormalizerContext,\n): (node: FigmaRestSpec.Node) => NormalizedSceneNode {\n function normalizeNodes(nodes: readonly FigmaRestSpec.Node[]): NormalizedSceneNode[] {\n // Figma REST API omits default values for some fields, \"visible\" is one of them\n return nodes.filter((node) => !(\"visible\" in node) || node.visible).map(normalizeNode);\n }\n\n function normalizeNode(node: FigmaRestSpec.Node): NormalizedSceneNode {\n switch (node.type) {\n case \"FRAME\":\n return normalizeFrameNode(node);\n case \"RECTANGLE\":\n return normalizeRectangleNode(node);\n case \"TEXT\":\n return normalizeTextNode(node);\n case \"COMPONENT\":\n return normalizeComponentNode(node);\n case \"INSTANCE\":\n return normalizeInstanceNode(node);\n case \"VECTOR\":\n return normalizeVectorNode(node);\n case \"BOOLEAN_OPERATION\":\n return normalizeBooleanOperationNode(node);\n case \"GROUP\":\n return normalizeGroupNodeAsFrameNode(node);\n default:\n return {\n type: \"UNHANDLED\",\n id: node.id,\n original: node,\n };\n }\n }\n\n function normalizeBoundVariables(\n boundVariables: FigmaRestSpec.IsLayerTrait[\"boundVariables\"] | undefined,\n ) {\n if (!boundVariables) return undefined;\n\n return {\n fills: boundVariables.fills,\n strokes: boundVariables.strokes,\n itemSpacing: boundVariables.itemSpacing,\n counterAxisSpacing: boundVariables.counterAxisSpacing,\n topLeftRadius: boundVariables.topLeftRadius,\n topRightRadius: boundVariables.topRightRadius,\n bottomLeftRadius: boundVariables.bottomLeftRadius,\n bottomRightRadius: boundVariables.bottomRightRadius,\n paddingTop: boundVariables.paddingTop,\n paddingRight: boundVariables.paddingRight,\n paddingBottom: boundVariables.paddingBottom,\n paddingLeft: boundVariables.paddingLeft,\n minWidth: boundVariables.minWidth,\n maxWidth: boundVariables.maxWidth,\n minHeight: boundVariables.minHeight,\n maxHeight: boundVariables.maxHeight,\n fontSize: boundVariables.fontSize,\n fontWeight: boundVariables.fontWeight,\n lineHeight: boundVariables.lineHeight,\n size: boundVariables.size,\n };\n }\n\n function normalizePaint(paint: FigmaRestSpec.Paint): NormalizedPaint {\n switch (paint.type) {\n case \"SOLID\":\n case \"IMAGE\":\n case \"GRADIENT_LINEAR\":\n case \"GRADIENT_RADIAL\":\n case \"GRADIENT_ANGULAR\":\n case \"GRADIENT_DIAMOND\":\n return paint;\n default:\n throw new Error(`Unimplemented paint type: ${paint.type}`);\n }\n }\n\n function normalizePaints(paints: FigmaRestSpec.Paint[] | undefined): NormalizedPaint[] {\n if (!paints) return [];\n\n return paints.map(normalizePaint);\n }\n\n function normalizeRadiusProps({\n cornerRadius,\n rectangleCornerRadii,\n }: Pick<\n FigmaRestSpec.RectangleNode,\n \"cornerRadius\" | \"rectangleCornerRadii\"\n >): NormalizedCornerTrait {\n return { cornerRadius, rectangleCornerRadii };\n }\n\n function normalizeEffectProps(\n node: Pick<FigmaRestSpec.FrameNode, \"effects\" | \"styles\">,\n ): NormalizedHasEffectsTrait {\n const effects = (node.effects ?? [])\n .filter(\n (effect): effect is FigmaRestSpec.DropShadowEffect | FigmaRestSpec.InnerShadowEffect =>\n effect.visible !== false &&\n (effect.type === \"DROP_SHADOW\" || effect.type === \"INNER_SHADOW\"),\n )\n .map((effect): NormalizedShadow => {\n const { type, color, offset, radius, spread, boundVariables } = effect;\n\n return {\n // remove fallback when resolved: https://github.com/figma/rest-api-spec/issues/84\n type: type ?? \"INNER_SHADOW\",\n color,\n offset,\n radius,\n spread,\n boundVariables,\n };\n });\n\n return {\n effects,\n effectStyleKey: node.styles?.[\"effect\"] ? ctx.styles[node.styles[\"effect\"]]?.key : undefined,\n };\n }\n\n function normalizeShapeProps(\n node: Pick<\n FigmaRestSpec.FrameNode,\n | \"fills\"\n | \"strokes\"\n | \"strokeWeight\"\n | \"styles\"\n | \"layoutGrow\"\n | \"layoutAlign\"\n | \"layoutSizingHorizontal\"\n | \"layoutSizingVertical\"\n | \"absoluteBoundingBox\"\n | \"relativeTransform\"\n | \"layoutPositioning\"\n | \"minHeight\"\n | \"minWidth\"\n | \"maxHeight\"\n | \"maxWidth\"\n | \"effects\"\n >,\n ): Omit<NormalizedDefaultShapeTrait, keyof NormalizedIsLayerTrait> {\n return {\n // NormalizedHasLayoutTrait\n layoutGrow: node.layoutGrow,\n layoutAlign: node.layoutAlign,\n layoutSizingHorizontal: node.layoutSizingHorizontal,\n layoutSizingVertical: node.layoutSizingVertical,\n absoluteBoundingBox: node.absoluteBoundingBox,\n relativeTransform: node.relativeTransform,\n layoutPositioning: node.layoutPositioning,\n minHeight: node.minHeight,\n minWidth: node.minWidth,\n maxHeight: node.maxHeight,\n maxWidth: node.maxWidth,\n\n // NormalizedHasGeometryTrait\n fills: normalizePaints(node.fills),\n fillStyleKey: node.styles?.[\"fill\"] ? ctx.styles[node.styles[\"fill\"]]?.key : undefined,\n strokes: normalizePaints(node.strokes),\n strokeWeight: node.strokeWeight,\n\n // NormalizedHasEffectsTrait\n ...normalizeEffectProps(node),\n };\n }\n\n function normalizeAutolayoutProps(\n node: Pick<\n FigmaRestSpec.FrameNode,\n | \"layoutMode\"\n | \"layoutWrap\"\n | \"paddingLeft\"\n | \"paddingRight\"\n | \"paddingTop\"\n | \"paddingBottom\"\n | \"primaryAxisAlignItems\"\n | \"primaryAxisSizingMode\"\n | \"counterAxisAlignItems\"\n | \"counterAxisSizingMode\"\n | \"itemSpacing\"\n | \"counterAxisSpacing\"\n >,\n ): NormalizedHasFramePropertiesTrait {\n return {\n layoutMode: node.layoutMode,\n layoutWrap: node.layoutWrap,\n paddingLeft: node.paddingLeft,\n paddingRight: node.paddingRight,\n paddingTop: node.paddingTop,\n paddingBottom: node.paddingBottom,\n primaryAxisAlignItems: node.primaryAxisAlignItems,\n primaryAxisSizingMode: node.primaryAxisSizingMode,\n counterAxisAlignItems: node.counterAxisAlignItems,\n counterAxisSizingMode: node.counterAxisSizingMode,\n itemSpacing: node.itemSpacing,\n counterAxisSpacing: node.counterAxisSpacing,\n };\n }\n\n function normalizeFrameNode(node: FigmaRestSpec.FrameNode): NormalizedFrameNode {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node.boundVariables),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait, NormalizedHasFramePropertiesTrait\n ...normalizeShapeProps(node),\n\n // NormalizedCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasFramePropertiesTrait\n ...normalizeAutolayoutProps(node),\n\n // NormalizedHasChildrenTrait\n children: normalizeNodes(node.children),\n };\n }\n\n function normalizeRectangleNode(node: FigmaRestSpec.RectangleNode): NormalizedRectangleNode {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node.boundVariables),\n\n // NormalizedCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...normalizeShapeProps(node),\n };\n }\n\n function normalizeTextNode(node: FigmaRestSpec.TextNode): NormalizedTextNode {\n // Convert TypeStyle to NormalizedTextSegment.style format\n function normalizeSegmentStyle(\n typeStyle: FigmaRestSpec.TypeStyle,\n ): NormalizedTextSegment[\"style\"] {\n return {\n fontFamily: typeStyle.fontFamily,\n fontWeight: typeStyle.fontWeight,\n fontSize: typeStyle.fontSize,\n italic: typeStyle.italic,\n textDecoration: typeStyle.textDecoration,\n letterSpacing: typeStyle.letterSpacing,\n lineHeight: typeStyle.lineHeightPx,\n };\n }\n\n // Function to segment a text node based on style overrides\n function segmentTextNode(textNode: FigmaRestSpec.TextNode): NormalizedTextSegment[] {\n const segments: NormalizedTextSegment[] = [];\n const characters = textNode.characters;\n const styleOverrides = textNode.characterStyleOverrides || [];\n const styleTable = textNode.styleOverrideTable || {};\n\n // If no style overrides, return the entire text as one segment\n if (!styleOverrides.length) {\n return [\n {\n characters: characters,\n start: 0,\n end: characters.length,\n style: normalizeSegmentStyle(textNode.style),\n },\n ];\n }\n\n let currentSegment: NormalizedTextSegment = {\n characters: \"\",\n start: 0,\n end: 0,\n style: {},\n };\n\n let currentStyleId: string | null = null;\n\n for (let i = 0; i < characters.length; i++) {\n const styleId = styleOverrides[i]?.toString() || null;\n\n // If style changes or it's the first character\n if (styleId !== currentStyleId || i === 0) {\n // Add the previous segment if it exists\n if (i > 0) {\n currentSegment.end = i;\n currentSegment.characters = characters.substring(\n currentSegment.start,\n currentSegment.end,\n );\n segments.push({ ...currentSegment });\n }\n\n // Start a new segment\n currentStyleId = styleId;\n currentSegment = {\n characters: \"\",\n start: i,\n end: 0,\n style: styleId && styleTable[styleId] ? normalizeSegmentStyle(styleTable[styleId]) : {},\n };\n }\n }\n\n // Add the last segment\n if (currentSegment.start < characters.length) {\n currentSegment.end = characters.length;\n currentSegment.characters = characters.substring(currentSegment.start, currentSegment.end);\n segments.push(currentSegment);\n }\n\n return segments;\n }\n\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node.boundVariables),\n\n // NormalizedTypePropertiesTrait\n style: node.style, // this style is the style of the first segment\n characters: node.characters,\n textStyleKey: node.styles?.[\"text\"] ? ctx.styles[node.styles[\"text\"]]?.key : undefined,\n segments: segmentTextNode(node),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...normalizeShapeProps(node),\n };\n }\n\n function normalizeComponentNode(node: FigmaRestSpec.ComponentNode): NormalizedComponentNode {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node.boundVariables),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...normalizeShapeProps(node),\n\n // NormalizedHasCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasFramePropertiesTrait\n ...normalizeAutolayoutProps(node),\n\n // NormalizedHasChildrenTrait\n children: normalizeNodes(node.children),\n };\n }\n\n function normalizeInstanceNode(node: FigmaRestSpec.InstanceNode): NormalizedInstanceNode {\n const mainComponent = ctx.components[node.componentId];\n if (!mainComponent) {\n throw new Error(`Component ${node.componentId} not found`);\n }\n\n const componentSet = mainComponent.componentSetId\n ? ctx.componentSets[mainComponent.componentSetId]\n : undefined;\n\n const componentProperties: NormalizedInstanceNode[\"componentProperties\"] = {};\n\n for (const [key, value] of Object.entries(node.componentProperties ?? {})) {\n componentProperties[key] = value;\n\n if (value.type === \"INSTANCE_SWAP\") {\n // unless value.type === \"BOOLEAN\", value.value is string\n const swappedComponent = ctx.components[value.value as string];\n\n if (swappedComponent) {\n componentProperties[key].componentKey = swappedComponent.key;\n\n const swappedComponentSet = swappedComponent?.componentSetId\n ? ctx.componentSets[swappedComponent.componentSetId]\n : undefined;\n\n if (swappedComponentSet) {\n componentProperties[key].componentSetKey = swappedComponentSet.key;\n }\n }\n }\n }\n\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node.boundVariables),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...normalizeShapeProps(node),\n\n // NormalizedCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasFramePropertiesTrait\n ...normalizeAutolayoutProps(node),\n\n // NormalizedHasChildrenTrait\n children: normalizeNodes(node.children),\n\n // NormalizedInstanceNode specific\n componentProperties,\n componentKey: mainComponent.key,\n componentSetKey: componentSet?.key,\n overrides: node.overrides,\n };\n }\n\n function normalizeVectorNode(node: FigmaRestSpec.VectorNode): NormalizedVectorNode {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node.boundVariables),\n\n // NormalizedCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...normalizeShapeProps(node),\n };\n }\n\n function normalizeBooleanOperationNode(\n node: FigmaRestSpec.BooleanOperationNode,\n ): NormalizedBooleanOperationNode {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node.boundVariables),\n\n // NormalizedHasLayoutTrait\n layoutGrow: node.layoutGrow,\n layoutAlign: node.layoutAlign,\n layoutSizingHorizontal: node.layoutSizingHorizontal,\n layoutSizingVertical: node.layoutSizingVertical,\n absoluteBoundingBox: node.absoluteBoundingBox,\n relativeTransform: node.relativeTransform,\n layoutPositioning: node.layoutPositioning,\n minHeight: node.minHeight,\n minWidth: node.minWidth,\n maxHeight: node.maxHeight,\n maxWidth: node.maxWidth,\n\n // NormalizedHasGeometryTrait\n fills: normalizePaints(node.fills),\n fillStyleKey: node.styles?.[\"fill\"] ? ctx.styles[node.styles[\"fill\"]]?.key : undefined,\n strokes: normalizePaints(node.strokes),\n strokeWeight: node.strokeWeight,\n\n // NormalizedHasEffectsTrait\n ...normalizeEffectProps(node),\n\n // NormalizedHasChildrenTrait\n children: normalizeNodes(node.children),\n };\n }\n\n function normalizeGroupNodeAsFrameNode(node: FigmaRestSpec.GroupNode): NormalizedFrameNode {\n return {\n // NormalizedIsLayerTrait\n type: \"FRAME\",\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node.boundVariables),\n\n // NormalizedHasLayoutTrait\n layoutGrow: node.layoutGrow,\n layoutAlign: node.layoutAlign,\n layoutSizingHorizontal: node.layoutSizingHorizontal,\n layoutSizingVertical: node.layoutSizingVertical,\n absoluteBoundingBox: node.absoluteBoundingBox,\n relativeTransform: node.relativeTransform,\n layoutPositioning: node.layoutPositioning,\n minHeight: node.minHeight,\n minWidth: node.minWidth,\n maxHeight: node.maxHeight,\n maxWidth: node.maxWidth,\n\n // NormalizedHasGeometryTrait\n fills: [],\n fillStyleKey: undefined,\n strokes: [],\n strokeWeight: undefined,\n\n // NormalizedHasEffectsTrait\n effects: [],\n effectStyleKey: undefined,\n\n // NormalizedCornerTrait\n cornerRadius: undefined,\n rectangleCornerRadii: undefined,\n\n // NormalizedHasFramePropertiesTrait\n // these are undefined compared to from-plugin normalizer\n // since inferredAutoLayout isn't available in REST API\n layoutMode: undefined,\n layoutWrap: undefined,\n paddingLeft: undefined,\n paddingRight: undefined,\n paddingTop: undefined,\n paddingBottom: undefined,\n primaryAxisAlignItems: undefined,\n primaryAxisSizingMode: undefined,\n counterAxisAlignItems: undefined,\n counterAxisSizingMode: undefined,\n itemSpacing: undefined,\n counterAxisSpacing: undefined,\n\n // NormalizedHasChildrenTrait\n children: normalizeNodes(node.children),\n };\n }\n\n return normalizeNode;\n}\n","/**\n * from-plugin is guaranteed to be run in the Figma Plugin environment\n * so we can use the Plugin API types directly (figma.getNodeByIdAsync, node.getMainComponentAsync etc)\n * however it could be better to make users can DI later\n */\n\nimport type {\n NormalizedSceneNode,\n NormalizedFrameNode,\n NormalizedRectangleNode,\n NormalizedTextNode,\n NormalizedComponentNode,\n NormalizedInstanceNode,\n NormalizedVectorNode,\n NormalizedBooleanOperationNode,\n NormalizedHasEffectsTrait,\n NormalizedShadow,\n NormalizedDefaultShapeTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedCornerTrait,\n NormalizedIsLayerTrait,\n NormalizedPaint,\n NormalizedTextSegment,\n} from \"./types\";\nimport { convertTransformToGradientHandles } from \"@/utils/figma-gradient\";\n\nexport function createPluginNormalizer(): (node: SceneNode) => Promise<NormalizedSceneNode> {\n async function normalizeNodes(nodes: readonly SceneNode[]): Promise<NormalizedSceneNode[]> {\n return Promise.all(nodes.filter((node) => node.visible).map(normalizeNode));\n }\n\n async function normalizeNode(node: SceneNode): Promise<NormalizedSceneNode> {\n switch (node.type) {\n case \"FRAME\":\n return normalizeFrameNode(node);\n case \"RECTANGLE\":\n return normalizeRectangleNode(node);\n case \"TEXT\":\n return normalizeTextNode(node);\n case \"COMPONENT\":\n return normalizeComponentNode(node);\n case \"INSTANCE\":\n return normalizeInstanceNode(node);\n case \"VECTOR\":\n return normalizeVectorNode(node);\n case \"BOOLEAN_OPERATION\":\n return normalizeBooleanOperationNode(node);\n case \"GROUP\":\n return normalizeGroupNodeAsFrameNode(node);\n default:\n return {\n type: \"UNHANDLED\",\n id: node.id,\n original: node,\n };\n }\n }\n\n /**\n * Pick specific fields from boundVariables\n */\n function normalizeBoundVariables({\n boundVariables,\n }: Pick<FrameNode, \"boundVariables\">): NormalizedIsLayerTrait[\"boundVariables\"] {\n if (!boundVariables) return undefined;\n\n return {\n fills: boundVariables.fills,\n strokes: boundVariables.strokes,\n itemSpacing: boundVariables.itemSpacing,\n counterAxisSpacing: boundVariables.counterAxisSpacing,\n topLeftRadius: boundVariables.topLeftRadius,\n topRightRadius: boundVariables.topRightRadius,\n bottomLeftRadius: boundVariables.bottomLeftRadius,\n bottomRightRadius: boundVariables.bottomRightRadius,\n paddingTop: boundVariables.paddingTop,\n paddingRight: boundVariables.paddingRight,\n paddingBottom: boundVariables.paddingBottom,\n paddingLeft: boundVariables.paddingLeft,\n minWidth: boundVariables.minWidth,\n maxWidth: boundVariables.maxWidth,\n minHeight: boundVariables.minHeight,\n maxHeight: boundVariables.maxHeight,\n fontSize: boundVariables.fontSize,\n fontWeight: boundVariables.fontWeight,\n lineHeight: boundVariables.lineHeight,\n size: {\n x: boundVariables.width,\n y: boundVariables.height,\n },\n };\n }\n\n function normalizeSolidPaint(paint: SolidPaint): NormalizedPaint {\n return {\n type: paint.type,\n color: {\n r: paint.color.r,\n g: paint.color.g,\n b: paint.color.b,\n a: paint.opacity ?? 1,\n },\n visible: paint.visible,\n blendMode: paint.blendMode ?? \"NORMAL\",\n opacity: paint.opacity,\n boundVariables: paint.boundVariables,\n };\n }\n\n function normalizePaint(paint: Paint): NormalizedPaint {\n switch (paint.type) {\n case \"SOLID\":\n return normalizeSolidPaint(paint);\n case \"IMAGE\":\n return {\n type: \"IMAGE\",\n scaleMode: paint.scaleMode === \"CROP\" ? \"STRETCH\" : paint.scaleMode,\n imageTransform: paint.imageTransform,\n scalingFactor: paint.scalingFactor,\n filters: paint.filters,\n rotation: paint.rotation,\n imageRef: paint.imageHash ?? \"\",\n blendMode: paint.blendMode ?? \"NORMAL\",\n visible: paint.visible,\n opacity: paint.opacity,\n };\n case \"GRADIENT_LINEAR\":\n case \"GRADIENT_RADIAL\":\n case \"GRADIENT_ANGULAR\":\n case \"GRADIENT_DIAMOND\":\n return {\n type: paint.type,\n gradientStops: [...paint.gradientStops],\n visible: paint.visible,\n opacity: paint.opacity,\n blendMode: paint.blendMode ?? \"NORMAL\",\n gradientHandlePositions: convertTransformToGradientHandles(paint.gradientTransform),\n };\n default:\n throw new Error(`Unimplemented paint type: ${paint.type}`);\n }\n }\n\n function normalizePaints(fills: readonly Paint[] | PluginAPI[\"mixed\"]): NormalizedPaint[] {\n if (fills === figma.mixed) {\n console.warn(\"Mixed fills are not supported\");\n\n return [];\n }\n\n return fills.map(normalizePaint);\n }\n\n function normalizeRadiusProps(\n node: Pick<\n RectangleNode,\n \"cornerRadius\" | \"topLeftRadius\" | \"topRightRadius\" | \"bottomRightRadius\" | \"bottomLeftRadius\"\n >,\n ): NormalizedCornerTrait {\n return {\n cornerRadius: node.cornerRadius === figma.mixed ? undefined : node.cornerRadius,\n rectangleCornerRadii: [\n node.topLeftRadius,\n node.topRightRadius,\n node.bottomRightRadius,\n node.bottomLeftRadius,\n ],\n };\n }\n\n async function normalizeEffectProps(\n node: Pick<RectangleNode, \"effects\" | \"effectStyleId\">,\n ): Promise<NormalizedHasEffectsTrait> {\n const effectStyleKey =\n typeof node.effectStyleId === \"string\"\n ? (await figma.getStyleByIdAsync(node.effectStyleId))?.key\n : undefined;\n\n const effects = node.effects\n .filter((effect): effect is DropShadowEffect | InnerShadowEffect => {\n if (!effect.visible) return false;\n\n return effect.type === \"DROP_SHADOW\" || effect.type === \"INNER_SHADOW\";\n })\n .map(({ blendMode, visible, ...rest }): NormalizedShadow => rest);\n\n return {\n ...(effectStyleKey ? { effectStyleKey } : {}),\n effects,\n };\n }\n\n async function normalizeShapeProps(\n node: Pick<\n RectangleNode,\n | \"fills\"\n | \"fillStyleId\"\n | \"strokes\"\n | \"strokeWeight\"\n | \"layoutGrow\"\n | \"layoutAlign\"\n | \"layoutSizingHorizontal\"\n | \"layoutSizingVertical\"\n | \"absoluteBoundingBox\"\n | \"relativeTransform\"\n | \"layoutPositioning\"\n | \"minHeight\"\n | \"minWidth\"\n | \"maxHeight\"\n | \"maxWidth\"\n | \"effects\"\n | \"effectStyleId\"\n > &\n Partial<Pick<FrameNode, \"inferredAutoLayout\">>,\n ): Promise<Omit<NormalizedDefaultShapeTrait, keyof NormalizedIsLayerTrait>> {\n const fillStyleKey =\n typeof node.fillStyleId === \"string\"\n ? (await figma.getStyleByIdAsync(node.fillStyleId))?.key\n : undefined;\n\n return {\n // NormalizedHasLayoutTrait\n layoutGrow: (node.inferredAutoLayout?.layoutGrow ?? node.layoutGrow) as 0 | 1 | undefined,\n layoutAlign: node.inferredAutoLayout?.layoutAlign ?? node.layoutAlign,\n layoutSizingHorizontal: node.layoutSizingHorizontal,\n layoutSizingVertical: node.layoutSizingVertical,\n absoluteBoundingBox: node.absoluteBoundingBox,\n relativeTransform: node.relativeTransform,\n layoutPositioning: node.layoutPositioning,\n minHeight: node.minHeight ?? undefined,\n minWidth: node.minWidth ?? undefined,\n maxHeight: node.maxHeight ?? undefined,\n maxWidth: node.maxWidth ?? undefined,\n\n // NormalizedHasGeometryTrait\n fills: await normalizePaints(node.fills),\n fillStyleKey,\n strokes: await normalizePaints(node.strokes),\n strokeWeight: node.strokeWeight === figma.mixed ? undefined : node.strokeWeight,\n\n // NormalizedHasEffectsTrait\n ...(await normalizeEffectProps(node)),\n };\n }\n\n async function normalizeAutolayoutProps(\n node: Omit<FrameNode, \"type\" | \"clone\">,\n ): Promise<NormalizedHasFramePropertiesTrait> {\n return {\n layoutMode: node.inferredAutoLayout?.layoutMode ?? node.layoutMode,\n layoutWrap: node.inferredAutoLayout?.layoutWrap ?? node.layoutWrap,\n paddingLeft: node.inferredAutoLayout?.paddingLeft ?? node.paddingLeft,\n paddingRight: node.inferredAutoLayout?.paddingRight ?? node.paddingRight,\n paddingTop: node.inferredAutoLayout?.paddingTop ?? node.paddingTop,\n paddingBottom: node.inferredAutoLayout?.paddingBottom ?? node.paddingBottom,\n primaryAxisAlignItems:\n node.inferredAutoLayout?.primaryAxisAlignItems ?? node.primaryAxisAlignItems,\n counterAxisAlignItems:\n node.inferredAutoLayout?.counterAxisAlignItems ?? node.counterAxisAlignItems,\n primaryAxisSizingMode:\n node.inferredAutoLayout?.primaryAxisSizingMode ?? node.primaryAxisSizingMode,\n counterAxisSizingMode:\n node.inferredAutoLayout?.counterAxisSizingMode ?? node.counterAxisSizingMode,\n itemSpacing: node.inferredAutoLayout?.itemSpacing ?? node.itemSpacing,\n counterAxisSpacing:\n node.inferredAutoLayout?.counterAxisSpacing ?? node.counterAxisSpacing ?? undefined,\n };\n }\n\n async function normalizeFrameNode(node: FrameNode): Promise<NormalizedFrameNode> {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...(await normalizeShapeProps(node)),\n\n // NormalizedCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasFramePropertiesTrait\n ...(await normalizeAutolayoutProps(node)),\n\n // NormalizedHasChildrenTrait\n children: await normalizeNodes(node.children),\n };\n }\n\n async function normalizeRectangleNode(node: RectangleNode): Promise<NormalizedRectangleNode> {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node),\n\n // NormalizedCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...(await normalizeShapeProps(node)),\n };\n }\n\n async function normalizeTextNode(node: TextNode): Promise<NormalizedTextNode> {\n const segments = node.getStyledTextSegments([\n \"fontName\",\n \"fontWeight\",\n \"fontSize\",\n \"letterSpacing\",\n \"lineHeight\",\n \"paragraphSpacing\",\n \"textStyleId\",\n \"fills\",\n \"boundVariables\",\n \"textDecoration\",\n ]);\n const first = segments[0];\n\n const textStyleKey =\n typeof node.textStyleId === \"string\"\n ? (await figma.getStyleByIdAsync(node.textStyleId))?.key\n : undefined;\n\n const normalizeLetterSpacing = (\n letterSpacing: LetterSpacing,\n fontSize: number,\n ): NormalizedTextSegment[\"style\"][\"letterSpacing\"] => {\n if (letterSpacing.unit === \"PIXELS\") return letterSpacing.value;\n if (letterSpacing.unit === \"PERCENT\") return (fontSize * letterSpacing.value) / 100;\n\n return undefined;\n };\n\n const normalizeLineHeight = (\n lineHeight: LineHeight,\n fontSize: number,\n ): NormalizedTextSegment[\"style\"][\"lineHeight\"] => {\n if (lineHeight.unit === \"PIXELS\") return lineHeight.value;\n if (lineHeight.unit === \"PERCENT\") return (fontSize * lineHeight.value) / 100;\n\n return undefined;\n };\n\n const isItalic = (fontName: FontName): boolean => {\n // {\n // family: \"SF Mono\",\n // style: \"Bold Italic\"\n // }\n return fontName.style.toLowerCase().includes(\"italic\");\n };\n\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node),\n\n // NormalizedTypePropertiesTrait\n // NOTE: this normalization is incomplete compared to from-rest.ts normalizer\n style: {\n fontFamily: first.fontName.family,\n fontPostScriptName: null,\n fontStyle: first.fontName.style,\n italic: isItalic(first.fontName),\n fontWeight: first.fontWeight,\n fontSize: first.fontSize,\n textAlignHorizontal: node.textAlignHorizontal,\n textAlignVertical: node.textAlignVertical,\n letterSpacing: normalizeLetterSpacing(first.letterSpacing, first.fontSize),\n paragraphSpacing: first.paragraphSpacing,\n textDecoration: first.textDecoration,\n lineHeightPx: normalizeLineHeight(first.lineHeight, first.fontSize),\n lineHeightUnit:\n first.lineHeight.unit === \"PIXELS\"\n ? \"PIXELS\"\n : first.lineHeight.unit === \"PERCENT\"\n ? \"FONT_SIZE_%\"\n : undefined,\n boundVariables: first.boundVariables,\n maxLines: node.maxLines ?? undefined,\n },\n characters: node.characters,\n textStyleKey,\n segments: segments.map((segment) => ({\n characters: segment.characters,\n start: segment.start,\n end: segment.end,\n style: {\n fontSize: segment.fontSize,\n fontWeight: segment.fontWeight,\n fontFamily: segment.fontName.family,\n italic: isItalic(segment.fontName),\n letterSpacing: normalizeLetterSpacing(segment.letterSpacing, segment.fontSize),\n lineHeight: normalizeLineHeight(segment.lineHeight, segment.fontSize),\n textDecoration: segment.textDecoration,\n },\n })),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...(await normalizeShapeProps(node)),\n };\n }\n\n async function normalizeComponentNode(node: ComponentNode): Promise<NormalizedComponentNode> {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...(await normalizeShapeProps(node)),\n\n // NormalizedCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasFramePropertiesTrait\n ...(await normalizeAutolayoutProps(node)),\n\n // NormalizedHasChildrenTrait\n children: await normalizeNodes(node.children),\n };\n }\n\n async function normalizeInstanceNode(node: InstanceNode): Promise<NormalizedInstanceNode> {\n const mainComponent = await node.getMainComponentAsync();\n if (!mainComponent) {\n throw new Error(\"Instance node has no main component\");\n }\n\n const componentProperties: NormalizedInstanceNode[\"componentProperties\"] = {};\n\n for (const [key, value] of Object.entries(node.componentProperties)) {\n componentProperties[key] = value;\n\n if (value.type === \"INSTANCE_SWAP\") {\n // unless value.type === \"BOOLEAN\", value.value is string\n const swappedComponent = (await figma.getNodeByIdAsync(\n value.value as string,\n )) as ComponentNode;\n\n if (swappedComponent) {\n componentProperties[key].componentKey = swappedComponent.key;\n\n if (swappedComponent.parent?.type === \"COMPONENT_SET\") {\n componentProperties[key].componentSetKey = swappedComponent.parent.key;\n }\n }\n }\n }\n\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...(await normalizeShapeProps(node)),\n\n // NormalizedCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasFramePropertiesTrait\n ...(await normalizeAutolayoutProps(node)),\n\n // NormalizedHasChildrenTrait\n children: await normalizeNodes(node.children),\n\n // NormalizedInstanceNode specific\n componentProperties,\n componentKey: mainComponent.key,\n componentSetKey:\n mainComponent.parent?.type === \"COMPONENT_SET\" ? mainComponent.parent.key : undefined,\n overrides: node.overrides,\n };\n }\n\n async function normalizeVectorNode(node: VectorNode): Promise<NormalizedVectorNode> {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node),\n\n // NormalizedCornerTrait\n cornerRadius: node.cornerRadius === figma.mixed ? undefined : node.cornerRadius,\n rectangleCornerRadii: undefined, // VectorNode does not have individual corner radii\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...(await normalizeShapeProps(node)),\n };\n }\n\n async function normalizeBooleanOperationNode(\n node: BooleanOperationNode,\n ): Promise<NormalizedBooleanOperationNode> {\n const fillStyleKey =\n typeof node.fillStyleId === \"string\"\n ? (await figma.getStyleByIdAsync(node.fillStyleId))?.key\n : undefined;\n\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node),\n\n // NormalizedHasLayoutTrait\n layoutGrow: node.layoutGrow as 0 | 1 | undefined,\n layoutAlign: node.layoutAlign,\n layoutSizingHorizontal: node.layoutSizingHorizontal,\n layoutSizingVertical: node.layoutSizingVertical,\n absoluteBoundingBox: node.absoluteBoundingBox,\n relativeTransform: node.relativeTransform,\n layoutPositioning: node.layoutPositioning,\n minHeight: node.minHeight ?? undefined,\n minWidth: node.minWidth ?? undefined,\n maxHeight: node.maxHeight ?? undefined,\n maxWidth: node.maxWidth ?? undefined,\n\n // NormalizedHasGeometryTrait\n fills: await normalizePaints(node.fills),\n fillStyleKey,\n strokes: await normalizePaints(node.strokes),\n strokeWeight: node.strokeWeight === figma.mixed ? undefined : node.strokeWeight,\n\n // NormalizedHasEffectsTrait\n ...(await normalizeEffectProps(node)),\n\n // NormalizedHasChildrenTrait\n children: await normalizeNodes(node.children),\n };\n }\n\n async function normalizeGroupNodeAsFrameNode(\n node: GroupNode & { inferredAutoLayout?: FrameNode[\"inferredAutoLayout\"] },\n ): Promise<NormalizedFrameNode> {\n return {\n // NormalizedIsLayerTrait\n type: \"FRAME\",\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node),\n\n // NormalizedHasLayoutTrait\n layoutGrow: (node.inferredAutoLayout?.layoutGrow ?? node.layoutGrow) as 0 | 1 | undefined,\n layoutAlign: node.inferredAutoLayout?.layoutAlign ?? node.layoutAlign,\n layoutSizingHorizontal: node.layoutSizingHorizontal,\n layoutSizingVertical: node.layoutSizingVertical,\n absoluteBoundingBox: node.absoluteBoundingBox,\n relativeTransform: node.relativeTransform,\n layoutPositioning: node.layoutPositioning,\n minHeight: node.minHeight ?? undefined,\n minWidth: node.minWidth ?? undefined,\n maxHeight: node.maxHeight ?? undefined,\n maxWidth: node.maxWidth ?? undefined,\n\n // NormalizedHasGeometryTrait\n fills: [],\n fillStyleKey: undefined,\n strokes: [],\n strokeWeight: undefined,\n\n // NormalizedHasEffectsTrait\n effects: [],\n effectStyleKey: undefined,\n\n // NormalizedCornerTrait\n cornerRadius: undefined,\n rectangleCornerRadii: undefined,\n\n // NormalizedHasFramePropertiesTrait\n layoutMode: node.inferredAutoLayout?.layoutMode,\n layoutWrap: node.inferredAutoLayout?.layoutWrap,\n paddingLeft: node.inferredAutoLayout?.paddingLeft,\n paddingRight: node.inferredAutoLayout?.paddingRight,\n paddingTop: node.inferredAutoLayout?.paddingTop,\n paddingBottom: node.inferredAutoLayout?.paddingBottom,\n primaryAxisAlignItems: node.inferredAutoLayout?.primaryAxisAlignItems,\n counterAxisAlignItems: node.inferredAutoLayout?.counterAxisAlignItems,\n primaryAxisSizingMode: node.inferredAutoLayout?.primaryAxisSizingMode,\n counterAxisSizingMode: node.inferredAutoLayout?.counterAxisSizingMode,\n itemSpacing: node.inferredAutoLayout?.itemSpacing,\n counterAxisSpacing: node.inferredAutoLayout?.counterAxisSpacing ?? undefined,\n\n // NormalizedHasChildrenTrait\n children: await normalizeNodes(node.children),\n };\n }\n\n return normalizeNode;\n}\n","import type { ComponentPropertyDefinition } from \"@/codegen\";\n\nexport interface ComponentMetadata {\n name: string;\n key: string;\n componentPropertyDefinitions: Record<string, ComponentPropertyDefinition>;\n}\n","export interface IconData {\n name: string;\n type: \"monochrome\" | \"multicolor\";\n weight?: string;\n}\n","import type {\n LocalVariable,\n LocalVariableCollection,\n VariableAlias,\n VariableResolvedDataType,\n} from \"@figma/rest-api-spec\";\n\nexport type Variable = LocalVariable;\n\nexport type VariableCollection = LocalVariableCollection;\n\nexport type VariableType = VariableResolvedDataType;\n\nexport type VariableValue = Variable[\"valuesByMode\"][string];\n\nexport type VariableValueResolved = Exclude<VariableValue, VariableAlias>;\n\nexport type { VariableScope } from \"@figma/rest-api-spec\";\n","import type { Variable, VariableCollection } from \"./variable.interface\";\n\nexport interface VariableRepository {\n getVariableList(): Variable[];\n getVariableCollectionList(): VariableCollection[];\n findVariableByKey(key: string): Variable | undefined;\n findVariableById(id: string): Variable | undefined;\n findVariableByName(name: string): Variable | undefined;\n findVariableCollectionByKey(key: string): VariableCollection | undefined;\n findVariableCollectionById(id: string): VariableCollection | undefined;\n}\n\nexport function createStaticVariableRepository({\n variables,\n variableCollections,\n}: {\n variables: Record<string, Variable>;\n variableCollections: Record<string, VariableCollection>;\n}): VariableRepository {\n const variablesKeyMap = new Map<string, Variable>();\n const variablesIdMap = new Map<string, Variable>();\n const variablesNameMap = new Map<string, Variable>();\n const variableCollectionsKeyMap = new Map<string, VariableCollection>();\n const variableCollectionsIdMap = new Map<string, VariableCollection>();\n\n for (const variable of Object.values(variables)) {\n if (variable.remote) {\n continue;\n }\n\n variablesKeyMap.set(variable.key, variable);\n variablesIdMap.set(variable.id, variable);\n variablesNameMap.set(variable.name, variable);\n }\n\n for (const variableCollection of Object.values(variableCollections)) {\n if (variableCollection.remote) {\n continue;\n }\n\n variableCollectionsKeyMap.set(variableCollection.key, variableCollection);\n variableCollectionsIdMap.set(variableCollection.id, variableCollection);\n }\n\n const variablesList = [...variablesKeyMap.values()];\n const variableCollectionsList = [...variableCollectionsKeyMap.values()];\n\n return {\n getVariableList: () => variablesList,\n getVariableCollectionList: () => variableCollectionsList,\n findVariableByName: (name: string) => variablesNameMap.get(name),\n findVariableByKey: (key: string) => variablesKeyMap.get(key),\n findVariableById: (id: string) => variablesIdMap.get(id),\n findVariableCollectionByKey: (key: string) => variableCollectionsKeyMap.get(key),\n findVariableCollectionById: (id: string) => variableCollectionsIdMap.get(id),\n };\n}\n","import type { Style as FigmaStyle, StyleType as FigmaStyleType } from \"@figma/rest-api-spec\";\n\nexport type Style = FigmaStyle;\n\nexport type StyleType = FigmaStyleType;\n","import type { Style } from \"./style.interface\";\n\nexport interface StyleRepository {\n getAll(): Style[];\n getTextStyles(): Style[];\n getColorStyles(): Style[];\n findOneByKey(key: string): Style | undefined;\n findOneByName(name: string): Style | undefined;\n}\n\nexport function createStaticStyleRepository(styles: Style[]): StyleRepository {\n const stylesMap = new Map<string, Style>();\n const stylesNameMap = new Map<string, Style>();\n\n for (const style of styles) {\n stylesMap.set(style.key, style);\n stylesNameMap.set(style.name, style);\n }\n\n return {\n getAll: () => styles,\n getTextStyles: () => styles.filter((style) => style.styleType === \"TEXT\"),\n getColorStyles: () => styles.filter((style) => style.styleType === \"FILL\"),\n findOneByKey: (key) => stylesMap.get(key),\n findOneByName: (name) => stylesNameMap.get(name),\n };\n}\n","import type { IconData } from \"./icon.interface\";\n\nexport interface IconRepository {\n getOne(key: string): IconData;\n}\n\nexport function createStaticIconRepository(iconRecord: Record<string, IconData>) {\n return {\n getOne: (key: string) => iconRecord[key],\n };\n}\n","import type { IconData } from \"./icon.interface\";\nimport type { IconRepository } from \"./icon.repository\";\n\nexport interface IconService {\n isAvailable: (componentKey: string) => boolean;\n getOne: (componentKey: string) => IconData;\n}\n\nexport function createIconService({\n iconRepository,\n}: {\n iconRepository: IconRepository;\n}): IconService {\n function isAvailable(componentKey: string) {\n return iconRepository.getOne(componentKey) !== undefined;\n }\n\n function getOne(componentKey: string) {\n return iconRepository.getOne(componentKey);\n }\n\n return {\n isAvailable,\n getOne,\n };\n}\n","import type { StyleRepository } from \"./style.repository\";\n\nexport interface StyleService {\n getSlug: (id: string) => string[] | undefined;\n}\n\n// TODO: inferStyleName 추가해야 함, rest api에서 style value가 제공되지 않고 있어 보류\nexport function createStyleService({\n styleRepository,\n}: {\n styleRepository: StyleRepository;\n}): StyleService {\n function getName(id: string) {\n const style = styleRepository.findOneByKey(id);\n\n if (!style) {\n return undefined;\n }\n\n return style.name;\n }\n\n function getSlug(id: string): string[] | undefined {\n const name = getName(id);\n\n if (!name) {\n return undefined;\n }\n\n return name.split(\"/\");\n }\n\n return {\n getSlug,\n };\n}\n","import {\n isIdenticalVariableValue,\n isInsideScope,\n isVariableAlias,\n sanitizeVariableId,\n} from \"@/utils/figma-variable\";\nimport type { Variable, VariableScope, VariableValueResolved } from \"./variable.interface\";\nimport type { VariableRepository } from \"./variable.repository\";\n\nexport interface VariableService {\n getSlug: (id: string) => string[] | undefined;\n resolveValue: (variable: Variable, mode: string) => VariableValueResolved;\n infer: (value: VariableValueResolved, scope: VariableScope) => Variable | undefined;\n}\n\nexport interface VariableServiceDeps {\n variableRepository: VariableRepository;\n inferCompareFunction: (a: Variable, b: Variable) => number;\n}\n\nexport function createVariableService({\n variableRepository,\n inferCompareFunction,\n}: VariableServiceDeps): VariableService {\n const variables = variableRepository.getVariableList();\n\n // private\n function getName(key: string) {\n const sanitizedId = sanitizeVariableId(key);\n const variable = variableRepository.findVariableByKey(sanitizedId);\n\n if (!variable) {\n return undefined;\n }\n\n return variable.name;\n }\n\n function getDefaultModeId(variable: Variable) {\n const variableCollection = variableRepository.findVariableCollectionById(\n variable.variableCollectionId,\n );\n\n if (!variableCollection) {\n // Variable collection not found: ${variable.variableCollectionId}, falling back to variable.valuesByMode key\n return Object.keys(variable.valuesByMode)[0]!;\n }\n\n return variableCollection.defaultModeId;\n }\n\n // public\n function getSlug(key: string): string[] | undefined {\n const name = getName(key);\n\n if (!name) {\n return undefined;\n }\n\n return name.split(\"/\");\n }\n\n function resolveValue(variable: Variable, mode: string): VariableValueResolved {\n const value = variable.valuesByMode[mode];\n\n if (value === undefined) {\n throw new Error(`Variable value not found: ${variable.id} ${mode}`);\n }\n\n if (isVariableAlias(value)) {\n const resolvedVariable = variableRepository.findVariableById(value.id);\n\n if (!resolvedVariable) {\n throw new Error(`Variable not found: ${value.id}`);\n }\n\n return resolveValue(resolvedVariable, mode);\n }\n\n return value;\n }\n\n function infer(value: VariableValueResolved, scope: VariableScope) {\n // NOTE: We assume that the variable is in the default mode or value is equal between all modes for simplicity.\n const inferredVariables = variables.filter(\n (variable) =>\n isInsideScope(variable, scope) &&\n isIdenticalVariableValue(resolveValue(variable, getDefaultModeId(variable)), value),\n );\n\n const sortedVariables = inferredVariables.sort(inferCompareFunction);\n\n return sortedVariables[0];\n }\n\n return {\n getSlug,\n resolveValue,\n infer,\n };\n}\n","import type { ComponentMetadata } from \"./component.interface\";\n\nexport interface ComponentRepository {\n getOne(key: string): ComponentMetadata | undefined;\n}\n\nexport function createStaticComponentRepository(data: Record<string, ComponentMetadata>) {\n const componentRecord: Record<string, ComponentMetadata> = {};\n Object.values(data).forEach((component) => {\n componentRecord[component.key] = component;\n });\n\n return {\n getOne: (key: string) => componentRecord[key],\n };\n}\n","import { createStaticIconRepository } from \"./icon.repository\";\nimport { createStaticStyleRepository } from \"./style.repository\";\nimport { createStaticVariableRepository } from \"./variable.repository\";\nimport { createStaticComponentRepository } from \"./component.repository\";\nimport { FIGMA_ICONS } from \"./data/__generated__/icons\";\n\n// Archive\nimport { FIGMA_STYLES as FIGMA_STYLES_ARCHIVE } from \"./data/__generated__/archive/styles\";\nimport { FIGMA_VARIABLE_COLLECTIONS as FIGMA_VARIABLE_COLLECTIONS_ARCHIVE } from \"./data/__generated__/archive/variable-collections\";\nimport { FIGMA_VARIABLES as FIGMA_VARIABLES_ARCHIVE } from \"./data/__generated__/archive/variables\";\nimport * as FIGMA_COMPONENTS_ARCHIVE from \"./data/__generated__/archive/component-sets\";\n\n// Current\nimport { FIGMA_STYLES } from \"./data/__generated__/styles\";\nimport { FIGMA_VARIABLE_COLLECTIONS } from \"./data/__generated__/variable-collections\";\nimport { FIGMA_VARIABLES } from \"./data/__generated__/variables\";\nimport * as FIGMA_COMPONENTS from \"./data/__generated__/component-sets\";\n\nexport * from \"./icon.interface\";\nexport * from \"./icon.repository\";\nexport * from \"./icon.service\";\nexport * from \"./style.interface\";\nexport * from \"./style.repository\";\nexport * from \"./style.service\";\nexport * from \"./variable.interface\";\nexport * from \"./variable.repository\";\nexport * from \"./variable.service\";\nexport * from \"./component.interface\";\nexport * from \"./component.repository\";\n\nexport const styleRepository = createStaticStyleRepository([\n ...FIGMA_STYLES_ARCHIVE,\n ...FIGMA_STYLES,\n]);\nexport const variableRepository = createStaticVariableRepository({\n variables: { ...FIGMA_VARIABLES_ARCHIVE, ...FIGMA_VARIABLES },\n variableCollections: { ...FIGMA_VARIABLE_COLLECTIONS_ARCHIVE, ...FIGMA_VARIABLE_COLLECTIONS },\n});\nexport const iconRepository = createStaticIconRepository(FIGMA_ICONS);\nexport const componentRepository = createStaticComponentRepository({\n ...FIGMA_COMPONENTS_ARCHIVE,\n ...FIGMA_COMPONENTS,\n});\n\nexport function getFigmaVariableKey(name: string) {\n return variableRepository.findVariableByName(name)?.key;\n}\n\nexport function getFigmaStyleKey(name: string) {\n return styleRepository.findOneByName(name)?.key;\n}\n\nexport function getFigmaColorVariableNames(scopes: Array<\"fg\" | \"bg\" | \"stroke\" | \"palette\">) {\n const variables = variableRepository.getVariableList();\n return variables\n .filter((variable) =>\n scopes.includes(variable.name.split(\"/\")[0] as \"fg\" | \"bg\" | \"stroke\" | \"palette\"),\n )\n .map((variable) => variable.name);\n}\n"],"names":[],"mappings":";;;;;;AACO;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACA;AACA;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACO;;ACjFP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACzBP;AACA;AACA;AACA;AACA;;AAEO;;ACLA;AACP;AACA;AACA;AACA;;ACLO;AACP;AACA;AACA;AACA;;ACHO;AACA;AACA;AACA;AACA;;ACJA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;;ACZO;AACA;;ACDA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;;ACPA;AACP;AACA;AACO;AACP;AACA;;ACJO;AACP;AACA;AACA;AACO;AACP;AACA;;ACPO;AACP;AACA;AACO;AACP;AACA;;ACJO;AACP;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACO;;ACVA;AACP;AACA;AACO;AACP;AACA;;ACKO;AACA;AACA;AACP;AACA;AACO;AACP;AACA;AACO;AACA;AACA;;;"}
1
+ {"version":3,"file":"index.d.ts","sources":["../src/normalizer/types.ts","../src/normalizer/from-rest.ts","../src/normalizer/from-plugin.ts","../src/entities/component.interface.ts","../src/entities/icon.interface.ts","../src/entities/variable.interface.ts","../src/entities/variable.repository.ts","../src/entities/style.interface.ts","../src/entities/style.repository.ts","../src/entities/icon.repository.ts","../src/entities/icon.service.ts","../src/entities/style.service.ts","../src/entities/variable.service.ts","../src/entities/component.repository.ts","../src/entities/index.ts"],"sourcesContent":["import type * as FigmaRestSpec from \"@figma/rest-api-spec\";\n\nexport type NormalizedIsLayerTrait = Pick<FigmaRestSpec.IsLayerTrait, \"type\" | \"id\" | \"name\"> & {\n boundVariables?: Pick<\n NonNullable<FigmaRestSpec.IsLayerTrait[\"boundVariables\"]>,\n | \"fills\"\n | \"strokes\"\n | \"itemSpacing\"\n | \"counterAxisSpacing\"\n | \"bottomLeftRadius\"\n | \"bottomRightRadius\"\n | \"topLeftRadius\"\n | \"topRightRadius\"\n | \"paddingBottom\"\n | \"paddingLeft\"\n | \"paddingRight\"\n | \"paddingTop\"\n | \"maxHeight\"\n | \"minHeight\"\n | \"maxWidth\"\n | \"minWidth\"\n | \"fontSize\"\n | \"fontWeight\"\n | \"lineHeight\"\n | \"size\"\n >;\n};\n\nexport type NormalizedCornerTrait = Pick<\n FigmaRestSpec.CornerTrait,\n \"cornerRadius\" | \"rectangleCornerRadii\"\n>;\n\nexport type NormalizedHasChildrenTrait = {\n children: NormalizedSceneNode[];\n};\n\nexport type NormalizedHasLayoutTrait = Pick<\n FigmaRestSpec.HasLayoutTrait,\n | \"layoutAlign\"\n | \"layoutGrow\"\n | \"absoluteBoundingBox\"\n | \"relativeTransform\"\n | \"layoutPositioning\"\n | \"layoutSizingHorizontal\"\n | \"layoutSizingVertical\"\n | \"minHeight\"\n | \"minWidth\"\n | \"maxHeight\"\n | \"maxWidth\"\n>;\n\nexport type NormalizedSolidPaint = FigmaRestSpec.SolidPaint;\n\nexport type NormalizedPaint =\n | NormalizedSolidPaint\n | FigmaRestSpec.GradientPaint\n | FigmaRestSpec.ImagePaint;\n\nexport type NormalizedHasGeometryTrait = Omit<\n Pick<FigmaRestSpec.HasGeometryTrait, \"fills\" | \"strokes\" | \"strokeWeight\">,\n \"fills\" | \"strokes\"\n> & {\n fills: NormalizedPaint[];\n strokes: NormalizedPaint[];\n fillStyleKey?: string;\n};\n\nexport type NormalizedShadow =\n | (Pick<\n FigmaRestSpec.DropShadowEffect,\n \"color\" | \"offset\" | \"radius\" | \"spread\" | \"boundVariables\"\n > &\n Required<Pick<FigmaRestSpec.DropShadowEffect, \"type\">>)\n | (Pick<\n FigmaRestSpec.InnerShadowEffect,\n \"color\" | \"offset\" | \"radius\" | \"spread\" | \"boundVariables\"\n > &\n Required<Pick<FigmaRestSpec.InnerShadowEffect, \"type\">>);\n\nexport type NormalizedHasEffectsTrait = Omit<FigmaRestSpec.HasEffectsTrait, \"effects\"> & {\n effects: NormalizedShadow[];\n effectStyleKey?: string;\n};\n\nexport type NormalizedHasFramePropertiesTrait = Pick<\n FigmaRestSpec.HasFramePropertiesTrait,\n | \"layoutMode\"\n | \"layoutWrap\"\n | \"paddingLeft\"\n | \"paddingRight\"\n | \"paddingTop\"\n | \"paddingBottom\"\n | \"primaryAxisAlignItems\"\n | \"primaryAxisSizingMode\"\n | \"counterAxisAlignItems\"\n | \"counterAxisSizingMode\"\n | \"itemSpacing\"\n | \"counterAxisSpacing\"\n>;\n\nexport interface NormalizedTextSegment {\n characters: string;\n start: number;\n end: number;\n style: {\n fontFamily?: string;\n fontWeight?: number;\n fontSize?: number;\n italic?: boolean;\n textDecoration?: string;\n letterSpacing?: number;\n /**\n * in pixels\n */\n lineHeight?: number;\n };\n}\n\nexport type NormalizedTypePropertiesTrait = Pick<\n FigmaRestSpec.TypePropertiesTrait,\n \"style\" | \"characters\"\n> & {\n segments: NormalizedTextSegment[];\n\n textStyleKey?: string;\n};\n\nexport type NormalizedDefaultShapeTrait = NormalizedIsLayerTrait &\n NormalizedHasLayoutTrait &\n NormalizedHasGeometryTrait &\n NormalizedHasEffectsTrait;\n\nexport type NormalizedFrameTrait = NormalizedIsLayerTrait &\n NormalizedHasLayoutTrait &\n NormalizedHasGeometryTrait &\n NormalizedHasEffectsTrait &\n NormalizedHasChildrenTrait &\n NormalizedCornerTrait &\n NormalizedHasFramePropertiesTrait;\n\nexport interface NormalizedFrameNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.FrameNode[\"type\"];\n}\n\nexport interface NormalizedRectangleNode\n extends NormalizedDefaultShapeTrait,\n NormalizedCornerTrait {\n type: FigmaRestSpec.RectangleNode[\"type\"];\n}\n\nexport interface NormalizedTextNode\n extends NormalizedDefaultShapeTrait,\n NormalizedTypePropertiesTrait {\n type: FigmaRestSpec.TextNode[\"type\"];\n}\n\nexport interface NormalizedComponentNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.ComponentNode[\"type\"];\n}\n\nexport interface NormalizedInstanceNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.InstanceNode[\"type\"];\n\n componentProperties: {\n [key: string]: FigmaRestSpec.ComponentProperty & {\n componentKey?: string;\n componentSetKey?: string;\n };\n };\n\n componentKey: string;\n\n componentSetKey?: string;\n\n overrides?: FigmaRestSpec.InstanceNode[\"overrides\"];\n\n children: NormalizedSceneNode[];\n}\n\nexport interface NormalizedSlotNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.SlotNode[\"type\"];\n componentPropertyReferences?: FigmaRestSpec.IsLayerTrait[\"componentPropertyReferences\"] & {\n slotContentId?: string;\n };\n}\n\nexport interface NormalizedVectorNode extends NormalizedDefaultShapeTrait, NormalizedCornerTrait {\n type: FigmaRestSpec.VectorNode[\"type\"];\n}\n\nexport interface NormalizedBooleanOperationNode\n extends NormalizedIsLayerTrait,\n NormalizedHasChildrenTrait,\n NormalizedHasLayoutTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasEffectsTrait {\n type: FigmaRestSpec.BooleanOperationNode[\"type\"];\n}\n\nexport interface NormalizedUnhandledNode {\n type: \"UNHANDLED\";\n id: string;\n original: FigmaRestSpec.Node | SceneNode;\n}\n\nexport type NormalizedSceneNode =\n | NormalizedFrameNode\n | NormalizedRectangleNode\n | NormalizedTextNode\n | NormalizedComponentNode\n | NormalizedInstanceNode\n | NormalizedSlotNode\n | NormalizedVectorNode\n | NormalizedBooleanOperationNode\n | NormalizedUnhandledNode;\n","/**\n * from-rest could be run outside of the Figma Plugin environment\n * so we cannot use the Plugin API types directly e.g. getNodeByIdAsync\n */\n\n/**\n * NOTE: types of MinimalFillsTrait[\"styles\"] can be found here:\n * https://developers.figma.com/docs/rest-api/component-types/#style-type\n * Record<\"text\" | \"fill\" | \"stroke\" | \"effect\" | \"grid\", string>\n */\n\nimport type * as FigmaRestSpec from \"@figma/rest-api-spec\";\nimport type {\n NormalizedSceneNode,\n NormalizedFrameNode,\n NormalizedRectangleNode,\n NormalizedTextNode,\n NormalizedComponentNode,\n NormalizedInstanceNode,\n NormalizedTextSegment,\n NormalizedSlotNode,\n NormalizedVectorNode,\n NormalizedBooleanOperationNode,\n NormalizedShadow,\n NormalizedCornerTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedPaint,\n NormalizedDefaultShapeTrait,\n NormalizedHasEffectsTrait,\n NormalizedIsLayerTrait,\n} from \"./types\";\n\nexport interface RestNormalizerContext {\n /**\n * A map of style **ID** to style data\n */\n styles: Record<string, FigmaRestSpec.Style>;\n /**\n * A map of component **ID** to component data\n */\n components: Record<string, FigmaRestSpec.Component>;\n /**\n * A map of component set **ID** to component set data\n */\n componentSets: Record<string, FigmaRestSpec.ComponentSet>;\n}\n\nexport function createRestNormalizer(\n ctx: RestNormalizerContext,\n): (node: FigmaRestSpec.Node) => NormalizedSceneNode {\n function normalizeNodes(nodes: readonly FigmaRestSpec.Node[]): NormalizedSceneNode[] {\n // Figma REST API omits default values for some fields, \"visible\" is one of them\n return nodes.filter((node) => !(\"visible\" in node) || node.visible).map(normalizeNode);\n }\n\n function normalizeNode(node: FigmaRestSpec.Node): NormalizedSceneNode {\n switch (node.type) {\n case \"FRAME\":\n return normalizeFrameNode(node);\n case \"RECTANGLE\":\n return normalizeRectangleNode(node);\n case \"TEXT\":\n return normalizeTextNode(node);\n case \"COMPONENT\":\n return normalizeComponentNode(node);\n case \"INSTANCE\":\n return normalizeInstanceNode(node);\n case \"SLOT\":\n return normalizeSlotNode(node);\n case \"VECTOR\":\n return normalizeVectorNode(node);\n case \"BOOLEAN_OPERATION\":\n return normalizeBooleanOperationNode(node);\n case \"GROUP\":\n return normalizeGroupNodeAsFrameNode(node);\n default:\n return {\n type: \"UNHANDLED\",\n id: node.id,\n original: node,\n };\n }\n }\n\n function normalizeBoundVariables(\n boundVariables: FigmaRestSpec.IsLayerTrait[\"boundVariables\"] | undefined,\n ) {\n if (!boundVariables) return undefined;\n\n return {\n fills: boundVariables.fills,\n strokes: boundVariables.strokes,\n itemSpacing: boundVariables.itemSpacing,\n counterAxisSpacing: boundVariables.counterAxisSpacing,\n topLeftRadius: boundVariables.topLeftRadius,\n topRightRadius: boundVariables.topRightRadius,\n bottomLeftRadius: boundVariables.bottomLeftRadius,\n bottomRightRadius: boundVariables.bottomRightRadius,\n paddingTop: boundVariables.paddingTop,\n paddingRight: boundVariables.paddingRight,\n paddingBottom: boundVariables.paddingBottom,\n paddingLeft: boundVariables.paddingLeft,\n minWidth: boundVariables.minWidth,\n maxWidth: boundVariables.maxWidth,\n minHeight: boundVariables.minHeight,\n maxHeight: boundVariables.maxHeight,\n fontSize: boundVariables.fontSize,\n fontWeight: boundVariables.fontWeight,\n lineHeight: boundVariables.lineHeight,\n size: boundVariables.size,\n };\n }\n\n function normalizePaint(paint: FigmaRestSpec.Paint): NormalizedPaint {\n switch (paint.type) {\n case \"SOLID\":\n case \"IMAGE\":\n case \"GRADIENT_LINEAR\":\n case \"GRADIENT_RADIAL\":\n case \"GRADIENT_ANGULAR\":\n case \"GRADIENT_DIAMOND\":\n return paint;\n default:\n throw new Error(`Unimplemented paint type: ${paint.type}`);\n }\n }\n\n function normalizePaints(paints: FigmaRestSpec.Paint[] | undefined): NormalizedPaint[] {\n if (!paints) return [];\n\n return paints\n .filter((paint) => !(\"visible\" in paint) || paint.visible !== false)\n .map(normalizePaint);\n }\n\n function normalizeRadiusProps({\n cornerRadius,\n rectangleCornerRadii,\n }: Pick<\n FigmaRestSpec.RectangleNode,\n \"cornerRadius\" | \"rectangleCornerRadii\"\n >): NormalizedCornerTrait {\n return { cornerRadius, rectangleCornerRadii };\n }\n\n function normalizeEffectProps(\n node: Pick<FigmaRestSpec.FrameNode, \"effects\" | \"styles\">,\n ): NormalizedHasEffectsTrait {\n const effects = (node.effects ?? [])\n .filter(\n (effect): effect is FigmaRestSpec.DropShadowEffect | FigmaRestSpec.InnerShadowEffect =>\n effect.visible !== false &&\n (effect.type === \"DROP_SHADOW\" || effect.type === \"INNER_SHADOW\"),\n )\n .map((effect): NormalizedShadow => {\n const { type, color, offset, radius, spread, boundVariables } = effect;\n\n return {\n // remove fallback when resolved: https://github.com/figma/rest-api-spec/issues/84\n type: type ?? \"INNER_SHADOW\",\n color,\n offset,\n radius,\n spread,\n boundVariables,\n };\n });\n\n return {\n effects,\n effectStyleKey: node.styles?.[\"effect\"] ? ctx.styles[node.styles[\"effect\"]]?.key : undefined,\n };\n }\n\n function normalizeShapeProps(\n node: Pick<\n FigmaRestSpec.FrameNode,\n | \"fills\"\n | \"strokes\"\n | \"strokeWeight\"\n | \"styles\"\n | \"layoutGrow\"\n | \"layoutAlign\"\n | \"layoutSizingHorizontal\"\n | \"layoutSizingVertical\"\n | \"absoluteBoundingBox\"\n | \"relativeTransform\"\n | \"layoutPositioning\"\n | \"minHeight\"\n | \"minWidth\"\n | \"maxHeight\"\n | \"maxWidth\"\n | \"effects\"\n >,\n ): Omit<NormalizedDefaultShapeTrait, keyof NormalizedIsLayerTrait> {\n return {\n // NormalizedHasLayoutTrait\n layoutGrow: node.layoutGrow,\n layoutAlign: node.layoutAlign,\n layoutSizingHorizontal: node.layoutSizingHorizontal,\n layoutSizingVertical: node.layoutSizingVertical,\n absoluteBoundingBox: node.absoluteBoundingBox,\n relativeTransform: node.relativeTransform,\n layoutPositioning: node.layoutPositioning,\n minHeight: node.minHeight,\n minWidth: node.minWidth,\n maxHeight: node.maxHeight,\n maxWidth: node.maxWidth,\n\n // NormalizedHasGeometryTrait\n fills: normalizePaints(node.fills),\n fillStyleKey: node.styles?.[\"fill\"] ? ctx.styles[node.styles[\"fill\"]]?.key : undefined,\n strokes: normalizePaints(node.strokes),\n strokeWeight: node.strokeWeight,\n\n // NormalizedHasEffectsTrait\n ...normalizeEffectProps(node),\n };\n }\n\n function normalizeAutolayoutProps(\n node: Pick<\n FigmaRestSpec.FrameNode,\n | \"layoutMode\"\n | \"layoutWrap\"\n | \"paddingLeft\"\n | \"paddingRight\"\n | \"paddingTop\"\n | \"paddingBottom\"\n | \"primaryAxisAlignItems\"\n | \"primaryAxisSizingMode\"\n | \"counterAxisAlignItems\"\n | \"counterAxisSizingMode\"\n | \"itemSpacing\"\n | \"counterAxisSpacing\"\n >,\n ): NormalizedHasFramePropertiesTrait {\n return {\n layoutMode: node.layoutMode,\n layoutWrap: node.layoutWrap,\n paddingLeft: node.paddingLeft,\n paddingRight: node.paddingRight,\n paddingTop: node.paddingTop,\n paddingBottom: node.paddingBottom,\n primaryAxisAlignItems: node.primaryAxisAlignItems,\n primaryAxisSizingMode: node.primaryAxisSizingMode,\n counterAxisAlignItems: node.counterAxisAlignItems,\n counterAxisSizingMode: node.counterAxisSizingMode,\n itemSpacing: node.itemSpacing,\n counterAxisSpacing: node.counterAxisSpacing,\n };\n }\n\n function normalizeFrameNode(node: FigmaRestSpec.FrameNode): NormalizedFrameNode {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node.boundVariables),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait, NormalizedHasFramePropertiesTrait\n ...normalizeShapeProps(node),\n\n // NormalizedCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasFramePropertiesTrait\n ...normalizeAutolayoutProps(node),\n\n // NormalizedHasChildrenTrait\n children: normalizeNodes(node.children),\n };\n }\n\n function normalizeSlotNode(node: FigmaRestSpec.SlotNode): NormalizedSlotNode {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node.boundVariables),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait, NormalizedHasFramePropertiesTrait\n ...normalizeShapeProps(node),\n\n // NormalizedCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasFramePropertiesTrait\n ...normalizeAutolayoutProps(node),\n\n // NormalizedHasChildrenTrait\n children: normalizeNodes(node.children),\n\n // NormalizedSlotNode specific\n ...(node.componentPropertyReferences?.[\"slotContentId\"] && {\n componentPropertyReferences: node.componentPropertyReferences,\n }),\n };\n }\n\n function normalizeRectangleNode(node: FigmaRestSpec.RectangleNode): NormalizedRectangleNode {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node.boundVariables),\n\n // NormalizedCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...normalizeShapeProps(node),\n };\n }\n\n function normalizeTextNode(node: FigmaRestSpec.TextNode): NormalizedTextNode {\n // Convert TypeStyle to NormalizedTextSegment.style format\n function normalizeSegmentStyle(\n typeStyle: FigmaRestSpec.TypeStyle,\n ): NormalizedTextSegment[\"style\"] {\n return {\n fontFamily: typeStyle.fontFamily,\n fontWeight: typeStyle.fontWeight,\n fontSize: typeStyle.fontSize,\n italic: typeStyle.italic,\n textDecoration: typeStyle.textDecoration,\n letterSpacing: typeStyle.letterSpacing,\n lineHeight: typeStyle.lineHeightPx,\n };\n }\n\n // Function to segment a text node based on style overrides\n function segmentTextNode(textNode: FigmaRestSpec.TextNode): NormalizedTextSegment[] {\n const segments: NormalizedTextSegment[] = [];\n const characters = textNode.characters;\n const styleOverrides = textNode.characterStyleOverrides || [];\n const styleTable = textNode.styleOverrideTable || {};\n\n // If no style overrides, return the entire text as one segment\n if (!styleOverrides.length) {\n return [\n {\n characters: characters,\n start: 0,\n end: characters.length,\n style: normalizeSegmentStyle(textNode.style),\n },\n ];\n }\n\n let currentSegment: NormalizedTextSegment = {\n characters: \"\",\n start: 0,\n end: 0,\n style: {},\n };\n\n let currentStyleId: string | null = null;\n\n for (let i = 0; i < characters.length; i++) {\n const styleId = styleOverrides[i]?.toString() || null;\n\n // If style changes or it's the first character\n if (styleId !== currentStyleId || i === 0) {\n // Add the previous segment if it exists\n if (i > 0) {\n currentSegment.end = i;\n currentSegment.characters = characters.substring(\n currentSegment.start,\n currentSegment.end,\n );\n segments.push({ ...currentSegment });\n }\n\n // Start a new segment\n currentStyleId = styleId;\n currentSegment = {\n characters: \"\",\n start: i,\n end: 0,\n style: styleId && styleTable[styleId] ? normalizeSegmentStyle(styleTable[styleId]) : {},\n };\n }\n }\n\n // Add the last segment\n if (currentSegment.start < characters.length) {\n currentSegment.end = characters.length;\n currentSegment.characters = characters.substring(currentSegment.start, currentSegment.end);\n segments.push(currentSegment);\n }\n\n return segments;\n }\n\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node.boundVariables),\n\n // NormalizedTypePropertiesTrait\n style: node.style, // this style is the style of the first segment\n characters: node.characters,\n textStyleKey: node.styles?.[\"text\"] ? ctx.styles[node.styles[\"text\"]]?.key : undefined,\n segments: segmentTextNode(node),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...normalizeShapeProps(node),\n };\n }\n\n function normalizeComponentNode(node: FigmaRestSpec.ComponentNode): NormalizedComponentNode {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node.boundVariables),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...normalizeShapeProps(node),\n\n // NormalizedHasCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasFramePropertiesTrait\n ...normalizeAutolayoutProps(node),\n\n // NormalizedHasChildrenTrait\n children: normalizeNodes(node.children),\n };\n }\n\n function normalizeInstanceNode(node: FigmaRestSpec.InstanceNode): NormalizedInstanceNode {\n const mainComponent = ctx.components[node.componentId];\n if (!mainComponent) {\n throw new Error(`Component ${node.componentId} not found`);\n }\n\n const componentSet = mainComponent.componentSetId\n ? ctx.componentSets[mainComponent.componentSetId]\n : undefined;\n\n const componentProperties: NormalizedInstanceNode[\"componentProperties\"] = {};\n\n for (const [key, value] of Object.entries(node.componentProperties ?? {})) {\n componentProperties[key] = value;\n\n if (value.type === \"INSTANCE_SWAP\") {\n // unless value.type === \"BOOLEAN\", value.value is string\n const swappedComponent = ctx.components[value.value as string];\n\n if (swappedComponent) {\n componentProperties[key].componentKey = swappedComponent.key;\n\n const swappedComponentSet = swappedComponent?.componentSetId\n ? ctx.componentSets[swappedComponent.componentSetId]\n : undefined;\n\n if (swappedComponentSet) {\n componentProperties[key].componentSetKey = swappedComponentSet.key;\n }\n }\n }\n }\n\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node.boundVariables),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...normalizeShapeProps(node),\n\n // NormalizedCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasFramePropertiesTrait\n ...normalizeAutolayoutProps(node),\n\n // NormalizedHasChildrenTrait\n children: normalizeNodes(node.children),\n\n // NormalizedInstanceNode specific\n componentProperties,\n componentKey: mainComponent.key,\n componentSetKey: componentSet?.key,\n overrides: node.overrides,\n };\n }\n\n function normalizeVectorNode(node: FigmaRestSpec.VectorNode): NormalizedVectorNode {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node.boundVariables),\n\n // NormalizedCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...normalizeShapeProps(node),\n };\n }\n\n function normalizeBooleanOperationNode(\n node: FigmaRestSpec.BooleanOperationNode,\n ): NormalizedBooleanOperationNode {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node.boundVariables),\n\n // NormalizedHasLayoutTrait\n layoutGrow: node.layoutGrow,\n layoutAlign: node.layoutAlign,\n layoutSizingHorizontal: node.layoutSizingHorizontal,\n layoutSizingVertical: node.layoutSizingVertical,\n absoluteBoundingBox: node.absoluteBoundingBox,\n relativeTransform: node.relativeTransform,\n layoutPositioning: node.layoutPositioning,\n minHeight: node.minHeight,\n minWidth: node.minWidth,\n maxHeight: node.maxHeight,\n maxWidth: node.maxWidth,\n\n // NormalizedHasGeometryTrait\n fills: normalizePaints(node.fills),\n fillStyleKey: node.styles?.[\"fill\"] ? ctx.styles[node.styles[\"fill\"]]?.key : undefined,\n strokes: normalizePaints(node.strokes),\n strokeWeight: node.strokeWeight,\n\n // NormalizedHasEffectsTrait\n ...normalizeEffectProps(node),\n\n // NormalizedHasChildrenTrait\n children: normalizeNodes(node.children),\n };\n }\n\n function normalizeGroupNodeAsFrameNode(node: FigmaRestSpec.GroupNode): NormalizedFrameNode {\n return {\n // NormalizedIsLayerTrait\n type: \"FRAME\",\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node.boundVariables),\n\n // NormalizedHasLayoutTrait\n layoutGrow: node.layoutGrow,\n layoutAlign: node.layoutAlign,\n layoutSizingHorizontal: node.layoutSizingHorizontal,\n layoutSizingVertical: node.layoutSizingVertical,\n absoluteBoundingBox: node.absoluteBoundingBox,\n relativeTransform: node.relativeTransform,\n layoutPositioning: node.layoutPositioning,\n minHeight: node.minHeight,\n minWidth: node.minWidth,\n maxHeight: node.maxHeight,\n maxWidth: node.maxWidth,\n\n // NormalizedHasGeometryTrait\n fills: [],\n fillStyleKey: undefined,\n strokes: [],\n strokeWeight: undefined,\n\n // NormalizedHasEffectsTrait\n effects: [],\n effectStyleKey: undefined,\n\n // NormalizedCornerTrait\n cornerRadius: undefined,\n rectangleCornerRadii: undefined,\n\n // NormalizedHasFramePropertiesTrait\n // these are undefined compared to from-plugin normalizer\n // since inferredAutoLayout isn't available in REST API\n layoutMode: undefined,\n layoutWrap: undefined,\n paddingLeft: undefined,\n paddingRight: undefined,\n paddingTop: undefined,\n paddingBottom: undefined,\n primaryAxisAlignItems: undefined,\n primaryAxisSizingMode: undefined,\n counterAxisAlignItems: undefined,\n counterAxisSizingMode: undefined,\n itemSpacing: undefined,\n counterAxisSpacing: undefined,\n\n // NormalizedHasChildrenTrait\n children: normalizeNodes(node.children),\n };\n }\n\n return normalizeNode;\n}\n","/**\n * from-plugin is guaranteed to be run in the Figma Plugin environment\n * so we can use the Plugin API types directly (figma.getNodeByIdAsync, node.getMainComponentAsync etc)\n * however it could be better to make users can DI later\n */\n\nimport type {\n NormalizedSceneNode,\n NormalizedFrameNode,\n NormalizedRectangleNode,\n NormalizedTextNode,\n NormalizedComponentNode,\n NormalizedInstanceNode,\n NormalizedSlotNode,\n NormalizedVectorNode,\n NormalizedBooleanOperationNode,\n NormalizedHasEffectsTrait,\n NormalizedShadow,\n NormalizedDefaultShapeTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedCornerTrait,\n NormalizedIsLayerTrait,\n NormalizedPaint,\n NormalizedTextSegment,\n} from \"./types\";\nimport { convertTransformToGradientHandles } from \"@/utils/figma-gradient\";\n\nexport function createPluginNormalizer(): (node: SceneNode) => Promise<NormalizedSceneNode> {\n async function normalizeNodes(nodes: readonly SceneNode[]): Promise<NormalizedSceneNode[]> {\n return Promise.all(nodes.filter((node) => node.visible).map(normalizeNode));\n }\n\n async function normalizeNode(node: SceneNode): Promise<NormalizedSceneNode> {\n switch (node.type) {\n case \"FRAME\":\n return normalizeFrameNode(node);\n case \"RECTANGLE\":\n return normalizeRectangleNode(node);\n case \"TEXT\":\n return normalizeTextNode(node);\n case \"COMPONENT\":\n return normalizeComponentNode(node);\n case \"INSTANCE\":\n return normalizeInstanceNode(node);\n case \"SLOT\":\n return normalizeSlotNode(node);\n case \"VECTOR\":\n return normalizeVectorNode(node);\n case \"BOOLEAN_OPERATION\":\n return normalizeBooleanOperationNode(node);\n case \"GROUP\":\n return normalizeGroupNodeAsFrameNode(node);\n default:\n return {\n type: \"UNHANDLED\",\n id: node.id,\n original: node,\n };\n }\n }\n\n /**\n * Pick specific fields from boundVariables\n */\n function normalizeBoundVariables({\n boundVariables,\n }: Pick<FrameNode, \"boundVariables\">): NormalizedIsLayerTrait[\"boundVariables\"] {\n if (!boundVariables) return undefined;\n\n return {\n fills: boundVariables.fills,\n strokes: boundVariables.strokes,\n itemSpacing: boundVariables.itemSpacing,\n counterAxisSpacing: boundVariables.counterAxisSpacing,\n topLeftRadius: boundVariables.topLeftRadius,\n topRightRadius: boundVariables.topRightRadius,\n bottomLeftRadius: boundVariables.bottomLeftRadius,\n bottomRightRadius: boundVariables.bottomRightRadius,\n paddingTop: boundVariables.paddingTop,\n paddingRight: boundVariables.paddingRight,\n paddingBottom: boundVariables.paddingBottom,\n paddingLeft: boundVariables.paddingLeft,\n minWidth: boundVariables.minWidth,\n maxWidth: boundVariables.maxWidth,\n minHeight: boundVariables.minHeight,\n maxHeight: boundVariables.maxHeight,\n fontSize: boundVariables.fontSize,\n fontWeight: boundVariables.fontWeight,\n lineHeight: boundVariables.lineHeight,\n size: {\n x: boundVariables.width,\n y: boundVariables.height,\n },\n };\n }\n\n function normalizeSolidPaint(paint: SolidPaint): NormalizedPaint {\n return {\n type: paint.type,\n color: {\n r: paint.color.r,\n g: paint.color.g,\n b: paint.color.b,\n a: paint.opacity ?? 1,\n },\n visible: paint.visible,\n blendMode: paint.blendMode ?? \"NORMAL\",\n opacity: paint.opacity,\n boundVariables: paint.boundVariables,\n };\n }\n\n function normalizePaint(paint: Paint): NormalizedPaint {\n switch (paint.type) {\n case \"SOLID\":\n return normalizeSolidPaint(paint);\n case \"IMAGE\":\n return {\n type: \"IMAGE\",\n scaleMode: paint.scaleMode === \"CROP\" ? \"STRETCH\" : paint.scaleMode,\n imageTransform: paint.imageTransform,\n scalingFactor: paint.scalingFactor,\n filters: paint.filters,\n rotation: paint.rotation,\n imageRef: paint.imageHash ?? \"\",\n blendMode: paint.blendMode ?? \"NORMAL\",\n visible: paint.visible,\n opacity: paint.opacity,\n };\n case \"GRADIENT_LINEAR\":\n case \"GRADIENT_RADIAL\":\n case \"GRADIENT_ANGULAR\":\n case \"GRADIENT_DIAMOND\":\n return {\n type: paint.type,\n gradientStops: [...paint.gradientStops],\n visible: paint.visible,\n opacity: paint.opacity,\n blendMode: paint.blendMode ?? \"NORMAL\",\n gradientHandlePositions: convertTransformToGradientHandles(paint.gradientTransform),\n };\n default:\n throw new Error(`Unimplemented paint type: ${paint.type}`);\n }\n }\n\n function normalizePaints(fills: readonly Paint[] | PluginAPI[\"mixed\"]): NormalizedPaint[] {\n if (fills === figma.mixed) {\n console.warn(\"Mixed fills are not supported\");\n\n return [];\n }\n\n return fills.filter((paint) => paint.visible !== false).map(normalizePaint);\n }\n\n function normalizeRadiusProps(\n node: Pick<\n RectangleNode,\n \"cornerRadius\" | \"topLeftRadius\" | \"topRightRadius\" | \"bottomRightRadius\" | \"bottomLeftRadius\"\n >,\n ): NormalizedCornerTrait {\n return {\n cornerRadius: node.cornerRadius === figma.mixed ? undefined : node.cornerRadius,\n rectangleCornerRadii: [\n node.topLeftRadius,\n node.topRightRadius,\n node.bottomRightRadius,\n node.bottomLeftRadius,\n ],\n };\n }\n\n async function normalizeEffectProps(\n node: Pick<RectangleNode, \"effects\" | \"effectStyleId\">,\n ): Promise<NormalizedHasEffectsTrait> {\n const effectStyleKey =\n typeof node.effectStyleId === \"string\"\n ? (await figma.getStyleByIdAsync(node.effectStyleId))?.key\n : undefined;\n\n const effects = node.effects\n .filter((effect): effect is DropShadowEffect | InnerShadowEffect => {\n if (!effect.visible) return false;\n\n return effect.type === \"DROP_SHADOW\" || effect.type === \"INNER_SHADOW\";\n })\n .map(({ blendMode, visible, ...rest }): NormalizedShadow => rest);\n\n return {\n ...(effectStyleKey ? { effectStyleKey } : {}),\n effects,\n };\n }\n\n async function normalizeShapeProps(\n node: Pick<\n RectangleNode,\n | \"fills\"\n | \"fillStyleId\"\n | \"strokes\"\n | \"strokeWeight\"\n | \"layoutGrow\"\n | \"layoutAlign\"\n | \"layoutSizingHorizontal\"\n | \"layoutSizingVertical\"\n | \"absoluteBoundingBox\"\n | \"relativeTransform\"\n | \"layoutPositioning\"\n | \"minHeight\"\n | \"minWidth\"\n | \"maxHeight\"\n | \"maxWidth\"\n | \"effects\"\n | \"effectStyleId\"\n > &\n Partial<Pick<FrameNode, \"inferredAutoLayout\">>,\n ): Promise<Omit<NormalizedDefaultShapeTrait, keyof NormalizedIsLayerTrait>> {\n const fillStyleKey =\n typeof node.fillStyleId === \"string\"\n ? (await figma.getStyleByIdAsync(node.fillStyleId))?.key\n : undefined;\n\n return {\n // NormalizedHasLayoutTrait\n layoutGrow: (node.inferredAutoLayout?.layoutGrow ?? node.layoutGrow) as 0 | 1 | undefined,\n layoutAlign: node.inferredAutoLayout?.layoutAlign ?? node.layoutAlign,\n layoutSizingHorizontal: node.layoutSizingHorizontal,\n layoutSizingVertical: node.layoutSizingVertical,\n absoluteBoundingBox: node.absoluteBoundingBox,\n relativeTransform: node.relativeTransform,\n layoutPositioning: node.layoutPositioning,\n minHeight: node.minHeight ?? undefined,\n minWidth: node.minWidth ?? undefined,\n maxHeight: node.maxHeight ?? undefined,\n maxWidth: node.maxWidth ?? undefined,\n\n // NormalizedHasGeometryTrait\n fills: await normalizePaints(node.fills),\n fillStyleKey,\n strokes: await normalizePaints(node.strokes),\n strokeWeight: node.strokeWeight === figma.mixed ? undefined : node.strokeWeight,\n\n // NormalizedHasEffectsTrait\n ...(await normalizeEffectProps(node)),\n };\n }\n\n async function normalizeAutolayoutProps(\n node: Omit<FrameNode, \"type\" | \"clone\">,\n ): Promise<NormalizedHasFramePropertiesTrait> {\n return {\n layoutMode: node.inferredAutoLayout?.layoutMode ?? node.layoutMode,\n layoutWrap: node.inferredAutoLayout?.layoutWrap ?? node.layoutWrap,\n paddingLeft: node.inferredAutoLayout?.paddingLeft ?? node.paddingLeft,\n paddingRight: node.inferredAutoLayout?.paddingRight ?? node.paddingRight,\n paddingTop: node.inferredAutoLayout?.paddingTop ?? node.paddingTop,\n paddingBottom: node.inferredAutoLayout?.paddingBottom ?? node.paddingBottom,\n primaryAxisAlignItems:\n node.inferredAutoLayout?.primaryAxisAlignItems ?? node.primaryAxisAlignItems,\n counterAxisAlignItems:\n node.inferredAutoLayout?.counterAxisAlignItems ?? node.counterAxisAlignItems,\n primaryAxisSizingMode:\n node.inferredAutoLayout?.primaryAxisSizingMode ?? node.primaryAxisSizingMode,\n counterAxisSizingMode:\n node.inferredAutoLayout?.counterAxisSizingMode ?? node.counterAxisSizingMode,\n itemSpacing: node.inferredAutoLayout?.itemSpacing ?? node.itemSpacing,\n counterAxisSpacing:\n node.inferredAutoLayout?.counterAxisSpacing ?? node.counterAxisSpacing ?? undefined,\n };\n }\n\n async function normalizeFrameNode(node: FrameNode): Promise<NormalizedFrameNode> {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...(await normalizeShapeProps(node)),\n\n // NormalizedCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasFramePropertiesTrait\n ...(await normalizeAutolayoutProps(node)),\n\n // NormalizedHasChildrenTrait\n children: await normalizeNodes(node.children),\n };\n }\n\n async function normalizeSlotNode(node: SlotNode): Promise<NormalizedSlotNode> {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...(await normalizeShapeProps(node)),\n\n // NormalizedCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasFramePropertiesTrait\n ...(await normalizeAutolayoutProps(node)),\n\n // NormalizedHasChildrenTrait\n children: await normalizeNodes(node.children),\n\n // NormalizedSlotNode specific\n // Plugin API types don't include \"slotContentId\" in componentPropertyReferences yet\n ...((node.componentPropertyReferences as Record<string, string> | null)?.[\n \"slotContentId\"\n ] && {\n componentPropertyReferences: node.componentPropertyReferences as Record<string, string>,\n }),\n };\n }\n\n async function normalizeRectangleNode(node: RectangleNode): Promise<NormalizedRectangleNode> {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node),\n\n // NormalizedCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...(await normalizeShapeProps(node)),\n };\n }\n\n async function normalizeTextNode(node: TextNode): Promise<NormalizedTextNode> {\n const segments = node.getStyledTextSegments([\n \"fontName\",\n \"fontWeight\",\n \"fontSize\",\n \"letterSpacing\",\n \"lineHeight\",\n \"paragraphSpacing\",\n \"textStyleId\",\n \"fills\",\n \"boundVariables\",\n \"textDecoration\",\n ]);\n const first = segments[0];\n\n const textStyleKey =\n typeof node.textStyleId === \"string\"\n ? (await figma.getStyleByIdAsync(node.textStyleId))?.key\n : undefined;\n\n const normalizeLetterSpacing = (\n letterSpacing: LetterSpacing,\n fontSize: number,\n ): NormalizedTextSegment[\"style\"][\"letterSpacing\"] => {\n if (letterSpacing.unit === \"PIXELS\") return letterSpacing.value;\n if (letterSpacing.unit === \"PERCENT\") return (fontSize * letterSpacing.value) / 100;\n\n return undefined;\n };\n\n const normalizeLineHeight = (\n lineHeight: LineHeight,\n fontSize: number,\n ): NormalizedTextSegment[\"style\"][\"lineHeight\"] => {\n if (lineHeight.unit === \"PIXELS\") return lineHeight.value;\n if (lineHeight.unit === \"PERCENT\") return (fontSize * lineHeight.value) / 100;\n\n return undefined;\n };\n\n const isItalic = (fontName: FontName): boolean => {\n // {\n // family: \"SF Mono\",\n // style: \"Bold Italic\"\n // }\n return fontName.style.toLowerCase().includes(\"italic\");\n };\n\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node),\n\n // NormalizedTypePropertiesTrait\n // NOTE: this normalization is incomplete compared to from-rest.ts normalizer\n style: {\n fontFamily: first.fontName.family,\n fontPostScriptName: null,\n fontStyle: first.fontName.style,\n italic: isItalic(first.fontName),\n fontWeight: first.fontWeight,\n fontSize: first.fontSize,\n textAlignHorizontal: node.textAlignHorizontal,\n textAlignVertical: node.textAlignVertical,\n letterSpacing: normalizeLetterSpacing(first.letterSpacing, first.fontSize),\n paragraphSpacing: first.paragraphSpacing,\n textDecoration: first.textDecoration,\n lineHeightPx: normalizeLineHeight(first.lineHeight, first.fontSize),\n lineHeightUnit:\n first.lineHeight.unit === \"PIXELS\"\n ? \"PIXELS\"\n : first.lineHeight.unit === \"PERCENT\"\n ? \"FONT_SIZE_%\"\n : undefined,\n boundVariables: first.boundVariables,\n maxLines: node.maxLines ?? undefined,\n },\n characters: node.characters,\n textStyleKey,\n segments: segments.map((segment) => ({\n characters: segment.characters,\n start: segment.start,\n end: segment.end,\n style: {\n fontSize: segment.fontSize,\n fontWeight: segment.fontWeight,\n fontFamily: segment.fontName.family,\n italic: isItalic(segment.fontName),\n letterSpacing: normalizeLetterSpacing(segment.letterSpacing, segment.fontSize),\n lineHeight: normalizeLineHeight(segment.lineHeight, segment.fontSize),\n textDecoration: segment.textDecoration,\n },\n })),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...(await normalizeShapeProps(node)),\n };\n }\n\n async function normalizeComponentNode(node: ComponentNode): Promise<NormalizedComponentNode> {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...(await normalizeShapeProps(node)),\n\n // NormalizedCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasFramePropertiesTrait\n ...(await normalizeAutolayoutProps(node)),\n\n // NormalizedHasChildrenTrait\n children: await normalizeNodes(node.children),\n };\n }\n\n async function normalizeInstanceNode(node: InstanceNode): Promise<NormalizedInstanceNode> {\n const mainComponent = await node.getMainComponentAsync();\n if (!mainComponent) {\n throw new Error(\"Instance node has no main component\");\n }\n\n const componentProperties: NormalizedInstanceNode[\"componentProperties\"] = {};\n\n for (const [key, value] of Object.entries(node.componentProperties)) {\n componentProperties[key] = value;\n\n if (value.type === \"INSTANCE_SWAP\") {\n // unless value.type === \"BOOLEAN\", value.value is string\n const swappedComponent = (await figma.getNodeByIdAsync(\n value.value as string,\n )) as ComponentNode;\n\n if (swappedComponent) {\n componentProperties[key].componentKey = swappedComponent.key;\n\n if (swappedComponent.parent?.type === \"COMPONENT_SET\") {\n componentProperties[key].componentSetKey = swappedComponent.parent.key;\n }\n }\n }\n }\n\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node),\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...(await normalizeShapeProps(node)),\n\n // NormalizedCornerTrait\n ...normalizeRadiusProps(node),\n\n // NormalizedHasFramePropertiesTrait\n ...(await normalizeAutolayoutProps(node)),\n\n // NormalizedHasChildrenTrait\n children: await normalizeNodes(node.children),\n\n // NormalizedInstanceNode specific\n componentProperties,\n componentKey: mainComponent.key,\n componentSetKey:\n mainComponent.parent?.type === \"COMPONENT_SET\" ? mainComponent.parent.key : undefined,\n overrides: node.overrides,\n };\n }\n\n async function normalizeVectorNode(node: VectorNode): Promise<NormalizedVectorNode> {\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node),\n\n // NormalizedCornerTrait\n cornerRadius: node.cornerRadius === figma.mixed ? undefined : node.cornerRadius,\n rectangleCornerRadii: undefined, // VectorNode does not have individual corner radii\n\n // NormalizedHasLayoutTrait, NormalizedHasGeometryTrait, NormalizedHasEffectsTrait\n ...(await normalizeShapeProps(node)),\n };\n }\n\n async function normalizeBooleanOperationNode(\n node: BooleanOperationNode,\n ): Promise<NormalizedBooleanOperationNode> {\n const fillStyleKey =\n typeof node.fillStyleId === \"string\"\n ? (await figma.getStyleByIdAsync(node.fillStyleId))?.key\n : undefined;\n\n return {\n // NormalizedIsLayerTrait\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node),\n\n // NormalizedHasLayoutTrait\n layoutGrow: node.layoutGrow as 0 | 1 | undefined,\n layoutAlign: node.layoutAlign,\n layoutSizingHorizontal: node.layoutSizingHorizontal,\n layoutSizingVertical: node.layoutSizingVertical,\n absoluteBoundingBox: node.absoluteBoundingBox,\n relativeTransform: node.relativeTransform,\n layoutPositioning: node.layoutPositioning,\n minHeight: node.minHeight ?? undefined,\n minWidth: node.minWidth ?? undefined,\n maxHeight: node.maxHeight ?? undefined,\n maxWidth: node.maxWidth ?? undefined,\n\n // NormalizedHasGeometryTrait\n fills: await normalizePaints(node.fills),\n fillStyleKey,\n strokes: await normalizePaints(node.strokes),\n strokeWeight: node.strokeWeight === figma.mixed ? undefined : node.strokeWeight,\n\n // NormalizedHasEffectsTrait\n ...(await normalizeEffectProps(node)),\n\n // NormalizedHasChildrenTrait\n children: await normalizeNodes(node.children),\n };\n }\n\n async function normalizeGroupNodeAsFrameNode(\n node: GroupNode & { inferredAutoLayout?: FrameNode[\"inferredAutoLayout\"] },\n ): Promise<NormalizedFrameNode> {\n return {\n // NormalizedIsLayerTrait\n type: \"FRAME\",\n id: node.id,\n name: node.name,\n boundVariables: normalizeBoundVariables(node),\n\n // NormalizedHasLayoutTrait\n layoutGrow: (node.inferredAutoLayout?.layoutGrow ?? node.layoutGrow) as 0 | 1 | undefined,\n layoutAlign: node.inferredAutoLayout?.layoutAlign ?? node.layoutAlign,\n layoutSizingHorizontal: node.layoutSizingHorizontal,\n layoutSizingVertical: node.layoutSizingVertical,\n absoluteBoundingBox: node.absoluteBoundingBox,\n relativeTransform: node.relativeTransform,\n layoutPositioning: node.layoutPositioning,\n minHeight: node.minHeight ?? undefined,\n minWidth: node.minWidth ?? undefined,\n maxHeight: node.maxHeight ?? undefined,\n maxWidth: node.maxWidth ?? undefined,\n\n // NormalizedHasGeometryTrait\n fills: [],\n fillStyleKey: undefined,\n strokes: [],\n strokeWeight: undefined,\n\n // NormalizedHasEffectsTrait\n effects: [],\n effectStyleKey: undefined,\n\n // NormalizedCornerTrait\n cornerRadius: undefined,\n rectangleCornerRadii: undefined,\n\n // NormalizedHasFramePropertiesTrait\n layoutMode: node.inferredAutoLayout?.layoutMode,\n layoutWrap: node.inferredAutoLayout?.layoutWrap,\n paddingLeft: node.inferredAutoLayout?.paddingLeft,\n paddingRight: node.inferredAutoLayout?.paddingRight,\n paddingTop: node.inferredAutoLayout?.paddingTop,\n paddingBottom: node.inferredAutoLayout?.paddingBottom,\n primaryAxisAlignItems: node.inferredAutoLayout?.primaryAxisAlignItems,\n counterAxisAlignItems: node.inferredAutoLayout?.counterAxisAlignItems,\n primaryAxisSizingMode: node.inferredAutoLayout?.primaryAxisSizingMode,\n counterAxisSizingMode: node.inferredAutoLayout?.counterAxisSizingMode,\n itemSpacing: node.inferredAutoLayout?.itemSpacing,\n counterAxisSpacing: node.inferredAutoLayout?.counterAxisSpacing ?? undefined,\n\n // NormalizedHasChildrenTrait\n children: await normalizeNodes(node.children),\n };\n }\n\n return normalizeNode;\n}\n","import type { ComponentPropertyDefinition } from \"@/codegen\";\n\nexport interface ComponentMetadata {\n name: string;\n key: string;\n componentPropertyDefinitions: Record<string, ComponentPropertyDefinition>;\n}\n","export interface IconData {\n name: string;\n type: \"monochrome\" | \"multicolor\";\n weight?: string;\n}\n","import type {\n LocalVariable,\n LocalVariableCollection,\n VariableAlias,\n VariableResolvedDataType,\n} from \"@figma/rest-api-spec\";\n\nexport type Variable = LocalVariable;\n\nexport type VariableCollection = LocalVariableCollection;\n\nexport type VariableType = VariableResolvedDataType;\n\nexport type VariableValue = Variable[\"valuesByMode\"][string];\n\nexport type VariableValueResolved = Exclude<VariableValue, VariableAlias>;\n\nexport type { VariableScope } from \"@figma/rest-api-spec\";\n","import type { Variable, VariableCollection } from \"./variable.interface\";\n\nexport interface VariableRepository {\n getVariableList(): Variable[];\n getVariableCollectionList(): VariableCollection[];\n findVariableByKey(key: string): Variable | undefined;\n findVariableById(id: string): Variable | undefined;\n findVariableByName(name: string): Variable | undefined;\n findVariableCollectionByKey(key: string): VariableCollection | undefined;\n findVariableCollectionById(id: string): VariableCollection | undefined;\n}\n\nexport function createStaticVariableRepository({\n variables,\n variableCollections,\n}: {\n variables: Record<string, Variable>;\n variableCollections: Record<string, VariableCollection>;\n}): VariableRepository {\n const variablesKeyMap = new Map<string, Variable>();\n const variablesIdMap = new Map<string, Variable>();\n const variablesNameMap = new Map<string, Variable>();\n const variableCollectionsKeyMap = new Map<string, VariableCollection>();\n const variableCollectionsIdMap = new Map<string, VariableCollection>();\n\n for (const variable of Object.values(variables)) {\n if (variable.remote) {\n continue;\n }\n\n variablesKeyMap.set(variable.key, variable);\n variablesIdMap.set(variable.id, variable);\n variablesNameMap.set(variable.name, variable);\n }\n\n for (const variableCollection of Object.values(variableCollections)) {\n if (variableCollection.remote) {\n continue;\n }\n\n variableCollectionsKeyMap.set(variableCollection.key, variableCollection);\n variableCollectionsIdMap.set(variableCollection.id, variableCollection);\n }\n\n const variablesList = [...variablesKeyMap.values()];\n const variableCollectionsList = [...variableCollectionsKeyMap.values()];\n\n return {\n getVariableList: () => variablesList,\n getVariableCollectionList: () => variableCollectionsList,\n findVariableByName: (name: string) => variablesNameMap.get(name),\n findVariableByKey: (key: string) => variablesKeyMap.get(key),\n findVariableById: (id: string) => variablesIdMap.get(id),\n findVariableCollectionByKey: (key: string) => variableCollectionsKeyMap.get(key),\n findVariableCollectionById: (id: string) => variableCollectionsIdMap.get(id),\n };\n}\n","import type { Style as FigmaStyle, StyleType as FigmaStyleType } from \"@figma/rest-api-spec\";\n\nexport type Style = FigmaStyle;\n\nexport type StyleType = FigmaStyleType;\n","import type { Style } from \"./style.interface\";\n\nexport interface StyleRepository {\n getAll(): Style[];\n getTextStyles(): Style[];\n getColorStyles(): Style[];\n findOneByKey(key: string): Style | undefined;\n findOneByName(name: string): Style | undefined;\n}\n\nexport function createStaticStyleRepository(styles: Style[]): StyleRepository {\n const stylesMap = new Map<string, Style>();\n const stylesNameMap = new Map<string, Style>();\n\n for (const style of styles) {\n stylesMap.set(style.key, style);\n stylesNameMap.set(style.name, style);\n }\n\n return {\n getAll: () => styles,\n getTextStyles: () => styles.filter((style) => style.styleType === \"TEXT\"),\n getColorStyles: () => styles.filter((style) => style.styleType === \"FILL\"),\n findOneByKey: (key) => stylesMap.get(key),\n findOneByName: (name) => stylesNameMap.get(name),\n };\n}\n","import type { IconData } from \"./icon.interface\";\n\nexport interface IconRepository {\n getOne(key: string): IconData;\n}\n\nexport function createStaticIconRepository(iconRecord: Record<string, IconData>) {\n return {\n getOne: (key: string) => iconRecord[key],\n };\n}\n","import type { IconData } from \"./icon.interface\";\nimport type { IconRepository } from \"./icon.repository\";\n\nexport interface IconService {\n isAvailable: (componentKey: string) => boolean;\n getOne: (componentKey: string) => IconData;\n}\n\nexport function createIconService({\n iconRepository,\n}: {\n iconRepository: IconRepository;\n}): IconService {\n function isAvailable(componentKey: string) {\n return iconRepository.getOne(componentKey) !== undefined;\n }\n\n function getOne(componentKey: string) {\n return iconRepository.getOne(componentKey);\n }\n\n return {\n isAvailable,\n getOne,\n };\n}\n","import type { StyleRepository } from \"./style.repository\";\n\nexport interface StyleService {\n getSlug: (id: string) => string[] | undefined;\n}\n\n// TODO: inferStyleName 추가해야 함, rest api에서 style value가 제공되지 않고 있어 보류\nexport function createStyleService({\n styleRepository,\n}: {\n styleRepository: StyleRepository;\n}): StyleService {\n function getName(id: string) {\n const style = styleRepository.findOneByKey(id);\n\n if (!style) {\n return undefined;\n }\n\n return style.name;\n }\n\n function getSlug(id: string): string[] | undefined {\n const name = getName(id);\n\n if (!name) {\n return undefined;\n }\n\n return name.split(\"/\");\n }\n\n return {\n getSlug,\n };\n}\n","import {\n isIdenticalVariableValue,\n isInsideScope,\n isVariableAlias,\n sanitizeVariableId,\n} from \"@/utils/figma-variable\";\nimport type { Variable, VariableScope, VariableValueResolved } from \"./variable.interface\";\nimport type { VariableRepository } from \"./variable.repository\";\n\nexport interface VariableService {\n getSlug: (id: string) => string[] | undefined;\n resolveValue: (variable: Variable, mode: string) => VariableValueResolved;\n infer: (value: VariableValueResolved, scope: VariableScope) => Variable | undefined;\n}\n\nexport interface VariableServiceDeps {\n variableRepository: VariableRepository;\n inferCompareFunction: (a: Variable, b: Variable) => number;\n}\n\nexport function createVariableService({\n variableRepository,\n inferCompareFunction,\n}: VariableServiceDeps): VariableService {\n const variables = variableRepository.getVariableList();\n\n // private\n function getName(key: string) {\n const sanitizedId = sanitizeVariableId(key);\n const variable = variableRepository.findVariableByKey(sanitizedId);\n\n if (!variable) {\n return undefined;\n }\n\n return variable.name;\n }\n\n function getDefaultModeId(variable: Variable) {\n const variableCollection = variableRepository.findVariableCollectionById(\n variable.variableCollectionId,\n );\n\n if (!variableCollection) {\n // Variable collection not found: ${variable.variableCollectionId}, falling back to variable.valuesByMode key\n return Object.keys(variable.valuesByMode)[0]!;\n }\n\n return variableCollection.defaultModeId;\n }\n\n // public\n function getSlug(key: string): string[] | undefined {\n const name = getName(key);\n\n if (!name) {\n return undefined;\n }\n\n return name.split(\"/\");\n }\n\n function resolveValue(variable: Variable, mode: string): VariableValueResolved {\n const value = variable.valuesByMode[mode];\n\n if (value === undefined) {\n throw new Error(`Variable value not found: ${variable.id} ${mode}`);\n }\n\n if (isVariableAlias(value)) {\n const resolvedVariable = variableRepository.findVariableById(value.id);\n\n if (!resolvedVariable) {\n throw new Error(`Variable not found: ${value.id}`);\n }\n\n return resolveValue(resolvedVariable, mode);\n }\n\n return value;\n }\n\n function infer(value: VariableValueResolved, scope: VariableScope) {\n // NOTE: We assume that the variable is in the default mode or value is equal between all modes for simplicity.\n const inferredVariables = variables.filter(\n (variable) =>\n isInsideScope(variable, scope) &&\n isIdenticalVariableValue(resolveValue(variable, getDefaultModeId(variable)), value),\n );\n\n const sortedVariables = inferredVariables.sort(inferCompareFunction);\n\n return sortedVariables[0];\n }\n\n return {\n getSlug,\n resolveValue,\n infer,\n };\n}\n","import type { ComponentMetadata } from \"./component.interface\";\n\nexport interface ComponentRepository {\n getOne(key: string): ComponentMetadata | undefined;\n}\n\nexport function createStaticComponentRepository(data: Record<string, ComponentMetadata>) {\n const componentRecord: Record<string, ComponentMetadata> = {};\n Object.values(data).forEach((component) => {\n componentRecord[component.key] = component;\n });\n\n return {\n getOne: (key: string) => componentRecord[key],\n };\n}\n","import { createStaticIconRepository } from \"./icon.repository\";\nimport { createStaticStyleRepository } from \"./style.repository\";\nimport { createStaticVariableRepository } from \"./variable.repository\";\nimport { createStaticComponentRepository } from \"./component.repository\";\nimport { FIGMA_ICONS } from \"./data/__generated__/icons\";\n\n// Archive\nimport { FIGMA_STYLES as FIGMA_STYLES_ARCHIVE } from \"./data/__generated__/archive/styles\";\nimport { FIGMA_VARIABLE_COLLECTIONS as FIGMA_VARIABLE_COLLECTIONS_ARCHIVE } from \"./data/__generated__/archive/variable-collections\";\nimport { FIGMA_VARIABLES as FIGMA_VARIABLES_ARCHIVE } from \"./data/__generated__/archive/variables\";\nimport * as FIGMA_COMPONENTS_ARCHIVE from \"./data/__generated__/archive/component-sets\";\n\n// Current\nimport { FIGMA_STYLES } from \"./data/__generated__/styles\";\nimport { FIGMA_VARIABLE_COLLECTIONS } from \"./data/__generated__/variable-collections\";\nimport { FIGMA_VARIABLES } from \"./data/__generated__/variables\";\nimport * as FIGMA_COMPONENTS from \"./data/__generated__/component-sets\";\n\nexport * from \"./icon.interface\";\nexport * from \"./icon.repository\";\nexport * from \"./icon.service\";\nexport * from \"./style.interface\";\nexport * from \"./style.repository\";\nexport * from \"./style.service\";\nexport * from \"./variable.interface\";\nexport * from \"./variable.repository\";\nexport * from \"./variable.service\";\nexport * from \"./component.interface\";\nexport * from \"./component.repository\";\n\nexport const styleRepository = createStaticStyleRepository([\n ...FIGMA_STYLES_ARCHIVE,\n ...FIGMA_STYLES,\n]);\nexport const variableRepository = createStaticVariableRepository({\n variables: { ...FIGMA_VARIABLES_ARCHIVE, ...FIGMA_VARIABLES },\n variableCollections: { ...FIGMA_VARIABLE_COLLECTIONS_ARCHIVE, ...FIGMA_VARIABLE_COLLECTIONS },\n});\nexport const iconRepository = createStaticIconRepository(FIGMA_ICONS);\nexport const componentRepository = createStaticComponentRepository({\n ...FIGMA_COMPONENTS_ARCHIVE,\n ...FIGMA_COMPONENTS,\n});\n\nexport function getFigmaVariableKey(name: string) {\n return variableRepository.findVariableByName(name)?.key;\n}\n\nexport function getFigmaStyleKey(name: string) {\n return styleRepository.findOneByName(name)?.key;\n}\n\nexport function getFigmaColorVariableNames(scopes: Array<\"fg\" | \"bg\" | \"stroke\" | \"palette\">) {\n const variables = variableRepository.getVariableList();\n return variables\n .filter((variable) =>\n scopes.includes(variable.name.split(\"/\")[0] as \"fg\" | \"bg\" | \"stroke\" | \"palette\"),\n )\n .map((variable) => variable.name);\n}\n"],"names":[],"mappings":";;;;;;AACO;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACA;AACA;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACO;;ACvFP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACzBP;AACA;AACA;AACA;AACA;;AAEO;;ACLA;AACP;AACA;AACA;AACA;;ACLO;AACP;AACA;AACA;AACA;;ACHO;AACA;AACA;AACA;AACA;;ACJA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;;ACZO;AACA;;ACDA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;;ACPA;AACP;AACA;AACO;AACP;AACA;;ACJO;AACP;AACA;AACA;AACO;AACP;AACA;;ACPO;AACP;AACA;AACO;AACP;AACA;;ACJO;AACP;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACO;;ACVA;AACP;AACA;AACO;AACP;AACA;;ACKO;AACA;AACA;AACP;AACA;AACO;AACP;AACA;AACO;AACA;AACA;;;"}