@seed-design/figma 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/codegen/index.cjs +5581 -5168
- package/lib/codegen/index.d.ts +460 -68
- package/lib/codegen/index.d.ts.map +1 -1
- package/lib/codegen/index.js +5581 -5168
- package/lib/codegen/targets/react/index.cjs +6143 -5348
- package/lib/codegen/targets/react/index.d.ts.map +1 -1
- package/lib/codegen/targets/react/index.js +6143 -5348
- package/lib/index.cjs +5567 -5165
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5567 -5165
- package/package.json +5 -4
- package/src/codegen/component-properties.ts +82 -6
- package/src/codegen/core/jsx.ts +14 -2
- package/src/codegen/core/value-resolver.ts +8 -4
- package/src/codegen/targets/react/component/handlers/checkbox.ts +7 -0
- package/src/codegen/targets/react/component/handlers/checkmark.ts +29 -0
- package/src/codegen/targets/react/component/handlers/chip.ts +5 -2
- package/src/codegen/targets/react/component/handlers/divider.ts +8 -2
- package/src/codegen/targets/react/component/handlers/help-bubble.ts +2 -0
- package/src/codegen/targets/react/component/handlers/list-item.ts +155 -0
- package/src/codegen/targets/react/component/handlers/radio-group.ts +31 -0
- package/src/codegen/targets/react/component/handlers/radio-mark.ts +27 -0
- package/src/codegen/targets/react/component/handlers/switch.ts +7 -0
- package/src/codegen/targets/react/component/handlers/tabs.ts +288 -5
- package/src/codegen/targets/react/component/index.ts +12 -2
- package/src/entities/data/__generated__/component-sets/checkbox.d.ts +9 -2
- package/src/entities/data/__generated__/component-sets/checkbox.mjs +9 -2
- package/src/entities/data/__generated__/component-sets/checkmark.d.ts +13 -6
- package/src/entities/data/__generated__/component-sets/checkmark.mjs +13 -6
- package/src/entities/data/__generated__/component-sets/divider.d.ts +2 -2
- package/src/entities/data/__generated__/component-sets/divider.mjs +2 -2
- package/src/entities/data/__generated__/component-sets/help-bubble.d.ts +3 -0
- package/src/entities/data/__generated__/component-sets/help-bubble.mjs +3 -0
- package/src/entities/data/__generated__/component-sets/index.d.ts +2 -0
- package/src/entities/data/__generated__/component-sets/index.mjs +2 -0
- package/src/entities/data/__generated__/component-sets/list-header.d.ts +24 -0
- package/src/entities/data/__generated__/component-sets/list-header.mjs +24 -0
- package/src/entities/data/__generated__/component-sets/list-item.d.ts +152 -0
- package/src/entities/data/__generated__/component-sets/list-item.mjs +152 -0
- package/src/entities/data/__generated__/component-sets/radio-mark.d.ts +7 -0
- package/src/entities/data/__generated__/component-sets/radio-mark.mjs +7 -0
- package/src/entities/data/__generated__/component-sets/radio.d.ts +14 -0
- package/src/entities/data/__generated__/component-sets/radio.mjs +14 -0
- package/src/entities/data/__generated__/component-sets/switch.d.ts +10 -3
- package/src/entities/data/__generated__/component-sets/switch.mjs +10 -3
- package/src/entities/data/__generated__/component-sets/tabs.d.ts +1 -1
- package/src/entities/data/__generated__/component-sets/tabs.mjs +1 -1
- package/src/entities/data/__generated__/variable-collections/index.d.ts +3 -0
- package/src/entities/data/{variable-collections.ts → __generated__/variable-collections/index.mjs} +229 -116
- package/src/entities/data/__generated__/variables/index.d.ts +3 -0
- package/src/entities/data/__generated__/variables/index.mjs +6442 -0
- package/src/entities/index.ts +2 -2
- package/src/entities/data/__generated__/component-sets/inline-banner.d.ts +0 -40
- package/src/entities/data/__generated__/component-sets/inline-banner.mjs +0 -40
- package/src/entities/data/variables.ts +0 -4706
package/lib/index.d.ts.map
CHANGED
|
@@ -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<\n FigmaRestSpec.IsLayerTrait,\n \"type\" | \"id\" | \"name\" | \"boundVariables\"\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 NormalizedHasGeometryTrait = Pick<\n FigmaRestSpec.HasGeometryTrait,\n \"fills\" | \"strokes\" | \"strokeWeight\" | \"styles\"\n> & {\n fillStyleKey?: 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 lineHeight?: number | { unit: string; value: 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\nexport type NormalizedFrameTrait = NormalizedIsLayerTrait &\n NormalizedHasLayoutTrait &\n NormalizedHasGeometryTrait &\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 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","import 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} from \"./types\";\n\nexport interface RestNormalizerContext {\n styles: Record<string, FigmaRestSpec.Style>;\n components: Record<string, FigmaRestSpec.Component>;\n componentSets: Record<string, FigmaRestSpec.ComponentSet>;\n}\n\nexport function createRestNormalizer(ctx: RestNormalizerContext) {\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 if (node.type === \"FRAME\") {\n return normalizeFrameNode(node);\n }\n if (node.type === \"GROUP\") {\n return normalizeGroupNode(node);\n }\n if (node.type === \"RECTANGLE\") {\n return normalizeRectangleNode(node);\n }\n if (node.type === \"VECTOR\") {\n return normalizeVectorNode(node);\n }\n if (node.type === \"BOOLEAN_OPERATION\") {\n return normalizeBooleanOperationNode(node);\n }\n if (node.type === \"TEXT\") {\n return normalizeTextNode(node);\n }\n if (node.type === \"COMPONENT\") {\n return normalizeComponentNode(node);\n }\n if (node.type === \"INSTANCE\") {\n return normalizeInstanceNode(node);\n }\n\n return {\n type: \"UNHANDLED\",\n id: node.id,\n original: node,\n };\n }\n\n function normalizeFrameNode(node: FigmaRestSpec.FrameNode): NormalizedFrameNode {\n return {\n ...node,\n children: normalizeNodes(node.children),\n };\n }\n\n function normalizeGroupNode(node: FigmaRestSpec.GroupNode): NormalizedFrameNode {\n return {\n ...node,\n type: \"FRAME\",\n children: normalizeNodes(node.children),\n };\n }\n\n function normalizeRectangleNode(node: FigmaRestSpec.RectangleNode): NormalizedRectangleNode {\n return node;\n }\n\n function normalizeVectorNode(node: FigmaRestSpec.VectorNode): NormalizedVectorNode {\n return node;\n }\n\n function normalizeBooleanOperationNode(\n node: FigmaRestSpec.BooleanOperationNode,\n ): NormalizedBooleanOperationNode {\n return {\n ...node,\n children: normalizeNodes(node.children),\n };\n }\n\n function normalizeTextNode(node: FigmaRestSpec.TextNode): NormalizedTextNode {\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: 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] || {} : {},\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 ...node,\n textStyleKey: node.styles?.[\"text\"] ? ctx.styles[node.styles[\"text\"]]?.key : undefined,\n segments: segmentTextNode(node),\n };\n }\n\n function normalizeComponentNode(node: FigmaRestSpec.ComponentNode): NormalizedComponentNode {\n return {\n ...node,\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 const componentSet = mainComponent.componentSetId\n ? ctx.componentSets[mainComponent.componentSetId]\n : undefined;\n const componentProperties: NormalizedInstanceNode[\"componentProperties\"] = {};\n\n for (const [key, value] of Object.entries(node.componentProperties ?? {})) {\n componentProperties[key] = value;\n if (value.type === \"INSTANCE_SWAP\") {\n const mainComponent = ctx.components[value.value as string];\n if (mainComponent) {\n componentProperties[key].componentKey = mainComponent.key;\n }\n const mainComponentSet = mainComponent?.componentSetId\n ? ctx.componentSets[mainComponent.componentSetId]\n : undefined;\n if (mainComponentSet) {\n componentProperties[key].componentSetKey = mainComponentSet.key;\n }\n }\n }\n\n return {\n ...node,\n children: normalizeNodes(node.children),\n componentKey: mainComponent.key,\n componentSetKey: componentSet?.key,\n componentProperties,\n };\n }\n\n return normalizeNode;\n}\n","import type * as FigmaRestSpec from \"@figma/rest-api-spec\";\nimport type {\n NormalizedSceneNode,\n NormalizedFrameNode,\n NormalizedRectangleNode,\n NormalizedTextNode,\n NormalizedComponentNode,\n NormalizedInstanceNode,\n NormalizedVectorNode,\n NormalizedBooleanOperationNode,\n} from \"./types\";\nimport { convertTransformToGradientHandles } from \"@/utils/figma-gradient\";\n\nexport function createPluginNormalizer() {\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 if (node.type === \"FRAME\") {\n return normalizeFrameNode(node);\n }\n if (node.type === \"GROUP\") {\n return normalizeGroupNode(node);\n }\n if (node.type === \"RECTANGLE\") {\n return normalizeRectangleNode(node);\n }\n if (node.type === \"VECTOR\") {\n return normalizeVectorNode(node);\n }\n if (node.type === \"BOOLEAN_OPERATION\") {\n return normalizeBooleanOperationNode(node);\n }\n if (node.type === \"TEXT\") {\n return normalizeTextNode(node);\n }\n if (node.type === \"COMPONENT\") {\n return normalizeComponentNode(node);\n }\n if (node.type === \"INSTANCE\") {\n return normalizeInstanceNode(node);\n }\n\n return {\n type: \"UNHANDLED\",\n id: node.id,\n original: node,\n };\n }\n\n async function normalizeFrameNode(node: FrameNode): Promise<NormalizedFrameNode> {\n return {\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: await normalizeBoundVariables(node),\n ...normalizeRadiusProps(node),\n ...(await normalizeAutolayoutProps(node)),\n children: await normalizeNodes(node.children),\n };\n }\n\n async function normalizeGroupNode(\n node: GroupNode & { inferredAutoLayout?: FrameNode[\"inferredAutoLayout\"] },\n ): Promise<NormalizedFrameNode> {\n return {\n type: \"FRAME\",\n id: node.id,\n name: node.name,\n boundVariables: await normalizeBoundVariables(node),\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 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 fills: [],\n strokes: [],\n children: await normalizeNodes(node.children),\n };\n }\n\n async function normalizeRectangleNode(node: RectangleNode): Promise<NormalizedRectangleNode> {\n return {\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: await normalizeBoundVariables(node),\n ...normalizeRadiusProps(node),\n ...(await normalizeShapeProps(node)),\n };\n }\n\n async function normalizeVectorNode(node: VectorNode): Promise<NormalizedVectorNode> {\n return {\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: await normalizeBoundVariables(node),\n ...(await normalizeShapeProps(node)),\n };\n }\n\n async function normalizeBooleanOperationNode(\n node: BooleanOperationNode,\n ): Promise<NormalizedBooleanOperationNode> {\n return {\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: await normalizeBoundVariables(node),\n children: await normalizeNodes(node.children),\n ...(await normalizeShapeProps(node)),\n };\n }\n async function normalizeTextNode(node: TextNode): Promise<NormalizedTextNode> {\n const segments = node.getStyledTextSegments([\n \"fontSize\",\n \"fontWeight\",\n \"fontName\",\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 return {\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: await normalizeBoundVariables(node),\n style: {\n fontSize: first.fontSize,\n fontWeight: first.fontWeight,\n fontFamily: first.fontName.family,\n // TODO: handle other units\n letterSpacing:\n first.letterSpacing.unit === \"PIXELS\" ? first.letterSpacing.value : undefined,\n lineHeightPx: first.lineHeight.unit === \"PIXELS\" ? first.lineHeight.value : undefined,\n paragraphSpacing: first.paragraphSpacing,\n textAlignHorizontal: node.textAlignHorizontal,\n },\n ...(textStyleKey ? { textStyleKey } : {}),\n characters: node.characters,\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 letterSpacing:\n segment.letterSpacing.unit === \"PIXELS\" ? segment.letterSpacing.value : undefined,\n lineHeightPx: segment.lineHeight.unit === \"PIXELS\" ? segment.lineHeight.value : undefined,\n },\n })),\n ...(await normalizeShapeProps(node)),\n };\n }\n\n async function normalizeComponentNode(node: ComponentNode): Promise<NormalizedComponentNode> {\n return {\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: await normalizeBoundVariables(node),\n ...normalizeRadiusProps(node),\n ...(await normalizeAutolayoutProps(node)),\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 for (const [key, value] of Object.entries(node.componentProperties)) {\n componentProperties[key] = value;\n if (value.type === \"INSTANCE_SWAP\") {\n const mainComponent = (await figma.getNodeByIdAsync(\n value.value as string,\n )) as ComponentNode;\n if (mainComponent) {\n componentProperties[key].componentKey = mainComponent.key;\n if (mainComponent.parent?.type === \"COMPONENT_SET\") {\n componentProperties[key].componentSetKey = mainComponent.parent.key;\n }\n }\n }\n }\n\n return {\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: await normalizeBoundVariables(node),\n ...normalizeRadiusProps(node),\n ...(await normalizeAutolayoutProps(node)),\n children: await normalizeNodes(node.children),\n componentKey: mainComponent.key,\n componentSetKey:\n mainComponent.parent?.type === \"COMPONENT_SET\" ? mainComponent.parent.key : undefined,\n componentProperties,\n overrides: node.overrides,\n };\n }\n\n function normalizeSolidPaint(paint: SolidPaint): FigmaRestSpec.SolidPaint {\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 boundVariables: paint.boundVariables,\n };\n }\n\n function normalizePaint(paint: Paint): FigmaRestSpec.Paint {\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\"]): FigmaRestSpec.Paint[] {\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 ) {\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 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 | \"minHeight\"\n | \"minWidth\"\n | \"maxHeight\"\n | \"maxWidth\"\n > &\n Partial<Pick<FrameNode, \"inferredAutoLayout\">>,\n ) {\n const fillStyleKey =\n typeof node.fillStyleId === \"string\"\n ? (await figma.getStyleByIdAsync(node.fillStyleId))?.key\n : undefined;\n\n return {\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 fills: normalizePaints(node.fills),\n ...(fillStyleKey ? { fillStyleKey } : {}),\n strokes: normalizePaints(node.strokes),\n strokeWeight: node.strokeWeight === figma.mixed ? undefined : node.strokeWeight,\n minHeight: node.minHeight ?? undefined,\n minWidth: node.minWidth ?? undefined,\n maxHeight: node.maxHeight ?? undefined,\n maxWidth: node.maxWidth ?? undefined,\n };\n }\n\n async function normalizeAutolayoutProps(node: Omit<FrameNode, \"type\" | \"clone\">) {\n return {\n ...(await normalizeShapeProps(node)),\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 normalizeBoundVariables({\n boundVariables,\n }: Pick<FrameNode, \"boundVariables\">): Promise<FigmaRestSpec.IsLayerTrait[\"boundVariables\"]> {\n if (!boundVariables) return undefined;\n\n const { width, height, componentProperties: _componentProperties, ...rest } = boundVariables;\n\n // replace VariableAlias' id with the actual variable key\n const resolveVariableId = async (variable: VariableAlias): Promise<VariableAlias> => ({\n ...variable,\n id: (await figma.variables.getVariableByIdAsync(variable.id))?.key ?? variable.id,\n });\n\n const needsResolution = [\n \"fills\",\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 ];\n\n // Process all properties in parallel\n const resolvedEntries = await Promise.all(\n Object.entries(rest).map(async ([key, value]) => {\n if (!value || !needsResolution.includes(key)) return [key, value];\n\n if (Array.isArray(value)) {\n return [key, await Promise.all(value.map(resolveVariableId))];\n }\n\n return [key, await resolveVariableId(value)];\n }),\n );\n\n return {\n ...Object.fromEntries(resolvedEntries),\n ...(width &&\n height && {\n size: {\n x: width,\n y: height,\n },\n }),\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 { FIGMA_ICONS } from \"./data/icons\";\nimport { FIGMA_FILL_STYLES, FIGMA_TEXT_STYLES } from \"./data/styles\";\nimport { FIGMA_VARIABLE_COLLECTIONS } from \"./data/variable-collections\";\nimport { FIGMA_VARIABLES } from \"./data/variables\";\nimport * as FIGMA_COMPONENTS from \"./data/__generated__/component-sets\";\nimport { createStaticStyleRepository } from \"./style.repository\";\nimport { createStaticVariableRepository } from \"./variable.repository\";\nimport { createStaticComponentRepository } from \"./component.repository\";\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_TEXT_STYLES,\n ...FIGMA_FILL_STYLES,\n]);\nexport const variableRepository = createStaticVariableRepository({\n variables: FIGMA_VARIABLES,\n variableCollections: FIGMA_VARIABLE_COLLECTIONS,\n});\nexport const iconRepository = createStaticIconRepository(FIGMA_ICONS);\nexport const componentRepository = createStaticComponentRepository(FIGMA_COMPONENTS);\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;AACA;AACA;AACP;AACA;AACO;AACA;AACP;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;;ACpEA;AACP;AACA;AACA;AACA;AACO;;ACNA;;ACAA;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<\n FigmaRestSpec.IsLayerTrait,\n \"type\" | \"id\" | \"name\" | \"boundVariables\"\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 NormalizedHasGeometryTrait = Pick<\n FigmaRestSpec.HasGeometryTrait,\n \"fills\" | \"strokes\" | \"strokeWeight\" | \"styles\"\n> & {\n fillStyleKey?: 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 lineHeight?: number | { unit: string; value: 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\nexport type NormalizedFrameTrait = NormalizedIsLayerTrait &\n NormalizedHasLayoutTrait &\n NormalizedHasGeometryTrait &\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 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","import 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} from \"./types\";\n\nexport interface RestNormalizerContext {\n styles: Record<string, FigmaRestSpec.Style>;\n components: Record<string, FigmaRestSpec.Component>;\n componentSets: Record<string, FigmaRestSpec.ComponentSet>;\n}\n\nexport function createRestNormalizer(ctx: RestNormalizerContext) {\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 if (node.type === \"FRAME\") {\n return normalizeFrameNode(node);\n }\n if (node.type === \"GROUP\") {\n return normalizeGroupNode(node);\n }\n if (node.type === \"RECTANGLE\") {\n return normalizeRectangleNode(node);\n }\n if (node.type === \"VECTOR\") {\n return normalizeVectorNode(node);\n }\n if (node.type === \"BOOLEAN_OPERATION\") {\n return normalizeBooleanOperationNode(node);\n }\n if (node.type === \"TEXT\") {\n return normalizeTextNode(node);\n }\n if (node.type === \"COMPONENT\") {\n return normalizeComponentNode(node);\n }\n if (node.type === \"INSTANCE\") {\n return normalizeInstanceNode(node);\n }\n\n return {\n type: \"UNHANDLED\",\n id: node.id,\n original: node,\n };\n }\n\n function normalizeFrameNode(node: FigmaRestSpec.FrameNode): NormalizedFrameNode {\n return {\n ...node,\n children: normalizeNodes(node.children),\n };\n }\n\n function normalizeGroupNode(node: FigmaRestSpec.GroupNode): NormalizedFrameNode {\n return {\n ...node,\n type: \"FRAME\",\n children: normalizeNodes(node.children),\n };\n }\n\n function normalizeRectangleNode(node: FigmaRestSpec.RectangleNode): NormalizedRectangleNode {\n return node;\n }\n\n function normalizeVectorNode(node: FigmaRestSpec.VectorNode): NormalizedVectorNode {\n return node;\n }\n\n function normalizeBooleanOperationNode(\n node: FigmaRestSpec.BooleanOperationNode,\n ): NormalizedBooleanOperationNode {\n return {\n ...node,\n children: normalizeNodes(node.children),\n };\n }\n\n function normalizeTextNode(node: FigmaRestSpec.TextNode): NormalizedTextNode {\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: 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] || {} : {},\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 ...node,\n textStyleKey: node.styles?.[\"text\"] ? ctx.styles[node.styles[\"text\"]]?.key : undefined,\n segments: segmentTextNode(node),\n };\n }\n\n function normalizeComponentNode(node: FigmaRestSpec.ComponentNode): NormalizedComponentNode {\n return {\n ...node,\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 const componentSet = mainComponent.componentSetId\n ? ctx.componentSets[mainComponent.componentSetId]\n : undefined;\n const componentProperties: NormalizedInstanceNode[\"componentProperties\"] = {};\n\n for (const [key, value] of Object.entries(node.componentProperties ?? {})) {\n componentProperties[key] = value;\n if (value.type === \"INSTANCE_SWAP\") {\n const mainComponent = ctx.components[value.value as string];\n if (mainComponent) {\n componentProperties[key].componentKey = mainComponent.key;\n }\n const mainComponentSet = mainComponent?.componentSetId\n ? ctx.componentSets[mainComponent.componentSetId]\n : undefined;\n if (mainComponentSet) {\n componentProperties[key].componentSetKey = mainComponentSet.key;\n }\n }\n }\n\n return {\n ...node,\n children: normalizeNodes(node.children),\n componentKey: mainComponent.key,\n componentSetKey: componentSet?.key,\n componentProperties,\n };\n }\n\n return normalizeNode;\n}\n","import type * as FigmaRestSpec from \"@figma/rest-api-spec\";\nimport type {\n NormalizedSceneNode,\n NormalizedFrameNode,\n NormalizedRectangleNode,\n NormalizedTextNode,\n NormalizedComponentNode,\n NormalizedInstanceNode,\n NormalizedVectorNode,\n NormalizedBooleanOperationNode,\n} from \"./types\";\nimport { convertTransformToGradientHandles } from \"@/utils/figma-gradient\";\n\nexport function createPluginNormalizer() {\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 if (node.type === \"FRAME\") {\n return normalizeFrameNode(node);\n }\n if (node.type === \"GROUP\") {\n return normalizeGroupNode(node);\n }\n if (node.type === \"RECTANGLE\") {\n return normalizeRectangleNode(node);\n }\n if (node.type === \"VECTOR\") {\n return normalizeVectorNode(node);\n }\n if (node.type === \"BOOLEAN_OPERATION\") {\n return normalizeBooleanOperationNode(node);\n }\n if (node.type === \"TEXT\") {\n return normalizeTextNode(node);\n }\n if (node.type === \"COMPONENT\") {\n return normalizeComponentNode(node);\n }\n if (node.type === \"INSTANCE\") {\n return normalizeInstanceNode(node);\n }\n\n return {\n type: \"UNHANDLED\",\n id: node.id,\n original: node,\n };\n }\n\n async function normalizeFrameNode(node: FrameNode): Promise<NormalizedFrameNode> {\n return {\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: await normalizeBoundVariables(node),\n ...normalizeRadiusProps(node),\n ...(await normalizeAutolayoutProps(node)),\n children: await normalizeNodes(node.children),\n };\n }\n\n async function normalizeGroupNode(\n node: GroupNode & { inferredAutoLayout?: FrameNode[\"inferredAutoLayout\"] },\n ): Promise<NormalizedFrameNode> {\n return {\n type: \"FRAME\",\n id: node.id,\n name: node.name,\n boundVariables: await normalizeBoundVariables(node),\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 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 fills: [],\n strokes: [],\n children: await normalizeNodes(node.children),\n };\n }\n\n async function normalizeRectangleNode(node: RectangleNode): Promise<NormalizedRectangleNode> {\n return {\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: await normalizeBoundVariables(node),\n ...normalizeRadiusProps(node),\n ...(await normalizeShapeProps(node)),\n };\n }\n\n async function normalizeVectorNode(node: VectorNode): Promise<NormalizedVectorNode> {\n return {\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: await normalizeBoundVariables(node),\n ...(await normalizeShapeProps(node)),\n };\n }\n\n async function normalizeBooleanOperationNode(\n node: BooleanOperationNode,\n ): Promise<NormalizedBooleanOperationNode> {\n return {\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: await normalizeBoundVariables(node),\n children: await normalizeNodes(node.children),\n ...(await normalizeShapeProps(node)),\n };\n }\n async function normalizeTextNode(node: TextNode): Promise<NormalizedTextNode> {\n const segments = node.getStyledTextSegments([\n \"fontSize\",\n \"fontWeight\",\n \"fontName\",\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 return {\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: await normalizeBoundVariables(node),\n style: {\n fontSize: first.fontSize,\n fontWeight: first.fontWeight,\n fontFamily: first.fontName.family,\n // TODO: handle other units\n letterSpacing:\n first.letterSpacing.unit === \"PIXELS\" ? first.letterSpacing.value : undefined,\n lineHeightPx: first.lineHeight.unit === \"PIXELS\" ? first.lineHeight.value : undefined,\n paragraphSpacing: first.paragraphSpacing,\n textAlignHorizontal: node.textAlignHorizontal,\n },\n ...(textStyleKey ? { textStyleKey } : {}),\n characters: node.characters,\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 letterSpacing:\n segment.letterSpacing.unit === \"PIXELS\" ? segment.letterSpacing.value : undefined,\n lineHeightPx: segment.lineHeight.unit === \"PIXELS\" ? segment.lineHeight.value : undefined,\n },\n })),\n ...(await normalizeShapeProps(node)),\n };\n }\n\n async function normalizeComponentNode(node: ComponentNode): Promise<NormalizedComponentNode> {\n return {\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: await normalizeBoundVariables(node),\n ...normalizeRadiusProps(node),\n ...(await normalizeAutolayoutProps(node)),\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 for (const [key, value] of Object.entries(node.componentProperties)) {\n componentProperties[key] = value;\n if (value.type === \"INSTANCE_SWAP\") {\n const mainComponent = (await figma.getNodeByIdAsync(\n value.value as string,\n )) as ComponentNode;\n if (mainComponent) {\n componentProperties[key].componentKey = mainComponent.key;\n if (mainComponent.parent?.type === \"COMPONENT_SET\") {\n componentProperties[key].componentSetKey = mainComponent.parent.key;\n }\n }\n }\n }\n\n return {\n type: node.type,\n id: node.id,\n name: node.name,\n boundVariables: await normalizeBoundVariables(node),\n ...normalizeRadiusProps(node),\n ...(await normalizeAutolayoutProps(node)),\n children: await normalizeNodes(node.children),\n componentKey: mainComponent.key,\n componentSetKey:\n mainComponent.parent?.type === \"COMPONENT_SET\" ? mainComponent.parent.key : undefined,\n componentProperties,\n overrides: node.overrides,\n };\n }\n\n function normalizeSolidPaint(paint: SolidPaint): FigmaRestSpec.SolidPaint {\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 boundVariables: paint.boundVariables,\n };\n }\n\n function normalizePaint(paint: Paint): FigmaRestSpec.Paint {\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\"]): FigmaRestSpec.Paint[] {\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 ) {\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 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 | \"minHeight\"\n | \"minWidth\"\n | \"maxHeight\"\n | \"maxWidth\"\n > &\n Partial<Pick<FrameNode, \"inferredAutoLayout\">>,\n ) {\n const fillStyleKey =\n typeof node.fillStyleId === \"string\"\n ? (await figma.getStyleByIdAsync(node.fillStyleId))?.key\n : undefined;\n\n return {\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 fills: normalizePaints(node.fills),\n ...(fillStyleKey ? { fillStyleKey } : {}),\n strokes: normalizePaints(node.strokes),\n strokeWeight: node.strokeWeight === figma.mixed ? undefined : node.strokeWeight,\n minHeight: node.minHeight ?? undefined,\n minWidth: node.minWidth ?? undefined,\n maxHeight: node.maxHeight ?? undefined,\n maxWidth: node.maxWidth ?? undefined,\n };\n }\n\n async function normalizeAutolayoutProps(node: Omit<FrameNode, \"type\" | \"clone\">) {\n return {\n ...(await normalizeShapeProps(node)),\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 normalizeBoundVariables({\n boundVariables,\n }: Pick<FrameNode, \"boundVariables\">): Promise<FigmaRestSpec.IsLayerTrait[\"boundVariables\"]> {\n if (!boundVariables) return undefined;\n\n const { width, height, componentProperties: _componentProperties, ...rest } = boundVariables;\n\n // replace VariableAlias' id with the actual variable key\n const resolveVariableId = async (variable: VariableAlias): Promise<VariableAlias> => ({\n ...variable,\n id: (await figma.variables.getVariableByIdAsync(variable.id))?.key ?? variable.id,\n });\n\n const needsResolution = [\n \"fills\",\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 ];\n\n // Process all properties in parallel\n const resolvedEntries = await Promise.all(\n Object.entries(rest).map(async ([key, value]) => {\n if (!value || !needsResolution.includes(key)) return [key, value];\n\n if (Array.isArray(value)) {\n return [key, await Promise.all(value.map(resolveVariableId))];\n }\n\n return [key, await resolveVariableId(value)];\n }),\n );\n\n return {\n ...Object.fromEntries(resolvedEntries),\n ...(width &&\n height && {\n size: {\n x: width,\n y: height,\n },\n }),\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 { FIGMA_ICONS } from \"./data/icons\";\nimport { FIGMA_FILL_STYLES, FIGMA_TEXT_STYLES } from \"./data/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\";\nimport { createStaticStyleRepository } from \"./style.repository\";\nimport { createStaticVariableRepository } from \"./variable.repository\";\nimport { createStaticComponentRepository } from \"./component.repository\";\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_TEXT_STYLES,\n ...FIGMA_FILL_STYLES,\n]);\nexport const variableRepository = createStaticVariableRepository({\n variables: FIGMA_VARIABLES,\n variableCollections: FIGMA_VARIABLE_COLLECTIONS,\n});\nexport const iconRepository = createStaticIconRepository(FIGMA_ICONS);\nexport const componentRepository = createStaticComponentRepository(FIGMA_COMPONENTS);\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;AACA;AACA;AACP;AACA;AACO;AACA;AACP;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;;ACpEA;AACP;AACA;AACA;AACA;AACO;;ACNA;;ACAA;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;;;"}
|