@seed-design/figma 0.1.2 → 0.1.3

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.
@@ -240,10 +240,10 @@ function inferLayout(parentNode) {
240
240
  }
241
241
  }
242
242
  // Heuristic: Prefer axis with more non-negative gaps and lower variance
243
- const hVariance = calculateVariance(horizontalGaps.filter((g)=>g >= -1));
244
- const vVariance = calculateVariance(verticalGaps.filter((g)=>g >= -1));
245
- const hCount = horizontalGaps.filter((g)=>g >= -1).length;
246
- const vCount = verticalGaps.filter((g)=>g >= -1).length;
243
+ const hVariance = calculateVariance(horizontalGaps.filter((g)=>g >= -EPSILON));
244
+ const vVariance = calculateVariance(verticalGaps.filter((g)=>g >= -EPSILON));
245
+ const hCount = horizontalGaps.filter((g)=>g >= -EPSILON).length;
246
+ const vCount = verticalGaps.filter((g)=>g >= -EPSILON).length;
247
247
  let primaryAxisSortedNodes = sortedByX; // Default guess
248
248
  // Basic variance check (lower is better). Add slight bias for horizontal if equal.
249
249
  if (vCount > 0 && (hCount === 0 || vVariance < hVariance - EPSILON && vCount >= hCount || vVariance <= hVariance && vCount > hCount)) {
@@ -270,7 +270,7 @@ function inferLayout(parentNode) {
270
270
  }
271
271
  }
272
272
  const primaryGaps = result.layoutMode === "HORIZONTAL" ? horizontalGaps : verticalGaps;
273
- const validGaps = primaryGaps.filter((g)=>g >= -1); // Allow slight overlap
273
+ const validGaps = primaryGaps.filter((g)=>g >= -EPSILON); // Allow slight overlap
274
274
  // --- 2. Calculate Spacing & Primary Alignment ---
275
275
  let isSpaceBetween = false;
276
276
  const collectiveBox = getCollectiveBoundingBox(children);
@@ -314,7 +314,7 @@ function inferLayout(parentNode) {
314
314
  // Use median spacing for robustness against outliers
315
315
  result.itemSpacing = calculateMedian(validGaps);
316
316
  // Clamp negative spacing if it's very small (likely float error)
317
- if (result.itemSpacing < 0 && result.itemSpacing > -1) {
317
+ if (result.itemSpacing < 0 && result.itemSpacing > -EPSILON) {
318
318
  result.itemSpacing = 0;
319
319
  }
320
320
  } else {
@@ -1841,3 +1841,4 @@ declare namespace index {
1841
1841
 
1842
1842
  export { cloneElement, createCodeGenerator, createElement, createPropsConverter, createValueResolver, defineComponentHandler, defineElementTransformer, definePropsConverter, index as figma, inferLayout };
1843
1843
  export type { ActionButtonProperties, ActionChipProperties, ActionSheetItemProperties, ActionSheetProperties, AppBarLeftProperties, AppBarMainProperties, AppBarProperties, AppBarRightProperties, AvatarProperties, AvatarStackProperties, BadgeProperties, CalloutProperties, CheckboxProperties, ChipTabsItemProperties, ChipTabsProperties, CodeGeneratorDeps, ComponentHandler, ComponentPropertyDefinition, ControlChipProperties, ElementNode, ElementTransformer, ErrorStateProperties, ExtendedActionSheetGroupProperties, ExtendedActionSheetItemProperties, ExtendedActionSheetProperties, ExtendedFabProperties, FabProperties, HelpBubbleProperties, IdentityPlaceholderProperties, InferComponentDefinition, InferComponentPropertyType, InlineBannerProperties, MannerTempBadgeProperties, MultilineTextFieldProperties, ProgressCircleProperties, PropsConverter, ReactionButtonProperties, SegmentedControlItemProperties, SegmentedControlProperties, SelectBoxGroupProperties, SelectBoxProperties, SkeletonProperties, SnackbarProperties, SwitchProperties, TabsFillItemProperties, TabsHugItemProperties, TabsProperties, TextButtonProperties, TextFieldProperties, ToggleButtonProperties, ValueResolver };
1844
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sources":["../../src/normalizer/types.ts","../../src/codegen/core/jsx.ts","../../src/codegen/core/element-transformer.ts","../../src/codegen/core/codegen.ts","../../src/codegen/core/component-handler.ts","../../src/codegen/core/component-type-helper.ts","../../src/entities/component.interface.ts","../../src/entities/variable.interface.ts","../../src/entities/style.service.ts","../../src/entities/variable.service.ts","../../src/entities/component.repository.ts","../../src/codegen/core/props-converter.ts","../../src/codegen/core/value-resolver.ts","../../src/codegen/core/infer-layout.ts","../../src/entities/data/__generated__/component-sets/template-select-box-group.d.ts","../../src/entities/data/__generated__/component-sets/action-button.d.ts","../../src/entities/data/__generated__/component-sets/action-chip.d.ts","../../src/entities/data/__generated__/component-sets/action-sheet.d.ts","../../src/entities/data/__generated__/component-sets/avatar.d.ts","../../src/entities/data/__generated__/component-sets/avatar-stack.d.ts","../../src/entities/data/__generated__/component-sets/badge.d.ts","../../src/entities/data/__generated__/component-sets/callout.d.ts","../../src/entities/data/__generated__/component-sets/checkbox.d.ts","../../src/entities/data/__generated__/component-sets/chip-tablist.d.ts","../../src/entities/data/__generated__/component-sets/control-chip.d.ts","../../src/entities/data/__generated__/component-sets/error-state.d.ts","../../src/entities/data/__generated__/component-sets/extended-action-sheet.d.ts","../../src/entities/data/__generated__/component-sets/extended-floating-action-button.d.ts","../../src/entities/data/__generated__/component-sets/floating-action-button.d.ts","../../src/entities/data/__generated__/component-sets/help-bubble.d.ts","../../src/entities/data/__generated__/component-sets/identity-placeholder.d.ts","../../src/entities/data/__generated__/component-sets/inline-banner.d.ts","../../src/entities/data/__generated__/component-sets/manner-temp-badge.d.ts","../../src/entities/data/__generated__/component-sets/multiline-text-field.d.ts","../../src/entities/data/__generated__/component-sets/progress-circle.d.ts","../../src/entities/data/__generated__/component-sets/reaction-button.d.ts","../../src/entities/data/__generated__/component-sets/segmented-control.d.ts","../../src/entities/data/__generated__/component-sets/select-box.d.ts","../../src/entities/data/__generated__/component-sets/skeleton.d.ts","../../src/entities/data/__generated__/component-sets/snackbar.d.ts","../../src/entities/data/__generated__/component-sets/standard-navigation.d.ts","../../src/entities/data/__generated__/component-sets/switch.d.ts","../../src/entities/data/__generated__/component-sets/tablist.d.ts","../../src/entities/data/__generated__/component-sets/text-button.d.ts","../../src/entities/data/__generated__/component-sets/text-field.d.ts","../../src/entities/data/__generated__/component-sets/toggle-button.d.ts","../../src/codegen/component-properties.ts","../../src/codegen/targets/figma/pipeline.ts","../../src/codegen/targets/figma/value-resolver.ts","../../src/codegen/targets/figma/props.ts","../../src/codegen/targets/figma/frame.ts","../../src/codegen/targets/figma/instance.ts","../../src/codegen/targets/figma/shape.ts","../../src/codegen/targets/figma/text.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\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 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 { ensureArray, exists } from \"@/utils/common\";\n\nexport interface ElementNode {\n __IS_JSX_ELEMENT_NODE: true;\n tag: string;\n props: Record<string, string | number | boolean | ElementNode | object | undefined>;\n children: (ElementNode | string)[];\n\n meta: {\n comment?: string;\n source?: string;\n importPath?: string;\n };\n}\n\nexport function createElement(\n tag: string,\n props: Record<string, string | number | boolean | object | undefined> = {},\n children?: ElementNode | string | undefined | (ElementNode | string | undefined)[],\n meta?: ElementNode[\"meta\"],\n): ElementNode {\n return {\n __IS_JSX_ELEMENT_NODE: true,\n tag,\n props,\n children: ensureArray(children).filter(exists),\n meta: meta ?? {},\n };\n}\n\nexport function cloneElement(\n element: ElementNode,\n props: Record<string, string | number | boolean | object | undefined> = {},\n children?: ElementNode | string | undefined | (ElementNode | string | undefined)[],\n) {\n return {\n ...element,\n props: { ...element.props, ...props },\n children: children ? ensureArray(children).filter(exists) : element.children,\n };\n}\n\nexport function appendSource(element: ElementNode, source: string) {\n return {\n ...element,\n source,\n };\n}\n\nexport function isElement(node: unknown): node is ElementNode {\n return (\n typeof node === \"object\" &&\n node != null &&\n \"__IS_JSX_ELEMENT_NODE\" in node &&\n node.__IS_JSX_ELEMENT_NODE === true\n );\n}\n\nexport function stringifyElement(element: ElementNode, options: { printSource?: boolean } = {}) {\n const importMap = new Map<string, Set<string>>();\n\n function recursive(node: ElementNode | string, depth: number): string {\n if (typeof node === \"string\") {\n return node;\n }\n\n const {\n tag,\n props,\n children,\n meta: { comment, source, importPath },\n } = node;\n\n if (importPath) {\n const existing = importMap.get(importPath);\n if (existing) {\n existing.add(tag);\n } else {\n importMap.set(importPath, new Set([tag]));\n }\n }\n\n const propEntries = Object.entries(\n options.printSource ? { ...props, \"data-figma-node-id\": source } : props,\n );\n const propFragments = propEntries\n .map(([key, value]) => {\n if (typeof value === \"string\") {\n if (value.includes(\"\\n\")) {\n return `${key}={\\\"${value.replace(\"\\n\", \"\\\\n\")}\\\"}`;\n }\n\n return `${key}=\"${value}\"`;\n }\n\n if (typeof value === \"number\") {\n return `${key}={${value}}`;\n }\n\n if (typeof value === \"boolean\") {\n return `${key}={${value}}`;\n }\n\n if (isElement(value)) {\n return `${key}={${recursive(value, depth + 1)}}`;\n }\n\n if (typeof value === \"object\") {\n return `${key}={${JSON.stringify(value)}}`;\n }\n\n if (typeof value === \"undefined\") {\n return undefined;\n }\n })\n .filter(exists);\n\n const oneLiner = propFragments.join(\" \");\n const propsString =\n propEntries.length === 0\n ? \"\"\n : ` ${\n oneLiner.length < 80\n ? oneLiner\n : `\\n${\" \".repeat(depth + 1)}${propFragments.join(\n `\\n${\" \".repeat(depth + 1)}`,\n )}\\n${\" \".repeat(depth)}`\n }`;\n\n if (children == null || children.length === 0) {\n return `<${tag}${propsString} />${comment ? `{/* ${comment} */}` : \"\"}`;\n }\n\n const result = [\n `<${tag}${propsString}>`,\n ...ensureArray(children)\n .filter(exists)\n .map((child) => recursive(child, depth + 1))\n .map((str) => \" \".repeat(depth + 1) + str),\n `${\" \".repeat(depth)}</${tag}>${comment ? `{/* ${comment} */}` : \"\"}`,\n ].join(\"\\n\");\n\n return result;\n }\n\n const jsx = recursive(element, 0);\n\n const imports = Array.from(importMap.entries())\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([importPath, tags]) => `import { ${Array.from(tags).join(\", \")} } from \"${importPath}\";`)\n .join(\"\\n\");\n\n return {\n imports,\n jsx,\n };\n}\n","import type { NormalizedSceneNode } from \"@/normalizer\";\nimport type { ElementNode } from \"./jsx\";\n\nexport type ElementTransformer<T extends NormalizedSceneNode> = (\n node: T,\n traverse: (node: NormalizedSceneNode) => ElementNode | undefined,\n) => ElementNode | undefined;\n\nexport function defineElementTransformer<T extends NormalizedSceneNode>(\n transformer: ElementTransformer<T>,\n) {\n return transformer;\n}\n","import type {\n NormalizedBooleanOperationNode,\n NormalizedComponentNode,\n NormalizedFrameNode,\n NormalizedInstanceNode,\n NormalizedRectangleNode,\n NormalizedSceneNode,\n NormalizedTextNode,\n NormalizedVectorNode,\n} from \"@/normalizer\";\nimport { match } from \"ts-pattern\";\nimport { appendSource, createElement, stringifyElement, type ElementNode } from \"../core/jsx\";\nimport type { ElementTransformer } from \"./element-transformer\";\nimport { applyInferredLayout, inferLayout } from \"./infer-layout\";\n\nexport interface CodeGeneratorDeps {\n frameTransformer: ElementTransformer<\n NormalizedFrameNode | NormalizedComponentNode | NormalizedInstanceNode\n >;\n textTransformer: ElementTransformer<NormalizedTextNode>;\n rectangleTransformer: ElementTransformer<NormalizedRectangleNode>;\n instanceTransformer: ElementTransformer<NormalizedInstanceNode>;\n vectorTransformer: ElementTransformer<NormalizedVectorNode>;\n booleanOperationTransformer: ElementTransformer<NormalizedBooleanOperationNode>;\n shouldInferAutoLayout: boolean;\n}\n\nexport interface CodeGenerator {\n generateJsxTree: (node: NormalizedSceneNode) => ElementNode | undefined;\n generateCode: (\n node: NormalizedSceneNode,\n options: { shouldPrintSource: boolean },\n ) => { imports: string; jsx: string } | undefined;\n}\n\nexport function createCodeGenerator({\n frameTransformer,\n textTransformer,\n rectangleTransformer,\n instanceTransformer,\n vectorTransformer,\n booleanOperationTransformer,\n shouldInferAutoLayout,\n}: CodeGeneratorDeps): CodeGenerator {\n function traverse(node: NormalizedSceneNode): ElementNode | undefined {\n if (\"visible\" in node && !node.visible) {\n return;\n }\n\n const result = match(node)\n .with({ type: \"FRAME\" }, (node) =>\n shouldInferAutoLayout\n ? frameTransformer(applyInferredLayout(node, inferLayout(node)), traverse)\n : frameTransformer(node, traverse),\n )\n .with({ type: \"TEXT\" }, (node) => textTransformer(node, traverse))\n .with({ type: \"RECTANGLE\" }, (node) => rectangleTransformer(node, traverse))\n .with({ type: \"COMPONENT\" }, (node) => frameTransformer(node, traverse)) // NOTE: Treat component node as Frame for now\n .with({ type: \"INSTANCE\" }, (node) => instanceTransformer(node, traverse))\n .with({ type: \"VECTOR\" }, (node) => vectorTransformer(node, traverse))\n .with({ type: \"BOOLEAN_OPERATION\" }, (node) => booleanOperationTransformer(node, traverse))\n .with({ type: \"UNHANDLED\" }, () => createElement(\"UnhandledFigmaNode\"))\n .exhaustive();\n\n if (result) {\n return appendSource(result, node.id);\n }\n\n return;\n }\n\n function generateJsxTree(node: NormalizedSceneNode) {\n return traverse(node);\n }\n\n function generateCode(node: NormalizedSceneNode, options: { shouldPrintSource: boolean }) {\n const jsxTree = generateJsxTree(node);\n\n if (!jsxTree) {\n return undefined;\n }\n\n return stringifyElement(jsxTree, { printSource: options.shouldPrintSource });\n }\n\n return { generateJsxTree, generateCode };\n}\n","import type { NormalizedInstanceNode } from \"@/normalizer\";\nimport type { ElementNode } from \"./jsx\";\n\nexport interface ComponentHandler<\n T extends\n NormalizedInstanceNode[\"componentProperties\"] = NormalizedInstanceNode[\"componentProperties\"],\n> {\n key: string;\n transform: (node: NormalizedInstanceNode & { componentProperties: T }) => ElementNode;\n}\n\nexport function defineComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]>(\n key: string,\n transform: (node: NormalizedInstanceNode & { componentProperties: T }) => ElementNode,\n): ComponentHandler<T> {\n return { key, transform };\n}\n","import type { ComponentPropertyType, InstanceSwapPreferredValue } from \"@figma/rest-api-spec\";\n\nexport interface ComponentPropertyDefinition {\n type: ComponentPropertyType;\n preferredValues?: InstanceSwapPreferredValue[];\n variantOptions?: string[];\n}\n\nexport type InferComponentPropertyType<T extends ComponentPropertyDefinition> =\n T[\"type\"] extends \"TEXT\"\n ? string\n : T[\"type\"] extends \"BOOLEAN\"\n ? boolean\n : T[\"type\"] extends \"INSTANCE_SWAP\"\n ? string\n : T[\"type\"] extends \"VARIANT\"\n ? T[\"variantOptions\"] extends string[]\n ? T[\"variantOptions\"][number]\n : never\n : never;\n\nexport type InferComponentDefinition<T extends Record<string, ComponentPropertyDefinition>> = {\n [K in keyof T]: {\n type: T[K][\"type\"];\n value: InferComponentPropertyType<T[K]>;\n readonly boundVariables?: {\n [field in VariableBindableComponentPropertyField]?: VariableAlias;\n };\n } & (T[K][\"type\"] extends \"INSTANCE_SWAP\"\n ? {\n componentKey: string;\n preferredValues: InstanceSwapPreferredValue[];\n }\n : {});\n};\n","import type { ComponentPropertyDefinition } from \"@/codegen\";\n\nexport interface ComponentMetadata {\n name: string;\n key: string;\n componentPropertyDefinitions: Record<string, ComponentPropertyDefinition>;\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 { 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 type { VariableValueResolved } from \"@/entities\";\nimport { objectEntries } from \"@/utils/common\";\n\nexport type PropsConverter<\n T extends Record<string, any> = Record<string, any>,\n R extends Record<string, any> = Record<string, any>,\n> = (node: T) => R;\n\nexport function definePropsConverter<T extends Record<string, any>, R extends Record<string, any>>(\n converter: PropsConverter<T, R>,\n) {\n return converter;\n}\n\ntype Handlers<\n TTrait extends Record<string, VariableValueResolved>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps = keyof TProps,\n> = {\n [K in HandlerKeys]: (node: TTrait) => TProps[K];\n};\n\ntype Shorthands<TProps extends Record<string, any>, HandlerKeys extends keyof TProps> = Record<\n Exclude<keyof TProps, HandlerKeys>,\n HandlerKeys[]\n>;\n\nexport interface CreatePropsConverterConfig<\n TTrait extends Record<string, any>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps,\n> {\n _types: {\n trait: TTrait;\n props: TProps;\n };\n handlers: Handlers<TTrait, TProps, HandlerKeys>;\n shorthands?: Shorthands<TProps, HandlerKeys>;\n defaults?: Partial<TProps>;\n}\n\nexport function createPropsConverter<\n TTrait extends Record<string, any>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps,\n>({\n handlers,\n shorthands,\n defaults,\n}: CreatePropsConverterConfig<TTrait, TProps, HandlerKeys>): PropsConverter<TTrait, TProps> {\n return definePropsConverter((node: TTrait) => {\n const result = {} as TProps;\n\n for (const [prop, handler] of objectEntries(handlers)) {\n const value = handler(node);\n if (value !== undefined && (!defaults || value !== defaults[prop as keyof TProps])) {\n result[prop as keyof TProps] = value as any;\n }\n }\n\n if (shorthands) {\n for (const [shorthand, props] of objectEntries(shorthands)) {\n const values = props.map((prop) => result[prop as keyof TProps]);\n const allDefined = values.every((value) => value !== undefined);\n const allEqual = allDefined && values.every((value) => value === values[0]);\n\n if (allEqual && values[0] !== undefined) {\n result[shorthand as keyof TProps] = values[0] as any;\n for (const prop of props) {\n delete result[prop as keyof TProps];\n }\n }\n }\n }\n\n return result;\n });\n}\n","import type { StyleService, VariableValueResolved } from \"@/entities\";\nimport type {\n NormalizedCornerTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasLayoutTrait,\n NormalizedIsLayerTrait,\n NormalizedTypePropertiesTrait,\n} from \"@/normalizer\";\nimport {\n getFirstFillVariable,\n getFirstSolidFill,\n getFirstStroke,\n getFirstStrokeVariable,\n} from \"@/utils/figma-node\";\nimport type { RGBA } from \"@figma/rest-api-spec\";\nimport type { VariableService } from \"../../entities/variable.service\";\n\nexport interface ValueResolver<TColor, TDimension, TFontDimension, TFontWeight> {\n getFormattedValue: {\n frameFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n shapeFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n textFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n stroke: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n width: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n height: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n minWidth: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n minHeight: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n maxWidth: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n maxHeight: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingLeft: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingRight: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingTop: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingBottom: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n itemSpacing: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n topLeftRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n topRightRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n bottomLeftRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n bottomRightRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n fontSize: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontDimension | undefined;\n fontWeight: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontWeight | undefined;\n lineHeight: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontDimension | undefined;\n };\n getTextStyleValue: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | undefined; // TODO: we might turn this into a generic; not sure yet\n}\n\nexport interface ValueResolverDeps<TColor, TDimension, TFontDimension, TFontWeight> {\n variableService: VariableService;\n variableNameFormatter: (props: { slug: string[] }) => string;\n styleService: StyleService;\n styleNameFormatter: (props: { slug: string[] }) => string;\n rawValueFormatters: {\n color: (value: RGBA) => string | TColor;\n dimension: (value: number) => string | TDimension;\n fontDimension: (value: number) => string | TFontDimension;\n fontWeight: (value: number) => string | TFontWeight;\n };\n shouldInferVariableName: boolean;\n}\n\nexport function createValueResolver<TColor, TDimension, TFontDimension, TFontWeight>({\n variableService,\n variableNameFormatter,\n styleService,\n styleNameFormatter,\n rawValueFormatters,\n shouldInferVariableName,\n}: ValueResolverDeps<TColor, TDimension, TFontDimension, TFontWeight>): ValueResolver<\n TColor,\n TDimension,\n TFontDimension,\n TFontWeight\n> {\n function getVariableName(key: string) {\n const slug = variableService.getSlug(key);\n\n if (!slug) {\n return undefined;\n }\n\n return variableNameFormatter({ slug });\n }\n\n function inferVariableName(value: VariableValueResolved, scope: VariableScope) {\n if (!shouldInferVariableName) {\n return undefined;\n }\n\n const inferred = variableService.infer(value, scope);\n\n if (!inferred) {\n return undefined;\n }\n\n return getVariableName(inferred.key);\n }\n\n function getStyleName(key: string) {\n const slug = styleService.getSlug(key);\n\n if (!slug) {\n return undefined;\n }\n\n return styleNameFormatter({ slug });\n }\n\n function processColor(\n key: string | undefined,\n value: RGBA | undefined,\n scope: \"FRAME_FILL\" | \"SHAPE_FILL\" | \"STROKE_COLOR\" | \"TEXT_FILL\",\n ) {\n if (key) {\n return getVariableName(key);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.color(value);\n }\n\n return undefined;\n }\n\n function processDimension(\n key: string | undefined,\n value: number | undefined,\n scope: \"WIDTH_HEIGHT\" | \"GAP\" | \"CORNER_RADIUS\",\n ) {\n if (key) {\n return getVariableName(key);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.dimension(value);\n }\n\n return undefined;\n }\n\n function processFontDimension(\n key: string | undefined,\n value: number | undefined,\n scope: \"FONT_SIZE\" | \"LINE_HEIGHT\",\n ) {\n if (key) {\n return getVariableName(key);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.fontDimension(value);\n }\n\n return undefined;\n }\n\n function processFontWeight(key: string | undefined, value: number | undefined) {\n if (key) {\n return getVariableName(key);\n }\n\n if (value !== undefined) {\n const fontWeightToString: Record<number, string> = {\n 100: \"thin\",\n 200: \"extra-light\",\n 300: \"light\",\n 400: \"regular\",\n 500: \"medium\",\n 600: \"semi-bold\",\n 700: \"bold\",\n 800: \"extra-bold\",\n 900: \"black\",\n };\n\n return (\n inferVariableName(value, \"FONT_WEIGHT\") ??\n inferVariableName(fontWeightToString[value], \"FONT_STYLE\") ??\n rawValueFormatters.fontWeight(value)\n );\n }\n\n return undefined;\n }\n\n const getFormattedValue: ValueResolver<\n TColor,\n TDimension,\n TFontDimension,\n TFontWeight\n >[\"getFormattedValue\"] = {\n width: (node) =>\n processDimension(\n node.boundVariables?.size?.x?.id,\n node.absoluteBoundingBox?.width,\n \"WIDTH_HEIGHT\",\n ),\n height: (node) =>\n processDimension(\n node.boundVariables?.size?.y?.id,\n node.absoluteBoundingBox?.height,\n \"WIDTH_HEIGHT\",\n ),\n minWidth: (node) =>\n processDimension(node.boundVariables?.minWidth?.id, node.minWidth, \"WIDTH_HEIGHT\"),\n minHeight: (node) =>\n processDimension(node.boundVariables?.minHeight?.id, node.minHeight, \"WIDTH_HEIGHT\"),\n maxWidth: (node) =>\n processDimension(node.boundVariables?.maxWidth?.id, node.maxWidth, \"WIDTH_HEIGHT\"),\n maxHeight: (node) =>\n processDimension(node.boundVariables?.maxHeight?.id, node.maxHeight, \"WIDTH_HEIGHT\"),\n paddingLeft: (node) =>\n processDimension(node.boundVariables?.paddingLeft?.id, node.paddingLeft, \"GAP\"),\n paddingRight: (node) =>\n processDimension(node.boundVariables?.paddingRight?.id, node.paddingRight, \"GAP\"),\n paddingTop: (node) =>\n processDimension(node.boundVariables?.paddingTop?.id, node.paddingTop, \"GAP\"),\n paddingBottom: (node) =>\n processDimension(node.boundVariables?.paddingBottom?.id, node.paddingBottom, \"GAP\"),\n itemSpacing: (node) =>\n processDimension(node.boundVariables?.itemSpacing?.id, node.itemSpacing, \"GAP\"),\n frameFill: (node) =>\n processColor(getFirstFillVariable(node)?.id, getFirstSolidFill(node)?.color, \"FRAME_FILL\"),\n shapeFill: (node) =>\n processColor(getFirstFillVariable(node)?.id, getFirstSolidFill(node)?.color, \"SHAPE_FILL\"),\n textFill: (node) =>\n processColor(getFirstFillVariable(node)?.id, getFirstSolidFill(node)?.color, \"TEXT_FILL\"),\n stroke: (node) =>\n processColor(getFirstStrokeVariable(node)?.id, getFirstStroke(node)?.color, \"STROKE_COLOR\"),\n topLeftRadius: (node) =>\n processDimension(\n node.boundVariables?.topLeftRadius?.id,\n node.rectangleCornerRadii?.[0] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n topRightRadius: (node) =>\n processDimension(\n node.boundVariables?.topRightRadius?.id,\n node.rectangleCornerRadii?.[1] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n bottomLeftRadius: (node) =>\n processDimension(\n node.boundVariables?.bottomLeftRadius?.id,\n node.rectangleCornerRadii?.[2] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n bottomRightRadius: (node) =>\n processDimension(\n node.boundVariables?.bottomRightRadius?.id,\n node.rectangleCornerRadii?.[3] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n fontSize: (node) =>\n processFontDimension(\n node.boundVariables?.fontSize?.[0]?.id,\n node.style.fontSize,\n \"FONT_SIZE\",\n ),\n fontWeight: (node) =>\n processFontWeight(node.boundVariables?.fontWeight?.[0]?.id, node.style.fontWeight),\n lineHeight: (node) =>\n processFontDimension(\n node.boundVariables?.lineHeight?.[0]?.id,\n node.style.lineHeightPx,\n \"LINE_HEIGHT\",\n ),\n };\n\n function getTextStyleValue(node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait) {\n if (node.textStyleKey) {\n return getStyleName(node.textStyleKey);\n }\n\n return undefined;\n }\n\n return {\n getFormattedValue,\n getTextStyleValue,\n };\n}\n","import type {\n NormalizedHasChildrenTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedHasLayoutTrait,\n NormalizedIsLayerTrait,\n} from \"@/normalizer\";\n\ninterface BoundingBox {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\ninterface LayoutProperties {\n layoutMode?: \"NONE\" | \"HORIZONTAL\" | \"VERTICAL\";\n primaryAxisSizingMode?: \"FIXED\" | \"AUTO\";\n counterAxisSizingMode?: \"FIXED\" | \"AUTO\";\n primaryAxisAlignItems?: \"MIN\" | \"CENTER\" | \"MAX\" | \"SPACE_BETWEEN\";\n counterAxisAlignItems?: \"MIN\" | \"CENTER\" | \"MAX\"; // 'BASELINE' requires more info\n paddingLeft?: number;\n paddingRight?: number;\n paddingTop?: number;\n paddingBottom?: number;\n itemSpacing?: number;\n}\n\ninterface InferResult {\n properties: LayoutProperties;\n childProperties: {\n [childId: string]: {\n layoutAlign?: \"MIN\" | \"STRETCH\";\n };\n };\n}\n\ntype LayoutNode = NormalizedIsLayerTrait &\n NormalizedHasFramePropertiesTrait &\n NormalizedHasChildrenTrait &\n NormalizedHasLayoutTrait;\n\n// --- Helper Functions ---\n\nfunction getCollectiveBoundingBox(nodes: LayoutNode[]): BoundingBox | null {\n if (nodes.length === 0) {\n return null;\n }\n\n let minX = Number.POSITIVE_INFINITY;\n let minY = Number.POSITIVE_INFINITY;\n let maxX = Number.NEGATIVE_INFINITY;\n let maxY = Number.NEGATIVE_INFINITY;\n\n nodes.forEach((node) => {\n const box = node.absoluteBoundingBox!;\n minX = Math.min(minX, box.x);\n minY = Math.min(minY, box.y);\n maxX = Math.max(maxX, box.x + box.width);\n maxY = Math.max(maxY, box.y + box.height);\n });\n\n return {\n x: minX,\n y: minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n}\n\nfunction calculateMean(arr: number[]): number {\n if (arr.length === 0) return 0;\n return arr.reduce((sum, val) => sum + val, 0) / arr.length;\n}\n\nfunction calculateVariance(arr: number[]): number {\n if (arr.length < 2) return 0;\n const mean = calculateMean(arr);\n return arr.reduce((sum, val) => sum + (val - mean) ** 2, 0) / arr.length;\n}\n\nfunction calculateMedian(arr: number[]): number {\n if (arr.length === 0) return 0;\n const sortedArr = [...arr].sort((a, b) => a - b);\n const mid = Math.floor(sortedArr.length / 2);\n if (sortedArr.length % 2 === 0) {\n return (sortedArr[mid - 1] + sortedArr[mid]) / 2;\n }\n return sortedArr[mid];\n}\n\n// Tolerance for floating point comparisons and alignment checks\nconst EPSILON = 1; // 1 pixel tolerance\n\n// --- Main Inference Function ---\n\nexport function inferLayout(parentNode: LayoutNode): InferResult {\n if (parentNode.layoutMode !== \"NONE\") {\n return {\n properties: {},\n childProperties: {},\n };\n }\n\n const children = (parentNode.children || []) as LayoutNode[];\n const parentBox = parentNode.absoluteBoundingBox!;\n const result: LayoutProperties = { layoutMode: \"NONE\" };\n\n if (children.length === 0) {\n return {\n properties: result,\n childProperties: {},\n }; // Cannot infer layout for no children\n }\n\n if (children.length === 1) {\n // Default for single child: Horizontal, Hug contents, No spacing, Calculate padding\n result.layoutMode = \"HORIZONTAL\";\n result.primaryAxisSizingMode = \"AUTO\";\n result.counterAxisSizingMode = \"AUTO\";\n result.itemSpacing = 0;\n result.primaryAxisAlignItems = \"MIN\"; // Doesn't matter for one item\n result.counterAxisAlignItems = \"MIN\"; // Doesn't matter for one item\n\n const childBox = children[0].absoluteBoundingBox!;\n result.paddingLeft = Math.max(0, childBox.x - parentBox.x);\n result.paddingRight = Math.max(\n 0,\n parentBox.x + parentBox.width - (childBox.x + childBox.width),\n );\n result.paddingTop = Math.max(0, childBox.y - parentBox.y);\n result.paddingBottom = Math.max(\n 0,\n parentBox.y + parentBox.height - (childBox.y + childBox.height),\n );\n return {\n properties: result,\n childProperties: {},\n };\n }\n\n // --- 1. Determine Layout Direction ---\n const sortedByX = [...children].sort(\n (a, b) => a.absoluteBoundingBox!.x - b.absoluteBoundingBox!.x,\n );\n const sortedByY = [...children].sort(\n (a, b) => a.absoluteBoundingBox!.y - b.absoluteBoundingBox!.y,\n );\n\n const horizontalGaps: number[] = [];\n for (let i = 0; i < sortedByX.length - 1; i++) {\n const current = sortedByX[i].absoluteBoundingBox!;\n const next = sortedByX[i + 1].absoluteBoundingBox!;\n // Ensure items don't significantly overlap vertically for horizontal check\n if (Math.max(current.y, next.y) < Math.min(current.y + current.height, next.y + next.height)) {\n horizontalGaps.push(next.x - (current.x + current.width));\n }\n }\n\n const verticalGaps: number[] = [];\n for (let i = 0; i < sortedByY.length - 1; i++) {\n const current = sortedByY[i].absoluteBoundingBox!;\n const next = sortedByY[i + 1].absoluteBoundingBox!;\n // Ensure items don't significantly overlap horizontally for vertical check\n if (Math.max(current.x, next.x) < Math.min(current.x + current.width, next.x + next.width)) {\n verticalGaps.push(next.y - (current.y + current.height));\n }\n }\n\n // Heuristic: Prefer axis with more non-negative gaps and lower variance\n const hVariance = calculateVariance(horizontalGaps.filter((g) => g >= -EPSILON));\n const vVariance = calculateVariance(verticalGaps.filter((g) => g >= -EPSILON));\n const hCount = horizontalGaps.filter((g) => g >= -EPSILON).length;\n const vCount = verticalGaps.filter((g) => g >= -EPSILON).length;\n\n let primaryAxisSortedNodes = sortedByX; // Default guess\n\n // Basic variance check (lower is better). Add slight bias for horizontal if equal.\n if (\n vCount > 0 &&\n (hCount === 0 ||\n (vVariance < hVariance - EPSILON && vCount >= hCount) ||\n (vVariance <= hVariance && vCount > hCount))\n ) {\n result.layoutMode = \"VERTICAL\";\n primaryAxisSortedNodes = sortedByY;\n } else if (hCount > 0) {\n result.layoutMode = \"HORIZONTAL\";\n primaryAxisSortedNodes = sortedByX;\n } else {\n // Ambiguous case based on gaps, fall back to bounding box aspect ratio\n const collectiveBox = getCollectiveBoundingBox(children);\n if (collectiveBox) {\n if (collectiveBox.height > collectiveBox.width) {\n result.layoutMode = \"VERTICAL\";\n primaryAxisSortedNodes = sortedByY;\n } else {\n result.layoutMode = \"HORIZONTAL\";\n primaryAxisSortedNodes = sortedByX;\n }\n } else {\n // Still nothing? Default to Horizontal\n result.layoutMode = \"HORIZONTAL\";\n primaryAxisSortedNodes = sortedByX;\n }\n }\n\n const primaryGaps = result.layoutMode === \"HORIZONTAL\" ? horizontalGaps : verticalGaps;\n const validGaps = primaryGaps.filter((g) => g >= -EPSILON); // Allow slight overlap\n\n // --- 2. Calculate Spacing & Primary Alignment ---\n let isSpaceBetween = false;\n const collectiveBox = getCollectiveBoundingBox(children);\n\n if (collectiveBox && children.length >= 2) {\n // Check for Space Between potential\n const first = primaryAxisSortedNodes[0].absoluteBoundingBox!;\n const last = primaryAxisSortedNodes[primaryAxisSortedNodes.length - 1].absoluteBoundingBox!;\n let firstStart: number;\n let lastEnd: number;\n let parentSize: number;\n\n if (result.layoutMode === \"HORIZONTAL\") {\n firstStart = first.x;\n lastEnd = last.x + last.width;\n parentSize = parentBox.width;\n } else {\n firstStart = first.y;\n lastEnd = last.y + last.height;\n parentSize = parentBox.height;\n }\n\n const contentSpan = lastEnd - firstStart;\n\n // Heuristic for Space Between: Content spans most of the parent & average gap is large\n const averageGap = calculateMean(validGaps);\n // Example threshold: Content fills > 85% AND average gap is > 20% of average item size? Or just large?\n if (contentSpan > parentSize * 0.8 && validGaps.length > 0 && averageGap > 10) {\n // Additional check: are first/last items close to parent edges (considering padding)?\n const startPadding =\n result.layoutMode === \"HORIZONTAL\" ? first.x - parentBox.x : first.y - parentBox.y;\n const endPadding =\n result.layoutMode === \"HORIZONTAL\"\n ? parentBox.x + parentBox.width - (last.x + last.width)\n : parentBox.y + parentBox.height - (last.y + last.height);\n\n // If start/end items are reasonably close to edges (e.g., < 2 * average gap?)\n if (\n Math.abs(startPadding) < Math.max(20, averageGap * 1.5) &&\n Math.abs(endPadding) < Math.max(20, averageGap * 1.5)\n ) {\n isSpaceBetween = true;\n }\n }\n }\n\n if (isSpaceBetween) {\n result.primaryAxisAlignItems = \"SPACE_BETWEEN\";\n result.itemSpacing = 0; // Spacing is implicit\n result.primaryAxisSizingMode = \"FIXED\"; // Usually fixed when using space between\n } else {\n result.primaryAxisAlignItems = \"MIN\"; // Default to MIN for packed, could refine later\n if (validGaps.length > 0) {\n // Use median spacing for robustness against outliers\n result.itemSpacing = calculateMedian(validGaps);\n // Clamp negative spacing if it's very small (likely float error)\n if (result.itemSpacing < 0 && result.itemSpacing > -EPSILON) {\n result.itemSpacing = 0;\n }\n } else {\n result.itemSpacing = 0; // No valid gaps found\n }\n result.primaryAxisSizingMode = \"AUTO\"; // Default to hug content for packed\n }\n\n // --- 3. Calculate Padding ---\n if (collectiveBox) {\n result.paddingLeft = Math.max(0, collectiveBox.x - parentBox.x);\n result.paddingRight = Math.max(\n 0,\n parentBox.x + parentBox.width - (collectiveBox.x + collectiveBox.width),\n );\n result.paddingTop = Math.max(0, collectiveBox.y - parentBox.y);\n result.paddingBottom = Math.max(\n 0,\n parentBox.y + parentBox.height - (collectiveBox.y + collectiveBox.height),\n );\n } else {\n result.paddingLeft = 0;\n result.paddingRight = 0;\n result.paddingTop = 0;\n result.paddingBottom = 0;\n }\n\n // --- 4. Determine Counter Axis Alignment ---\n const counterCoordsMin: number[] = [];\n const counterCoordsCenter: number[] = [];\n const counterCoordsMax: number[] = [];\n\n if (result.layoutMode === \"HORIZONTAL\") {\n // Check vertical alignment (Y)\n children.forEach((node) => {\n const box = node.absoluteBoundingBox!;\n counterCoordsMin.push(box.y);\n counterCoordsCenter.push(box.y + box.height / 2);\n counterCoordsMax.push(box.y + box.height);\n });\n } else {\n // VERTICAL layout\n // Check horizontal alignment (X)\n children.forEach((node) => {\n const box = node.absoluteBoundingBox!;\n counterCoordsMin.push(box.x);\n counterCoordsCenter.push(box.x + box.width / 2);\n counterCoordsMax.push(box.x + box.width);\n });\n }\n\n const minVariance = calculateVariance(counterCoordsMin);\n const centerVariance = calculateVariance(counterCoordsCenter);\n const maxVariance = calculateVariance(counterCoordsMax);\n\n const alignmentTolerance = EPSILON * EPSILON * 4; // Allow slightly more variance for alignment match\n if (\n minVariance <= centerVariance &&\n minVariance <= maxVariance &&\n minVariance < alignmentTolerance\n ) {\n result.counterAxisAlignItems = \"MIN\";\n } else if (\n centerVariance <= minVariance &&\n centerVariance <= maxVariance &&\n centerVariance < alignmentTolerance\n ) {\n result.counterAxisAlignItems = \"CENTER\";\n } else if (\n maxVariance <= minVariance &&\n maxVariance <= centerVariance &&\n maxVariance < alignmentTolerance\n ) {\n result.counterAxisAlignItems = \"MAX\";\n } else {\n // Default if variances are high or similar\n result.counterAxisAlignItems = \"CENTER\";\n }\n\n // --- 5. Determine Counter Axis Sizing Mode ---\n // Default to AUTO unless children perfectly fill the parent counter dimension\n result.counterAxisSizingMode = \"AUTO\";\n if (collectiveBox) {\n let collectiveCounterSize: number;\n let parentCounterSize: number;\n if (result.layoutMode === \"HORIZONTAL\") {\n collectiveCounterSize = collectiveBox.height;\n parentCounterSize = parentBox.height - (result.paddingTop ?? 0) - (result.paddingBottom ?? 0);\n } else {\n collectiveCounterSize = collectiveBox.width;\n parentCounterSize = parentBox.width - (result.paddingLeft ?? 0) - (result.paddingRight ?? 0);\n }\n // If collective size is very close to parent size on counter axis\n if (Math.abs(collectiveCounterSize - parentCounterSize) < EPSILON) {\n result.counterAxisSizingMode = \"FIXED\";\n }\n }\n\n // 6. Infer layoutAlign for each child\n const childProperties: InferResult[\"childProperties\"] = {};\n const availableWidth = parentBox.width - (result.paddingLeft ?? 0) - (result.paddingRight ?? 0);\n const availableHeight = parentBox.height - (result.paddingTop ?? 0) - (result.paddingBottom ?? 0);\n\n children.forEach((child) => {\n const childBox = child.absoluteBoundingBox!;\n let inferredChildAlign: \"INHERIT\" | \"STRETCH\" | undefined = undefined;\n\n // Check STRETCH\n if (result.layoutMode === \"HORIZONTAL\") {\n // Counter: Vertical\n if (Math.abs(childBox.height - availableHeight) < EPSILON && availableHeight > 0) {\n inferredChildAlign = \"STRETCH\";\n }\n } else {\n // Counter: Horizontal\n if (Math.abs(childBox.width - availableWidth) < EPSILON && availableWidth > 0) {\n inferredChildAlign = \"STRETCH\";\n }\n }\n\n if (inferredChildAlign) {\n childProperties[child.id] = { layoutAlign: inferredChildAlign };\n }\n });\n\n return {\n properties: result,\n childProperties,\n };\n}\n\nexport function applyInferredLayout<T extends LayoutNode>(parentNode: T, result: InferResult): T {\n const { properties, childProperties } = result;\n\n if (properties.layoutMode === \"NONE\") {\n return parentNode;\n }\n\n return {\n ...parentNode,\n ...properties,\n children: parentNode.children.map((child) => {\n const props = childProperties[child.id];\n if (props) {\n return { ...child, ...props };\n }\n return child;\n }),\n };\n}\n","export declare const metadata: {\n \"name\": \"🔵 [Template] Select Box Group\",\n \"key\": \"a3d58bb8540600878742cdcf2608a4b3851667ec\",\n \"componentPropertyDefinitions\": {\n \"Control\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Checkbox\",\n \"Radio\"\n ]\n },\n \"Item Count\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"1\",\n \"2\",\n \"3\",\n \"4\",\n \"5\",\n \"6\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Action Button\",\n \"key\": \"450ede9d0bf42fc6ef14345c77e6e407d6d5ee89\",\n \"componentPropertyDefinitions\": {\n \"Suffix Icon#5987:244\": {\n \"type\": \"INSTANCE_SWAP\",\n \"preferredValues\": []\n },\n \"Icon#7574:0\": {\n \"type\": \"INSTANCE_SWAP\",\n \"preferredValues\": []\n },\n \"Prefix Icon#5987:305\": {\n \"type\": \"INSTANCE_SWAP\",\n \"preferredValues\": []\n },\n \"Label#5987:61\": {\n \"type\": \"TEXT\"\n },\n \"Size\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"XSmall\",\n \"Small\",\n \"Medium\",\n \"Large\"\n ]\n },\n \"Layout\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Text Only\",\n \"Icon First\",\n \"Icon Last\",\n \"Icon Only\"\n ]\n },\n \"Variant\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Neutral Solid\",\n \"Neutral Weak\",\n \"Neutral Outline\",\n \"Brand Solid\",\n \"Brand Outline\",\n \"Critical Solid\"\n ]\n },\n \"State\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Enabled\",\n \"Pressed\",\n \"Loading\",\n \"Disabled\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Action Chip\",\n \"key\": \"3d21594ef116e94a9465d507447b858aea062575\",\n \"componentPropertyDefinitions\": {\n \"Icon#8714:0\": {\n \"type\": \"INSTANCE_SWAP\",\n \"preferredValues\": []\n },\n \"Prefix Icon#8711:0\": {\n \"type\": \"INSTANCE_SWAP\",\n \"preferredValues\": [\n {\n \"type\": \"COMPONENT_SET\",\n \"key\": \"8ed05ef62a40f2dc034ee7eb6945bd0e63ad49aa\"\n }\n ]\n },\n \"Suffix Icon#8711:3\": {\n \"type\": \"INSTANCE_SWAP\",\n \"preferredValues\": []\n },\n \"Label#7185:0\": {\n \"type\": \"TEXT\"\n },\n \"Show Count#7185:42\": {\n \"type\": \"BOOLEAN\"\n },\n \"Count#7185:21\": {\n \"type\": \"TEXT\"\n },\n \"Size\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Medium\",\n \"Small\"\n ]\n },\n \"Layout\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Text Only\",\n \"Icon First\",\n \"Icon Last\",\n \"Icon Both\",\n \"Icon Only\"\n ]\n },\n \"State\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Enabled\",\n \"Pressed\",\n \"Disabled\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Action Sheet\",\n \"key\": \"7c29b70b7e71618e1894c26f61f336de2730d76e\",\n \"componentPropertyDefinitions\": {\n \"Description#15641:70\": {\n \"type\": \"TEXT\"\n },\n \"Title#15641:37\": {\n \"type\": \"TEXT\"\n },\n \"OS Indicator (Figma Only)#15641:20\": {\n \"type\": \"BOOLEAN\"\n },\n \"Type\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Normal\",\n \"Destructive\"\n ]\n },\n \"Header\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Title Only\",\n \"Description Only\",\n \"Title With Description\",\n \"None\"\n ]\n },\n \"Action Count\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"1\",\n \"2\",\n \"3\",\n \"4\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Avatar\",\n \"key\": \"d71644aeba2e29deda366798fdfe35977166d120\",\n \"componentPropertyDefinitions\": {\n \"Show Image#71850:57\": {\n \"type\": \"BOOLEAN\"\n },\n \"Show Badge#1398:26\": {\n \"type\": \"BOOLEAN\"\n },\n \"Size\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"20\",\n \"24\",\n \"36\",\n \"42\",\n \"48\",\n \"64\",\n \"80\",\n \"96\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Avatar Stack\",\n \"key\": \"019467fdad2192abb48699dcfb79e344df04b799\",\n \"componentPropertyDefinitions\": {\n \"Size\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"20\",\n \"24\",\n \"36\",\n \"48\",\n \"64\"\n ]\n },\n \"Item Count\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"2\",\n \"3\",\n \"4\",\n \"5\"\n ]\n },\n \"Top Item\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Last Item\",\n \"First Item\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Badge\",\n \"key\": \"04609a35d47a1a0ef4904b3c25f79451892a85a1\",\n \"componentPropertyDefinitions\": {\n \"Label#1584:0\": {\n \"type\": \"TEXT\"\n },\n \"Size\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Medium\",\n \"Large\"\n ]\n },\n \"Tone\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Neutral\",\n \"Brand\",\n \"Informative\",\n \"Positive\",\n \"Critical\"\n ]\n },\n \"Variant\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Solid\",\n \"Weak\",\n \"Outline\"\n ]\n },\n \"Shape\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Rectangle\",\n \"Pill\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Callout\",\n \"key\": \"ec46d38baac3c367c4a5ffa47a2110d51ba0a4fe\",\n \"componentPropertyDefinitions\": {\n \"Show Icon#12598:229\": {\n \"type\": \"BOOLEAN\"\n },\n \"Icon#12598:210\": {\n \"type\": \"INSTANCE_SWAP\",\n \"preferredValues\": [\n {\n \"type\": \"COMPONENT_SET\",\n \"key\": \"f2c04b68b0bec4ec9145d832de45947030d3b653\"\n }\n ]\n },\n \"Interaction\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Default\",\n \"Actionable\",\n \"Dismissible\"\n ]\n },\n \"Tone\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Neutral\",\n \"Informative\",\n \"Warning\",\n \"Critical\",\n \"Magic\"\n ]\n },\n \"State\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Enabled\",\n \"Pressed\"\n ]\n },\n \"Show Title\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"True\",\n \"False\"\n ]\n },\n \"Show Link Label\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"True\",\n \"False\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Checkbox\",\n \"key\": \"94a2f6957a86f8ae3b8c7ca200dfcd5e29f6075b\",\n \"componentPropertyDefinitions\": {\n \"Label#49990:0\": {\n \"type\": \"TEXT\"\n },\n \"Size\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Medium\",\n \"Large\"\n ]\n },\n \"Shape\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Square\",\n \"Ghost\"\n ]\n },\n \"Weight\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Default\",\n \"Stronger\"\n ]\n },\n \"State\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Enabled\",\n \"Pressed\",\n \"Selected\",\n \"Selected-Pressed\",\n \"Indeterminate\",\n \"Indeterminate-Pressed\",\n \"Disabled\",\n \"Disabled-Selected\",\n \"Disabled-Indeterminate\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Chip Tablist\",\n \"key\": \"d098159beacf7713e9116f0ef38d8a20f64ec84f\",\n \"componentPropertyDefinitions\": {\n \"Tab Count\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"2\",\n \"3\",\n \"4\",\n \"5\",\n \"6\",\n \"Max\"\n ]\n },\n \"Variant\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Neutral Solid\",\n \"Brand Solid\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Control Chip\",\n \"key\": \"5780d56fc2f9bc4bbd6bc3db93949d8a8b7b7563\",\n \"componentPropertyDefinitions\": {\n \"Suffix Icon#8722:82\": {\n \"type\": \"INSTANCE_SWAP\",\n \"preferredValues\": []\n },\n \"Label#7185:0\": {\n \"type\": \"TEXT\"\n },\n \"Show Count#7185:42\": {\n \"type\": \"BOOLEAN\"\n },\n \"Icon#8722:41\": {\n \"type\": \"INSTANCE_SWAP\",\n \"preferredValues\": []\n },\n \"Prefix Icon#8722:0\": {\n \"type\": \"INSTANCE_SWAP\",\n \"preferredValues\": [\n {\n \"type\": \"COMPONENT_SET\",\n \"key\": \"8ed05ef62a40f2dc034ee7eb6945bd0e63ad49aa\"\n }\n ]\n },\n \"Count#7185:21\": {\n \"type\": \"TEXT\"\n },\n \"Size\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Medium\",\n \"Small\"\n ]\n },\n \"Layout\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Text Only\",\n \"Icon First\",\n \"Icon Last\",\n \"Icon Both\",\n \"Icon Only\"\n ]\n },\n \"State\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Enabled\",\n \"Pressed\",\n \"Selected\",\n \"Selected-Pressed\",\n \"Disabled\",\n \"Disabled-Selected\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Error State\",\n \"key\": \"39b4ecd0b5b4d35f4dc5791765ca04aa062a5172\",\n \"componentPropertyDefinitions\": {\n \"Secondary Action Label#17042:0\": {\n \"type\": \"TEXT\"\n },\n \"Title#16237:0\": {\n \"type\": \"TEXT\"\n },\n \"Description#16237:5\": {\n \"type\": \"TEXT\"\n },\n \"Show Buttons#9080:5\": {\n \"type\": \"BOOLEAN\"\n },\n \"Variant\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Default\",\n \"Basement\"\n ]\n },\n \"Layout\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"With Title\",\n \"Description Only\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Extended Action Sheet\",\n \"key\": \"cd4cf8a850bf3de87b79080b36b421a649bf3fcb\",\n \"componentPropertyDefinitions\": {\n \"Show Title#17043:12\": {\n \"type\": \"BOOLEAN\"\n },\n \"OS Indicator (Figma Only)#81637:129\": {\n \"type\": \"BOOLEAN\"\n },\n \"Description#14599:13\": {\n \"type\": \"TEXT\"\n },\n \"Title#14599:0\": {\n \"type\": \"TEXT\"\n },\n \"Type\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Normal\",\n \"Destructive\"\n ]\n },\n \"Action Group Count\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"1\",\n \"2\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Extended Floating Action Button\",\n \"key\": \"032f3fddaad0aa3fa5a7f680768c1f5d02fb463f\",\n \"componentPropertyDefinitions\": {\n \"Label#28936:0\": {\n \"type\": \"TEXT\"\n },\n \"Icon#28796:0\": {\n \"type\": \"INSTANCE_SWAP\",\n \"preferredValues\": []\n },\n \"Size\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Small\",\n \"Medium\"\n ]\n },\n \"Variant\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Neutral Solid\",\n \"Layer Floating\"\n ]\n },\n \"State\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Enabled\",\n \"Pressed\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Floating Action Button\",\n \"key\": \"1974b94703032585bb9e20bd54743e01094b965c\",\n \"componentPropertyDefinitions\": {\n \"Icon#28796:0\": {\n \"type\": \"INSTANCE_SWAP\",\n \"preferredValues\": []\n },\n \"State\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Enabled\",\n \"Pressed\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Help Bubble\",\n \"key\": \"804b327c091278a40d5891939eaed90bb2889659\",\n \"componentPropertyDefinitions\": {\n \"Title#62535:0\": {\n \"type\": \"TEXT\"\n },\n \"Description#62535:98\": {\n \"type\": \"TEXT\"\n },\n \"Show Description#62499:0\": {\n \"type\": \"BOOLEAN\"\n },\n \"Placement\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Right-Top\",\n \"Right-Center\",\n \"Right-Bottom\",\n \"Left-Top\",\n \"Left-Center\",\n \"Left-Bottom\",\n \"Bottom-Left\",\n \"Bottom-Center\",\n \"Bottom-Right\",\n \"Top-Left\",\n \"Top-Center\",\n \"Top-Right\"\n ]\n },\n \"Show Close Button\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"True\",\n \"False\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Identity Placeholder\",\n \"key\": \"808206c07408aa1056ec85a55925e9844e9265c2\",\n \"componentPropertyDefinitions\": {\n \"Identity\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Person\",\n \"Business\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Inline Banner\",\n \"key\": \"ce587d0f21754af05240cb32a4880227cb0ea1e1\",\n \"componentPropertyDefinitions\": {\n \"Show Icon#11840:27\": {\n \"type\": \"BOOLEAN\"\n },\n \"Link Label#1547:81\": {\n \"type\": \"TEXT\"\n },\n \"Interaction\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Default\",\n \"Link\",\n \"Actionable\",\n \"Dismissible\"\n ]\n },\n \"Variant\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Neutral Weak\",\n \"Informative Weak\",\n \"Positive Weak\",\n \"Warning Solid\",\n \"Warning Weak\",\n \"Critical Solid\",\n \"Critical Weak\"\n ]\n },\n \"Show Title\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"True\",\n \"False\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Manner Temp Badge\",\n \"key\": \"ac5331cec7a2c75b671df5b85ef247dfd820dd2f\",\n \"componentPropertyDefinitions\": {\n \"State\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"L1 (~29.9)\",\n \"L2 (30.0~36.2)\",\n \"L3 (36.3~37.5)\",\n \"L4 (37.6~41.9)\",\n \"L5 (42~51.9)\",\n \"L6 (52~)\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Multiline Text Field\",\n \"key\": \"88b2399c930c85f9ce2972163a078bc684b84bbe\",\n \"componentPropertyDefinitions\": {\n \"Indicator#15327:286\": {\n \"type\": \"TEXT\"\n },\n \"Character Count#15327:360\": {\n \"type\": \"TEXT\"\n },\n \"Max Character Count#15327:175\": {\n \"type\": \"TEXT\"\n },\n \"Label#15327:323\": {\n \"type\": \"TEXT\"\n },\n \"Filled Text#1304:0\": {\n \"type\": \"TEXT\"\n },\n \"Show Character count#958:75\": {\n \"type\": \"BOOLEAN\"\n },\n \"Show Footer#958:25\": {\n \"type\": \"BOOLEAN\"\n },\n \"Show Description#958:50\": {\n \"type\": \"BOOLEAN\"\n },\n \"Description#15327:212\": {\n \"type\": \"TEXT\"\n },\n \"Show Indicator#1259:0\": {\n \"type\": \"BOOLEAN\"\n },\n \"Placeholder#958:0\": {\n \"type\": \"TEXT\"\n },\n \"Show Header#870:0\": {\n \"type\": \"BOOLEAN\"\n },\n \"Size\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Medium\",\n \"Large\",\n \"XLarge\"\n ]\n },\n \"State\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Enabled\",\n \"Focused\",\n \"Invalid\",\n \"Invalid-Focused\",\n \"Disabled\",\n \"Read Only\"\n ]\n },\n \"Filled\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"True\",\n \"False\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Progress Circle\",\n \"key\": \"6e6779a372cab2485a0e25529bc4dbc9932a7346\",\n \"componentPropertyDefinitions\": {\n \"Size\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"24\",\n \"40\"\n ]\n },\n \"Tone\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Neutral\",\n \"Brand\",\n \"Static White\"\n ]\n },\n \"Value\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Indeterminate\",\n \"0%\",\n \"25%\",\n \"75%\",\n \"100%\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Reaction Button\",\n \"key\": \"ec43e4e881f7048e95601f8b58c01a0905a174e0\",\n \"componentPropertyDefinitions\": {\n \"Icon#12379:0\": {\n \"type\": \"INSTANCE_SWAP\",\n \"preferredValues\": []\n },\n \"Show Count#6397:33\": {\n \"type\": \"BOOLEAN\"\n },\n \"Count#15816:0\": {\n \"type\": \"TEXT\"\n },\n \"Label#6397:0\": {\n \"type\": \"TEXT\"\n },\n \"Size\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"XSmall\",\n \"Small\"\n ]\n },\n \"State\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Enabled\",\n \"Pressed\",\n \"Loading\",\n \"Selected\",\n \"Selected-Pressed\",\n \"Selected-Loading\",\n \"Disabled\",\n \"Disabled-Selected\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Segmented Control\",\n \"key\": \"3ad7133ba52755867f42f9232375f75639e00d58\",\n \"componentPropertyDefinitions\": {\n \"Item Count\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"2\",\n \"3\",\n \"4\"\n ]\n },\n \"Selected Item\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"1\",\n \"2\",\n \"3\",\n \"4\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Select Box\",\n \"key\": \"38722ffeb4c966256a709155e8ddac50c93d7c60\",\n \"componentPropertyDefinitions\": {\n \"Label#3635:0\": {\n \"type\": \"TEXT\"\n },\n \"Description #3033:5\": {\n \"type\": \"TEXT\"\n },\n \"Show Description#3033:0\": {\n \"type\": \"BOOLEAN\"\n },\n \"Control\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Checkbox\",\n \"Radio\"\n ]\n },\n \"State\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Enabled\",\n \"Pressed\",\n \"Selected\",\n \"Selected-Pressed\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Skeleton\",\n \"key\": \"ef22c3288722fbfa64a5ab73df397ade88f8e05a\",\n \"componentPropertyDefinitions\": {\n \"Radius\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"0\",\n \"8\",\n \"16\",\n \"Full\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Snackbar\",\n \"key\": \"81b17fb8c7d731a19cf8d36a8605559d41414eca\",\n \"componentPropertyDefinitions\": {\n \"Action Button Label#1528:8\": {\n \"type\": \"TEXT\"\n },\n \"Message#1528:4\": {\n \"type\": \"TEXT\"\n },\n \"Show Action Button#1528:0\": {\n \"type\": \"BOOLEAN\"\n },\n \"Variant\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Default\",\n \"Positive\",\n \"Critical\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Standard Navigation\",\n \"key\": \"c07bfe331cf214375fce9ad47cb6fdb459d1fb1b\",\n \"componentPropertyDefinitions\": {\n \"Title#28176:5\": {\n \"type\": \"BOOLEAN\"\n },\n \"Variant\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Layer Default\",\n \"Transparent\"\n ]\n },\n \"OS\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"iOS\",\n \"Android\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Switch\",\n \"key\": \"80ce5a33b5ab713ab3bd2449472e2fb13d78c7f3\",\n \"componentPropertyDefinitions\": {\n \"Label#15191:2\": {\n \"type\": \"TEXT\"\n },\n \"Size\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Small\",\n \"Medium\"\n ]\n },\n \"State\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Enabled\",\n \"Selected\",\n \"Disabled\",\n \"Disabled-Selected\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Tablist\",\n \"key\": \"ffe33411fb8796f7a95d3637b90150007f0dd954\",\n \"componentPropertyDefinitions\": {\n \"Size\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Small\",\n \"Medium\"\n ]\n },\n \"Layout\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Hug\",\n \"Fill\"\n ]\n },\n \"Tab Count\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"2\",\n \"3\",\n \"4\",\n \"5+\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Text Button\",\n \"key\": \"601f788792916250e33d04bd3165dee1404342df\",\n \"componentPropertyDefinitions\": {\n \"Prefix Icon#7561:0\": {\n \"type\": \"INSTANCE_SWAP\",\n \"preferredValues\": []\n },\n \"Label#6148:0\": {\n \"type\": \"TEXT\"\n },\n \"Size\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Small\",\n \"Medium\",\n \"Large\"\n ]\n },\n \"Layout\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Icon First\",\n \"Icon Last\"\n ]\n },\n \"Tone\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Neutral\",\n \"Neutral Subtle\",\n \"Brand\",\n \"Critical\"\n ]\n },\n \"State\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Enabled\",\n \"Pressed\",\n \"Disabled\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Text Field\",\n \"key\": \"c49873c37a639f0dffdea4efd0eb43760d66c141\",\n \"componentPropertyDefinitions\": {\n \"Suffix Text#15327:138\": {\n \"type\": \"TEXT\"\n },\n \"Indicator#15327:249\": {\n \"type\": \"TEXT\"\n },\n \"Label#14964:0\": {\n \"type\": \"TEXT\"\n },\n \"Character Count#15327:64\": {\n \"type\": \"TEXT\"\n },\n \"Description#12626:5\": {\n \"type\": \"TEXT\"\n },\n \"Filled Text#1304:0\": {\n \"type\": \"TEXT\"\n },\n \"Show Suffix Icon#1267:75\": {\n \"type\": \"BOOLEAN\"\n },\n \"Show Prefix Icon#1267:50\": {\n \"type\": \"BOOLEAN\"\n },\n \"Prefix Icon#1267:25\": {\n \"type\": \"INSTANCE_SWAP\",\n \"preferredValues\": []\n },\n \"Show Prefix#958:125\": {\n \"type\": \"BOOLEAN\"\n },\n \"Show Suffix#958:100\": {\n \"type\": \"BOOLEAN\"\n },\n \"Show Character Count#958:75\": {\n \"type\": \"BOOLEAN\"\n },\n \"Show Footer#958:25\": {\n \"type\": \"BOOLEAN\"\n },\n \"Max Character Count#15327:27\": {\n \"type\": \"TEXT\"\n },\n \"Show Prefix Text#1267:0\": {\n \"type\": \"BOOLEAN\"\n },\n \"Show Suffix Text#1267:125\": {\n \"type\": \"BOOLEAN\"\n },\n \"Suffix Icon #1267:100\": {\n \"type\": \"INSTANCE_SWAP\",\n \"preferredValues\": []\n },\n \"Show Description#958:50\": {\n \"type\": \"BOOLEAN\"\n },\n \"Prefix Text#15327:101\": {\n \"type\": \"TEXT\"\n },\n \"Show Indicator#1259:0\": {\n \"type\": \"BOOLEAN\"\n },\n \"Placeholder#958:0\": {\n \"type\": \"TEXT\"\n },\n \"Show Header#870:0\": {\n \"type\": \"BOOLEAN\"\n },\n \"Size\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Medium\",\n \"Large\",\n \"XLarge\"\n ]\n },\n \"State\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Enabled\",\n \"Focused\",\n \"Invalid\",\n \"Invalid-Focused\",\n \"Disabled\",\n \"Read Only\"\n ]\n },\n \"Filled\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"True\",\n \"False\"\n ]\n }\n }\n};\n","export declare const metadata: {\n \"name\": \"🟢 Toggle Button\",\n \"key\": \"1d240ee5fd7a56879713e69cbea1b6f006f0ea22\",\n \"componentPropertyDefinitions\": {\n \"Suffix Icon#6122:343\": {\n \"type\": \"INSTANCE_SWAP\",\n \"preferredValues\": []\n },\n \"Show Prefix Icon#6122:392\": {\n \"type\": \"BOOLEAN\"\n },\n \"Show Suffix Icon#6122:147\": {\n \"type\": \"BOOLEAN\"\n },\n \"Prefix Icon#6122:98\": {\n \"type\": \"INSTANCE_SWAP\",\n \"preferredValues\": []\n },\n \"Label#6122:49\": {\n \"type\": \"TEXT\"\n },\n \"Size\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Small\",\n \"XSmall\"\n ]\n },\n \"Variant\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Neutral Weak\",\n \"Brand Solid\"\n ]\n },\n \"State\": {\n \"type\": \"VARIANT\",\n \"variantOptions\": [\n \"Enabled\",\n \"Pressed\",\n \"Loading\",\n \"Selected\",\n \"Selected-Pressed\",\n \"Selected-Loading\",\n \"Disabled\",\n \"Disabled-Selected\"\n ]\n }\n }\n};\n","import type { InferComponentDefinition } from \"@/codegen/core\";\nimport type * as metadata from \"@/entities/data/__generated__/component-sets\";\n\nexport type ActionButtonProperties = InferComponentDefinition<\n typeof metadata.actionButton.componentPropertyDefinitions\n>;\n\nexport type ActionChipProperties = InferComponentDefinition<\n typeof metadata.actionChip.componentPropertyDefinitions\n>;\n\nexport type ActionSheetProperties = InferComponentDefinition<\n typeof metadata.actionSheet.componentPropertyDefinitions\n>;\n\nexport type ActionSheetItemProperties = InferComponentDefinition<{\n \"Label#15420:4\": {\n type: \"TEXT\";\n defaultValue: \"액션 버튼\";\n };\n Tone: {\n type: \"VARIANT\";\n defaultValue: \"Default\";\n variantOptions: [\"Default\", \"Critical\"];\n };\n State: {\n type: \"VARIANT\";\n defaultValue: \"Enabled\";\n variantOptions: [\"Enabled\", \"Enabled-Pressed\", \"Disabled\"];\n };\n}>;\n\nexport type AvatarProperties = InferComponentDefinition<\n typeof metadata.avatar.componentPropertyDefinitions\n>;\n\nexport type AvatarStackProperties = InferComponentDefinition<\n typeof metadata.avatarStack.componentPropertyDefinitions\n>;\n\nexport type BadgeProperties = InferComponentDefinition<\n typeof metadata.badge.componentPropertyDefinitions\n>;\n\nexport type CalloutProperties = InferComponentDefinition<\n typeof metadata.callout.componentPropertyDefinitions\n>;\n\nexport type CheckboxProperties = InferComponentDefinition<\n typeof metadata.checkbox.componentPropertyDefinitions\n>;\n\nexport type ChipTabsProperties = InferComponentDefinition<\n typeof metadata.chipTablist.componentPropertyDefinitions\n>;\n\nexport type ChipTabsItemProperties = InferComponentDefinition<{\n \"Label#8876:0\": {\n type: \"TEXT\";\n defaultValue: \"라벨\";\n };\n Variant: {\n type: \"VARIANT\";\n defaultValue: \"Neutral Solid\";\n variantOptions: [\"Neutral Solid\", \"Brand Solid\"];\n };\n State: {\n type: \"VARIANT\";\n defaultValue: \"Enabled\";\n variantOptions: [\n \"Enabled\",\n \"Enabled-Pressed\",\n \"Enabled-Selected\",\n \"Enabled-Selected-Pressed\",\n \"Disabled\",\n \"Disabled-Selected\",\n ];\n };\n}>;\n\nexport type ControlChipProperties = InferComponentDefinition<\n typeof metadata.controlChip.componentPropertyDefinitions\n>;\n\nexport type ErrorStateProperties = InferComponentDefinition<\n typeof metadata.errorState.componentPropertyDefinitions\n>;\n\nexport type ExtendedActionSheetProperties = InferComponentDefinition<\n typeof metadata.extendedActionSheet.componentPropertyDefinitions\n>;\n\nexport type ExtendedActionSheetGroupProperties = InferComponentDefinition<{\n \"Action Count\": {\n type: \"VARIANT\";\n defaultValue: \"8\";\n variantOptions: [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"];\n };\n}>;\n\nexport type ExtendedActionSheetItemProperties = InferComponentDefinition<{\n \"Show Prefix Icon#17043:5\": {\n type: \"BOOLEAN\";\n defaultValue: true;\n };\n \"Label#55905:8\": {\n type: \"TEXT\";\n defaultValue: \"액션 버튼\";\n };\n \"Prefix Icon#55948:0\": {\n type: \"INSTANCE_SWAP\";\n defaultValue: \"17024:100799\";\n preferredValues: [];\n };\n Tone: {\n type: \"VARIANT\";\n defaultValue: \"Neutral\";\n variantOptions: [\"Neutral\", \"Critical\"];\n };\n State: {\n type: \"VARIANT\";\n defaultValue: \"Enabled\";\n variantOptions: [\"Enabled\", \"Enabled-Pressed\", \"Disabled\"];\n };\n}>;\n\nexport type ExtendedFabProperties = InferComponentDefinition<\n typeof metadata.extendedFloatingActionButton.componentPropertyDefinitions\n>;\n\nexport type FabProperties = InferComponentDefinition<\n typeof metadata.floatingActionButton.componentPropertyDefinitions\n>;\n\nexport type HelpBubbleProperties = InferComponentDefinition<\n typeof metadata.helpBubble.componentPropertyDefinitions\n>;\n\nexport type IdentityPlaceholderProperties = InferComponentDefinition<\n typeof metadata.identityPlaceholder.componentPropertyDefinitions\n>;\n\nexport type InlineBannerProperties = InferComponentDefinition<\n typeof metadata.inlineBanner.componentPropertyDefinitions\n>;\n\nexport type MannerTempBadgeProperties = InferComponentDefinition<\n typeof metadata.mannerTempBadge.componentPropertyDefinitions\n>;\n\nexport type MultilineTextFieldProperties = InferComponentDefinition<\n typeof metadata.multilineTextField.componentPropertyDefinitions\n>;\n\nexport type ProgressCircleProperties = InferComponentDefinition<\n typeof metadata.progressCircle.componentPropertyDefinitions\n>;\n\nexport type ReactionButtonProperties = InferComponentDefinition<\n typeof metadata.reactionButton.componentPropertyDefinitions\n>;\n\nexport type SegmentedControlProperties = InferComponentDefinition<\n typeof metadata.segmentedControl.componentPropertyDefinitions\n>;\n\nexport type SegmentedControlItemProperties = InferComponentDefinition<{\n \"Label#11366:15\": {\n type: \"TEXT\";\n defaultValue: \"라벨\";\n };\n State: {\n type: \"VARIANT\";\n defaultValue: \"Enabled-Selected\";\n variantOptions: [\n \"Enabled\",\n \"Enabled-Selected\",\n \"Enabled-Pressed\",\n \"Enabled-Selected-Pressed\",\n \"Disabled\",\n \"Disabled-Selected\",\n ];\n };\n}>;\n\nexport type SelectBoxGroupProperties = InferComponentDefinition<\n typeof metadata.templateSelectBoxGroup.componentPropertyDefinitions\n>;\n\nexport type SelectBoxProperties = InferComponentDefinition<\n typeof metadata.selectBox.componentPropertyDefinitions\n>;\n\nexport type SkeletonProperties = InferComponentDefinition<\n typeof metadata.skeleton.componentPropertyDefinitions\n>;\n\nexport type SnackbarProperties = InferComponentDefinition<\n typeof metadata.snackbar.componentPropertyDefinitions\n>;\n\nexport type SwitchProperties = InferComponentDefinition<\n typeof metadata.switch.componentPropertyDefinitions\n>;\n\nexport type TabsProperties = InferComponentDefinition<\n typeof metadata.tablist.componentPropertyDefinitions\n>;\n\nexport type TabsHugItemProperties = InferComponentDefinition<{\n \"Label#4478:2\": {\n type: \"TEXT\";\n defaultValue: \"라벨\";\n };\n Size: {\n type: \"VARIANT\";\n defaultValue: \"Small\";\n variantOptions: [\"Small\", \"Medium\"];\n };\n Notification: {\n type: \"VARIANT\";\n defaultValue: \"False\";\n variantOptions: [\"True\", \"False\"];\n };\n State: {\n type: \"VARIANT\";\n defaultValue: \"Enabled-Selected\";\n variantOptions: [\"Enabled\", \"Enabled-Selected\", \"Disabled\"];\n };\n}>;\n\nexport type TabsFillItemProperties = InferComponentDefinition<{\n \"Label#4478:2\": {\n type: \"TEXT\";\n defaultValue: \"라벨\";\n };\n Size: {\n type: \"VARIANT\";\n defaultValue: \"Small\";\n variantOptions: [\"Small\", \"Medium\"];\n };\n Notification: {\n type: \"VARIANT\";\n defaultValue: \"False\";\n variantOptions: [\"True\", \"False\"];\n };\n State: {\n type: \"VARIANT\";\n defaultValue: \"Enabled-Selected\";\n variantOptions: [\"Enabled\", \"Enabled-Selected\", \"Disabled\"];\n };\n}>;\n\nexport type TextButtonProperties = InferComponentDefinition<\n typeof metadata.textButton.componentPropertyDefinitions\n>;\n\nexport type TextFieldProperties = InferComponentDefinition<\n typeof metadata.textField.componentPropertyDefinitions\n>;\n\nexport type ToggleButtonProperties = InferComponentDefinition<\n typeof metadata.toggleButton.componentPropertyDefinitions\n>;\n\nexport type AppBarProperties = InferComponentDefinition<\n typeof metadata.standardNavigation.componentPropertyDefinitions\n>;\n\nexport type AppBarMainProperties = InferComponentDefinition<{\n \"Show Right#16958:13\": {\n type: \"BOOLEAN\";\n defaultValue: false;\n };\n \"Subtitle#16958:9\": {\n type: \"TEXT\";\n defaultValue: \"서브타이틀\";\n };\n \"Logo#16958:5\": {\n type: \"INSTANCE_SWAP\";\n defaultValue: \"1574:3942\";\n preferredValues: [\n {\n type: \"COMPONENT_SET\";\n key: \"c7dab3f6d0df0a150564e696c0df00bd43ffef3f\";\n },\n ];\n };\n \"Show Left#16958:17\": {\n type: \"BOOLEAN\";\n defaultValue: false;\n };\n \"Title#16944:0\": {\n type: \"TEXT\";\n defaultValue: \"타이틀\";\n };\n Type: {\n type: \"VARIANT\";\n defaultValue: \"Title\";\n variantOptions: [\"Title\", \"Title-Subtitle\", \"Logo\"];\n };\n}>;\n\nexport type AppBarLeftProperties = InferComponentDefinition<{\n Action: {\n type: \"VARIANT\";\n defaultValue: \"Back\";\n variantOptions: [\"Back\", \"Close\", \"Other\"];\n };\n}>;\n\nexport type AppBarRightProperties = InferComponentDefinition<{\n Type: {\n type: \"VARIANT\";\n defaultValue: \"1 Icon\";\n variantOptions: [\"1 Icon\", \"2 Icons\", \"3 Icons\", \"1 Text\"];\n };\n}>;\n","import { createCodeGenerator, createValueResolver } from \"@/codegen/core\";\nimport type { CodeGenerator } from \"@/codegen/core/codegen\";\nimport { styleService, variableService } from \"@/codegen/default-services\";\nimport { componentRepository } from \"@/entities\";\nimport { createFrameTransformer } from \"./frame\";\nimport { createInstanceTransformer } from \"./instance\";\nimport {\n createContainerLayoutPropsConverter,\n createFrameFillPropsConverter,\n createRadiusPropsConverter,\n createSelfLayoutPropsConverter,\n createShapeFillPropsConverter,\n createStrokePropsConverter,\n createTextFillPropsConverter,\n createTypeStylePropsConverter,\n} from \"./props\";\nimport {\n createBooleanOperationTransformer,\n createRectangleTransformer,\n createVectorTransformer,\n} from \"./shape\";\nimport { createTextTransformer } from \"./text\";\nimport {\n defaultRawValueFormatters,\n defaultStyleNameFormatter,\n defaultVariableNameFormatter,\n} from \"./value-resolver\";\n\nexport interface CreatePipelineConfig {\n shouldInferAutoLayout?: boolean;\n shouldInferVariableName?: boolean;\n}\n\nexport function createPipeline(options: CreatePipelineConfig = {}): CodeGenerator {\n const { shouldInferAutoLayout = true, shouldInferVariableName = true } = options;\n\n const valueResolver = createValueResolver({\n variableService,\n variableNameFormatter: defaultVariableNameFormatter,\n styleService,\n styleNameFormatter: defaultStyleNameFormatter,\n rawValueFormatters: defaultRawValueFormatters,\n shouldInferVariableName,\n });\n\n const containerLayoutPropsConverter = createContainerLayoutPropsConverter(valueResolver);\n const selfLayoutPropsConverter = createSelfLayoutPropsConverter(valueResolver);\n const frameFillPropsConverter = createFrameFillPropsConverter(valueResolver);\n const shapeFillPropsConverter = createShapeFillPropsConverter(valueResolver);\n const textFillPropsConverter = createTextFillPropsConverter(valueResolver);\n const radiusPropsConverter = createRadiusPropsConverter(valueResolver);\n const strokePropsConverter = createStrokePropsConverter(valueResolver);\n const typeStylePropsConverter = createTypeStylePropsConverter(valueResolver);\n\n const propsConverters = {\n containerLayout: containerLayoutPropsConverter,\n selfLayout: selfLayoutPropsConverter,\n frameFill: frameFillPropsConverter,\n shapeFill: shapeFillPropsConverter,\n textFill: textFillPropsConverter,\n radius: radiusPropsConverter,\n stroke: strokePropsConverter,\n typeStyle: typeStylePropsConverter,\n };\n\n const frameTransformer = createFrameTransformer({\n propsConverters,\n });\n const instanceTransformer = createInstanceTransformer({\n frameTransformer,\n componentRepository,\n });\n const textTransformer = createTextTransformer({\n propsConverters,\n });\n const rectangleTransformer = createRectangleTransformer({\n propsConverters,\n });\n const vectorTransformer = createVectorTransformer({\n propsConverters,\n });\n const booleanOperationTransformer = createBooleanOperationTransformer({\n propsConverters,\n });\n\n const codegenTransformer = createCodeGenerator({\n frameTransformer,\n textTransformer,\n rectangleTransformer,\n instanceTransformer,\n vectorTransformer,\n booleanOperationTransformer,\n shouldInferAutoLayout,\n });\n\n return codegenTransformer;\n}\n","import type { ValueResolver } from \"@/codegen/core\";\nimport { toCssRgba } from \"@/utils/css\";\n\nexport type FigmaValueResolver = ValueResolver<string, number, number, number>;\n\nexport const defaultVariableNameFormatter = ({ slug }: { slug: string[] }) =>\n slug\n .filter((s) => s !== \"dimension\")\n .map((s) => s.replaceAll(\",\", \"_\"))\n .join(\"/\");\n\nexport const defaultStyleNameFormatter = ({ slug }: { slug: string[] }) => slug[slug.length - 1]!;\n\nexport const defaultRawValueFormatters = {\n color: (value: RGBA) => toCssRgba(value),\n dimension: (value: number) => value,\n fontDimension: (value: number) => value,\n fontWeight: (value: number) => value,\n};\n","import { createPropsConverter, definePropsConverter, type PropsConverter } from \"@/codegen/core\";\nimport type {\n NormalizedCornerTrait,\n NormalizedHasChildrenTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasLayoutTrait,\n NormalizedIsLayerTrait,\n NormalizedTypePropertiesTrait,\n} from \"@/normalizer\";\nimport type { FigmaValueResolver } from \"./value-resolver\";\n\nexport interface PropsConverters {\n containerLayout: PropsConverter<ContainerLayoutTrait, ContainerLayoutProps>;\n selfLayout: PropsConverter<SelfLayoutTrait, SelfLayoutProps>;\n radius: PropsConverter<RadiusTrait, RadiusProps>;\n frameFill: PropsConverter<FillTrait, FillProps>;\n shapeFill: PropsConverter<FillTrait, FillProps>;\n textFill: PropsConverter<FillTrait, FillProps>;\n stroke: PropsConverter<StrokeTrait, StrokeProps>;\n typeStyle: PropsConverter<TypeStyleTrait, TypeStyleProps>;\n}\n\nexport type ContainerLayoutTrait = NormalizedHasFramePropertiesTrait &\n NormalizedHasChildrenTrait &\n NormalizedHasLayoutTrait &\n NormalizedIsLayerTrait;\n\nexport type SelfLayoutTrait = NormalizedIsLayerTrait & NormalizedHasLayoutTrait;\n\nexport type RadiusTrait = NormalizedCornerTrait & NormalizedIsLayerTrait;\n\nexport type FillTrait = NormalizedIsLayerTrait & NormalizedHasGeometryTrait;\n\nexport type StrokeTrait = NormalizedIsLayerTrait & NormalizedHasGeometryTrait;\n\nexport type TypeStyleTrait = NormalizedTypePropertiesTrait & NormalizedIsLayerTrait;\n\nexport interface ContainerLayoutProps {\n layoutMode?: \"HORIZONTAL\" | \"VERTICAL\" | \"NONE\";\n primaryAxisAlignItems?: \"MIN\" | \"CENTER\" | \"MAX\" | \"SPACE_BETWEEN\";\n counterAxisAlignItems?: \"MIN\" | \"CENTER\" | \"MAX\" | \"BASELINE\";\n layoutWrap?: \"WRAP\" | \"NO_WRAP\";\n itemSpacing?: number | string; // string when variable\n paddingTop?: number | string; // string when variable\n paddingBottom?: number | string; // string when variable\n paddingLeft?: number | string; // string when variable\n paddingRight?: number | string; // string when variable\n horizontalPadding?: number | string; // string when variable\n verticalPadding?: number | string; // string when variable\n}\n\nexport function createContainerLayoutPropsConverter(\n valueResolver: FigmaValueResolver,\n): PropsConverter<ContainerLayoutTrait, ContainerLayoutProps> {\n return createPropsConverter({\n _types: {\n trait: {} as ContainerLayoutTrait,\n props: {} as ContainerLayoutProps,\n },\n handlers: {\n layoutMode: ({ layoutMode }) => layoutMode ?? \"NONE\",\n primaryAxisAlignItems: ({ primaryAxisAlignItems }) => primaryAxisAlignItems,\n counterAxisAlignItems: ({ counterAxisAlignItems }) => counterAxisAlignItems,\n layoutWrap: ({ layoutWrap }) => layoutWrap,\n itemSpacing: (node) => valueResolver.getFormattedValue.itemSpacing(node),\n paddingTop: (node) => valueResolver.getFormattedValue.paddingTop(node),\n paddingBottom: (node) => valueResolver.getFormattedValue.paddingBottom(node),\n paddingLeft: (node) => valueResolver.getFormattedValue.paddingLeft(node),\n paddingRight: (node) => valueResolver.getFormattedValue.paddingRight(node),\n },\n shorthands: {\n horizontalPadding: [\"paddingLeft\", \"paddingRight\"],\n verticalPadding: [\"paddingTop\", \"paddingBottom\"],\n },\n defaults: {},\n });\n}\n\nexport interface SelfLayoutProps {\n layoutGrow?: number;\n layoutAlign?: \"STRETCH\" | \"INHERIT\" | \"MIN\" | \"CENTER\" | \"MAX\";\n layoutSizingVertical?: \"FIXED\" | \"HUG\" | \"FILL\";\n layoutSizingHorizontal?: \"FIXED\" | \"HUG\" | \"FILL\";\n width?: string | number; // string when variable\n height?: string | number; // string when variable\n minWidth?: string | number; // string when variable\n minHeight?: string | number; // string when variable\n maxWidth?: string | number; // string when variable\n maxHeight?: string | number; // string when variable\n}\n\nexport function createSelfLayoutPropsConverter(\n valueResolver: FigmaValueResolver,\n): PropsConverter<SelfLayoutTrait, SelfLayoutProps> {\n return createPropsConverter({\n _types: {\n trait: {} as SelfLayoutTrait,\n props: {} as SelfLayoutProps,\n },\n handlers: {\n layoutGrow: ({ layoutGrow }) => layoutGrow,\n layoutAlign: ({ layoutAlign }) => layoutAlign,\n layoutSizingVertical: ({ layoutSizingVertical }) => layoutSizingVertical,\n layoutSizingHorizontal: ({ layoutSizingHorizontal }) => layoutSizingHorizontal,\n width: (node) => valueResolver.getFormattedValue.width(node),\n height: (node) => valueResolver.getFormattedValue.height(node),\n minWidth: (node) => valueResolver.getFormattedValue.minWidth(node),\n minHeight: (node) => valueResolver.getFormattedValue.minHeight(node),\n maxWidth: (node) => valueResolver.getFormattedValue.maxWidth(node),\n maxHeight: (node) => valueResolver.getFormattedValue.maxHeight(node),\n },\n defaults: {},\n });\n}\n\nexport interface RadiusProps {\n cornerRadius?: number | string; // string when variable\n topLeftRadius?: number | string; // string when variable\n topRightRadius?: number | string; // string when variable\n bottomLeftRadius?: number | string; // string when variable\n bottomRightRadius?: number | string; // string when variable\n}\n\nexport function createRadiusPropsConverter(valueResolver: FigmaValueResolver) {\n return createPropsConverter({\n _types: {\n trait: {} as RadiusTrait,\n props: {} as RadiusProps,\n },\n handlers: {\n topLeftRadius: (node) => valueResolver.getFormattedValue.topLeftRadius(node),\n topRightRadius: (node) => valueResolver.getFormattedValue.topRightRadius(node),\n bottomLeftRadius: (node) => valueResolver.getFormattedValue.bottomLeftRadius(node),\n bottomRightRadius: (node) => valueResolver.getFormattedValue.bottomRightRadius(node),\n },\n shorthands: {\n cornerRadius: [\"topLeftRadius\", \"topRightRadius\", \"bottomLeftRadius\", \"bottomRightRadius\"],\n },\n defaults: {\n cornerRadius: 0,\n topLeftRadius: 0,\n topRightRadius: 0,\n bottomLeftRadius: 0,\n bottomRightRadius: 0,\n },\n });\n}\n\nexport interface FillProps {\n fill?: string;\n}\n\nexport function createFrameFillPropsConverter(valueResolver: FigmaValueResolver) {\n return definePropsConverter<FillTrait, FillProps>((node: FillTrait) => {\n const fill = valueResolver.getFormattedValue.frameFill(node);\n\n return {\n fill,\n };\n });\n}\n\nexport function createShapeFillPropsConverter(valueResolver: FigmaValueResolver) {\n return definePropsConverter<FillTrait, FillProps>((node: FillTrait) => {\n const fill = valueResolver.getFormattedValue.shapeFill(node);\n\n return {\n fill,\n };\n });\n}\n\nexport function createTextFillPropsConverter(valueResolver: FigmaValueResolver) {\n return definePropsConverter<FillTrait, FillProps>((node: FillTrait) => {\n const fill = valueResolver.getFormattedValue.textFill(node);\n\n return {\n fill,\n };\n });\n}\n\nexport interface StrokeProps {\n stroke?: string;\n strokeWeight?: number;\n}\n\nexport function createStrokePropsConverter(\n valueResolver: FigmaValueResolver,\n): PropsConverter<StrokeTrait, StrokeProps> {\n return definePropsConverter((node: StrokeTrait) => {\n const stroke = valueResolver.getFormattedValue.stroke(node);\n const strokeWeight = node.strokeWeight;\n\n return {\n stroke,\n strokeWeight,\n };\n });\n}\n\nexport interface TypeStyleProps {\n textStyle?: string;\n fontSize?: string | number;\n fontWeight?: string | number;\n lineHeight?: string | number;\n maxLines?: number;\n}\n\nexport function createTypeStylePropsConverter(\n valueResolver: FigmaValueResolver,\n): PropsConverter<TypeStyleTrait, TypeStyleProps> {\n return definePropsConverter((node) => {\n const styleName = valueResolver.getTextStyleValue(node);\n const maxLines =\n node.style.textTruncation === \"ENDING\" ? (node.style.maxLines ?? undefined) : undefined;\n\n if (styleName) {\n return {\n textStyle: styleName,\n maxLines,\n };\n }\n\n return {\n fontSize: valueResolver.getFormattedValue.fontSize(node),\n fontWeight: valueResolver.getFormattedValue.fontWeight(node),\n lineHeight: valueResolver.getFormattedValue.lineHeight(node),\n maxLines,\n };\n });\n}\n","import type {\n NormalizedComponentNode,\n NormalizedFrameNode,\n NormalizedInstanceNode,\n} from \"@/normalizer\";\nimport { createElement, defineElementTransformer, type ElementTransformer } from \"../../core\";\nimport type { PropsConverters } from \"./props\";\n\nexport interface FrameTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createFrameTransformer({\n propsConverters,\n}: FrameTransformerDeps): ElementTransformer<\n NormalizedFrameNode | NormalizedInstanceNode | NormalizedComponentNode\n> {\n return defineElementTransformer(\n (node: NormalizedFrameNode | NormalizedInstanceNode | NormalizedComponentNode, traverse) => {\n const children = node.children;\n\n const props = {\n ...propsConverters.radius(node),\n ...propsConverters.containerLayout(node),\n ...propsConverters.selfLayout(node),\n ...propsConverters.frameFill(node),\n ...propsConverters.stroke(node),\n };\n\n return createElement(\n \"Frame\",\n props,\n children.map((child) => traverse(child)),\n );\n },\n );\n}\n","import type { ComponentRepository } from \"@/entities\";\nimport type { NormalizedInstanceNode } from \"@/normalizer\";\nimport { camelCase } from \"change-case\";\nimport { createElement, defineElementTransformer, type ElementTransformer } from \"../../core\";\n\nexport interface InstanceTransformerDeps {\n frameTransformer: ElementTransformer<NormalizedInstanceNode>;\n componentRepository?: ComponentRepository;\n}\n\nexport function createInstanceTransformer({\n frameTransformer,\n componentRepository,\n}: InstanceTransformerDeps): ElementTransformer<NormalizedInstanceNode> {\n const transform = defineElementTransformer((node: NormalizedInstanceNode, traverse) => {\n const component = componentRepository?.getOne(node.componentSetKey ?? node.componentKey);\n\n if (component) {\n return createElement(\"Instance\", {\n componentName: component.name,\n ...Object.fromEntries(\n Object.entries(node.componentProperties)\n .filter(([_, props]) => props.type === \"VARIANT\" || props.type === \"TEXT\")\n .map(([key, props]) => [\n camelCase(key.split(\"#\")[0]),\n camelCase(props.value as string),\n ]),\n ),\n });\n }\n\n return frameTransformer(node, traverse);\n });\n\n return transform;\n}\n","import type {\n NormalizedBooleanOperationNode,\n NormalizedRectangleNode,\n NormalizedVectorNode,\n} from \"@/normalizer\";\nimport { createElement, defineElementTransformer, type ElementTransformer } from \"../../core\";\nimport type { PropsConverters } from \"./props\";\n\nexport interface RectangleTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createRectangleTransformer({\n propsConverters,\n}: RectangleTransformerDeps): ElementTransformer<NormalizedRectangleNode> {\n return defineElementTransformer((node: NormalizedRectangleNode) => {\n return createElement(\"Rectangle\", { ...propsConverters.selfLayout(node) });\n });\n}\n\nexport interface VectorTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createVectorTransformer({\n propsConverters,\n}: VectorTransformerDeps): ElementTransformer<NormalizedVectorNode> {\n return defineElementTransformer((node) => {\n return createElement(\n \"Vector\",\n {\n ...propsConverters.selfLayout(node),\n ...propsConverters.radius(node),\n ...propsConverters.stroke(node),\n ...propsConverters.shapeFill(node),\n },\n [],\n {\n comment: \"Vector Node Placeholder\",\n },\n );\n });\n}\n\nexport interface BooleanOperationTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createBooleanOperationTransformer({\n propsConverters,\n}: BooleanOperationTransformerDeps): ElementTransformer<NormalizedBooleanOperationNode> {\n return defineElementTransformer((node, traverse) => {\n return createElement(\n \"BooleanOperation\",\n {\n ...propsConverters.selfLayout(node),\n ...propsConverters.radius(node),\n ...propsConverters.stroke(node),\n ...propsConverters.shapeFill(node),\n },\n node.children.map(traverse),\n );\n });\n}\n","import type { NormalizedTextNode } from \"@/normalizer\";\nimport { compactObject } from \"@/utils/common\";\nimport { createElement, defineElementTransformer, type ElementTransformer } from \"../../core\";\nimport type { PropsConverters } from \"./props\";\n\nexport interface TextTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createTextTransformer({\n propsConverters,\n}: TextTransformerDeps): ElementTransformer<NormalizedTextNode> {\n return defineElementTransformer((node: NormalizedTextNode) => {\n const hasMultipleFills = node.fills.length > 1;\n\n const fillProps = propsConverters.textFill(node);\n const typeStyleProps = propsConverters.typeStyle(node);\n\n const props = compactObject({\n ...typeStyleProps,\n ...fillProps,\n });\n\n return createElement(\"Text\", props, node.characters, {\n comment: hasMultipleFills\n ? \"Multiple fills in Text node encountered, only the first fill is used.\"\n : undefined,\n });\n });\n}\n"],"names":[],"mappings":";;;;;AACO;AACA;AACA;AACP;AACA;AACO;AACA;AACA;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;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACO;;ACnEA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACtBO;AACA;;ACAA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACnBA;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;;ACTO;AACP;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACjBO;AACP;AACA;AACA;AACA;;ACJO;AAGA;AACA;;ACJA;AACP;AACA;;ACDO;AACP;AACA;AACA;AACA;;ACLO;AACP;AACA;;ACFO;AACA;AACP;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACZA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;AC/CP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACtBA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACvBO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC1DO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACxDO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACvCO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACxBO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC/BO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACxCO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACxDO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC3CO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACvBO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC3DO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC/BO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC/BO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACjCO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AChBO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACtCO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACZO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACvCO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AChBO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACnEO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC9BO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACtCO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACtBO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC9BO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACdO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACtBO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACtBO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACxBO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC5BO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC5CO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACnGO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC/CO;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACA;AACA;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACA;AACA;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;;AClNO;AACP;AACA;AACA;AACO;;ACJA;;ACEA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACA;AACA;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACA;AACA;AACP;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;;ACrEA;AACP;AACA;AACO;;ACHA;AACP;AACA;AACA;AACO;;ACJA;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACO;;ACXA;AACP;AACA;AACO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -221,10 +221,10 @@ function inferLayout(parentNode) {
221
221
  }
222
222
  }
223
223
  // Heuristic: Prefer axis with more non-negative gaps and lower variance
224
- const hVariance = calculateVariance(horizontalGaps.filter((g)=>g >= -1));
225
- const vVariance = calculateVariance(verticalGaps.filter((g)=>g >= -1));
226
- const hCount = horizontalGaps.filter((g)=>g >= -1).length;
227
- const vCount = verticalGaps.filter((g)=>g >= -1).length;
224
+ const hVariance = calculateVariance(horizontalGaps.filter((g)=>g >= -EPSILON));
225
+ const vVariance = calculateVariance(verticalGaps.filter((g)=>g >= -EPSILON));
226
+ const hCount = horizontalGaps.filter((g)=>g >= -EPSILON).length;
227
+ const vCount = verticalGaps.filter((g)=>g >= -EPSILON).length;
228
228
  let primaryAxisSortedNodes = sortedByX; // Default guess
229
229
  // Basic variance check (lower is better). Add slight bias for horizontal if equal.
230
230
  if (vCount > 0 && (hCount === 0 || vVariance < hVariance - EPSILON && vCount >= hCount || vVariance <= hVariance && vCount > hCount)) {
@@ -251,7 +251,7 @@ function inferLayout(parentNode) {
251
251
  }
252
252
  }
253
253
  const primaryGaps = result.layoutMode === "HORIZONTAL" ? horizontalGaps : verticalGaps;
254
- const validGaps = primaryGaps.filter((g)=>g >= -1); // Allow slight overlap
254
+ const validGaps = primaryGaps.filter((g)=>g >= -EPSILON); // Allow slight overlap
255
255
  // --- 2. Calculate Spacing & Primary Alignment ---
256
256
  let isSpaceBetween = false;
257
257
  const collectiveBox = getCollectiveBoundingBox(children);
@@ -295,7 +295,7 @@ function inferLayout(parentNode) {
295
295
  // Use median spacing for robustness against outliers
296
296
  result.itemSpacing = calculateMedian(validGaps);
297
297
  // Clamp negative spacing if it's very small (likely float error)
298
- if (result.itemSpacing < 0 && result.itemSpacing > -1) {
298
+ if (result.itemSpacing < 0 && result.itemSpacing > -EPSILON) {
299
299
  result.itemSpacing = 0;
300
300
  }
301
301
  } else {
@@ -228,10 +228,10 @@ function inferLayout(parentNode) {
228
228
  }
229
229
  }
230
230
  // Heuristic: Prefer axis with more non-negative gaps and lower variance
231
- const hVariance = calculateVariance(horizontalGaps.filter((g)=>g >= -1));
232
- const vVariance = calculateVariance(verticalGaps.filter((g)=>g >= -1));
233
- const hCount = horizontalGaps.filter((g)=>g >= -1).length;
234
- const vCount = verticalGaps.filter((g)=>g >= -1).length;
231
+ const hVariance = calculateVariance(horizontalGaps.filter((g)=>g >= -EPSILON));
232
+ const vVariance = calculateVariance(verticalGaps.filter((g)=>g >= -EPSILON));
233
+ const hCount = horizontalGaps.filter((g)=>g >= -EPSILON).length;
234
+ const vCount = verticalGaps.filter((g)=>g >= -EPSILON).length;
235
235
  let primaryAxisSortedNodes = sortedByX; // Default guess
236
236
  // Basic variance check (lower is better). Add slight bias for horizontal if equal.
237
237
  if (vCount > 0 && (hCount === 0 || vVariance < hVariance - EPSILON && vCount >= hCount || vVariance <= hVariance && vCount > hCount)) {
@@ -258,7 +258,7 @@ function inferLayout(parentNode) {
258
258
  }
259
259
  }
260
260
  const primaryGaps = result.layoutMode === "HORIZONTAL" ? horizontalGaps : verticalGaps;
261
- const validGaps = primaryGaps.filter((g)=>g >= -1); // Allow slight overlap
261
+ const validGaps = primaryGaps.filter((g)=>g >= -EPSILON); // Allow slight overlap
262
262
  // --- 2. Calculate Spacing & Primary Alignment ---
263
263
  let isSpaceBetween = false;
264
264
  const collectiveBox = getCollectiveBoundingBox(children);
@@ -302,7 +302,7 @@ function inferLayout(parentNode) {
302
302
  // Use median spacing for robustness against outliers
303
303
  result.itemSpacing = calculateMedian(validGaps);
304
304
  // Clamp negative spacing if it's very small (likely float error)
305
- if (result.itemSpacing < 0 && result.itemSpacing > -1) {
305
+ if (result.itemSpacing < 0 && result.itemSpacing > -EPSILON) {
306
306
  result.itemSpacing = 0;
307
307
  }
308
308
  } else {
@@ -273,3 +273,4 @@ declare function createPipeline(options?: CreatePipelineConfig): CodeGenerator;
273
273
 
274
274
  export { bindComponentHandler, createBooleanOperationTransformer, createContainerLayoutPropsConverter, createFrameFillPropsConverter, createFrameTransformer, createIconSelfLayoutPropsConverter, createInstanceTransformer, createPipeline, createRadiusPropsConverter, createRectangleTransformer, createSelfLayoutPropsConverter, createShapeFillPropsConverter, createStrokePropsConverter, createTextFillPropsConverter, createTextTransformer, createTypeStylePropsConverter, createVectorChildrenFillPropsConverter, createVectorTransformer, unboundSeedComponentHandlers };
275
275
  export type { ComponentHandlerDeps, ContainerLayoutProps, ContainerLayoutTrait, CreatePipelineConfig, FillTrait, FrameFillProps, FrameTransformerDeps, IconSelfLayoutProps, InstanceTransformerDeps, PropsConverters, RadiusProps, RadiusTrait, RectangleTransformerDeps, SelfLayoutProps, SelfLayoutTrait, ShapeFillProps, StrokeProps, StrokeTrait, TextFillProps, TextTransformerDeps, TypeStyleProps, TypeStyleTrait, UnboundComponentHandler, VectorChildrenFillProps };
276
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sources":["../../../../src/normalizer/types.ts","../../../../src/codegen/core/jsx.ts","../../../../src/codegen/core/element-transformer.ts","../../../../src/codegen/core/codegen.ts","../../../../src/codegen/core/component-handler.ts","../../../../src/codegen/core/props-converter.ts","../../../../src/codegen/core/value-resolver.ts","../../../../src/codegen/targets/react/value-resolver.ts","../../../../src/codegen/targets/react/props.ts","../../../../src/codegen/targets/react/shape.ts","../../../../src/codegen/targets/react/frame.ts","../../../../src/codegen/targets/react/icon.ts","../../../../src/codegen/targets/react/instance.ts","../../../../src/codegen/targets/react/text.ts","../../../../src/codegen/targets/react/component/deps.interface.ts","../../../../src/codegen/targets/react/component/index.ts","../../../../src/codegen/targets/react/pipeline.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\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 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 { ensureArray, exists } from \"@/utils/common\";\n\nexport interface ElementNode {\n __IS_JSX_ELEMENT_NODE: true;\n tag: string;\n props: Record<string, string | number | boolean | ElementNode | object | undefined>;\n children: (ElementNode | string)[];\n\n meta: {\n comment?: string;\n source?: string;\n importPath?: string;\n };\n}\n\nexport function createElement(\n tag: string,\n props: Record<string, string | number | boolean | object | undefined> = {},\n children?: ElementNode | string | undefined | (ElementNode | string | undefined)[],\n meta?: ElementNode[\"meta\"],\n): ElementNode {\n return {\n __IS_JSX_ELEMENT_NODE: true,\n tag,\n props,\n children: ensureArray(children).filter(exists),\n meta: meta ?? {},\n };\n}\n\nexport function cloneElement(\n element: ElementNode,\n props: Record<string, string | number | boolean | object | undefined> = {},\n children?: ElementNode | string | undefined | (ElementNode | string | undefined)[],\n) {\n return {\n ...element,\n props: { ...element.props, ...props },\n children: children ? ensureArray(children).filter(exists) : element.children,\n };\n}\n\nexport function appendSource(element: ElementNode, source: string) {\n return {\n ...element,\n source,\n };\n}\n\nexport function isElement(node: unknown): node is ElementNode {\n return (\n typeof node === \"object\" &&\n node != null &&\n \"__IS_JSX_ELEMENT_NODE\" in node &&\n node.__IS_JSX_ELEMENT_NODE === true\n );\n}\n\nexport function stringifyElement(element: ElementNode, options: { printSource?: boolean } = {}) {\n const importMap = new Map<string, Set<string>>();\n\n function recursive(node: ElementNode | string, depth: number): string {\n if (typeof node === \"string\") {\n return node;\n }\n\n const {\n tag,\n props,\n children,\n meta: { comment, source, importPath },\n } = node;\n\n if (importPath) {\n const existing = importMap.get(importPath);\n if (existing) {\n existing.add(tag);\n } else {\n importMap.set(importPath, new Set([tag]));\n }\n }\n\n const propEntries = Object.entries(\n options.printSource ? { ...props, \"data-figma-node-id\": source } : props,\n );\n const propFragments = propEntries\n .map(([key, value]) => {\n if (typeof value === \"string\") {\n if (value.includes(\"\\n\")) {\n return `${key}={\\\"${value.replace(\"\\n\", \"\\\\n\")}\\\"}`;\n }\n\n return `${key}=\"${value}\"`;\n }\n\n if (typeof value === \"number\") {\n return `${key}={${value}}`;\n }\n\n if (typeof value === \"boolean\") {\n return `${key}={${value}}`;\n }\n\n if (isElement(value)) {\n return `${key}={${recursive(value, depth + 1)}}`;\n }\n\n if (typeof value === \"object\") {\n return `${key}={${JSON.stringify(value)}}`;\n }\n\n if (typeof value === \"undefined\") {\n return undefined;\n }\n })\n .filter(exists);\n\n const oneLiner = propFragments.join(\" \");\n const propsString =\n propEntries.length === 0\n ? \"\"\n : ` ${\n oneLiner.length < 80\n ? oneLiner\n : `\\n${\" \".repeat(depth + 1)}${propFragments.join(\n `\\n${\" \".repeat(depth + 1)}`,\n )}\\n${\" \".repeat(depth)}`\n }`;\n\n if (children == null || children.length === 0) {\n return `<${tag}${propsString} />${comment ? `{/* ${comment} */}` : \"\"}`;\n }\n\n const result = [\n `<${tag}${propsString}>`,\n ...ensureArray(children)\n .filter(exists)\n .map((child) => recursive(child, depth + 1))\n .map((str) => \" \".repeat(depth + 1) + str),\n `${\" \".repeat(depth)}</${tag}>${comment ? `{/* ${comment} */}` : \"\"}`,\n ].join(\"\\n\");\n\n return result;\n }\n\n const jsx = recursive(element, 0);\n\n const imports = Array.from(importMap.entries())\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([importPath, tags]) => `import { ${Array.from(tags).join(\", \")} } from \"${importPath}\";`)\n .join(\"\\n\");\n\n return {\n imports,\n jsx,\n };\n}\n","import type { NormalizedSceneNode } from \"@/normalizer\";\nimport type { ElementNode } from \"./jsx\";\n\nexport type ElementTransformer<T extends NormalizedSceneNode> = (\n node: T,\n traverse: (node: NormalizedSceneNode) => ElementNode | undefined,\n) => ElementNode | undefined;\n\nexport function defineElementTransformer<T extends NormalizedSceneNode>(\n transformer: ElementTransformer<T>,\n) {\n return transformer;\n}\n","import type {\n NormalizedBooleanOperationNode,\n NormalizedComponentNode,\n NormalizedFrameNode,\n NormalizedInstanceNode,\n NormalizedRectangleNode,\n NormalizedSceneNode,\n NormalizedTextNode,\n NormalizedVectorNode,\n} from \"@/normalizer\";\nimport { match } from \"ts-pattern\";\nimport { appendSource, createElement, stringifyElement, type ElementNode } from \"../core/jsx\";\nimport type { ElementTransformer } from \"./element-transformer\";\nimport { applyInferredLayout, inferLayout } from \"./infer-layout\";\n\nexport interface CodeGeneratorDeps {\n frameTransformer: ElementTransformer<\n NormalizedFrameNode | NormalizedComponentNode | NormalizedInstanceNode\n >;\n textTransformer: ElementTransformer<NormalizedTextNode>;\n rectangleTransformer: ElementTransformer<NormalizedRectangleNode>;\n instanceTransformer: ElementTransformer<NormalizedInstanceNode>;\n vectorTransformer: ElementTransformer<NormalizedVectorNode>;\n booleanOperationTransformer: ElementTransformer<NormalizedBooleanOperationNode>;\n shouldInferAutoLayout: boolean;\n}\n\nexport interface CodeGenerator {\n generateJsxTree: (node: NormalizedSceneNode) => ElementNode | undefined;\n generateCode: (\n node: NormalizedSceneNode,\n options: { shouldPrintSource: boolean },\n ) => { imports: string; jsx: string } | undefined;\n}\n\nexport function createCodeGenerator({\n frameTransformer,\n textTransformer,\n rectangleTransformer,\n instanceTransformer,\n vectorTransformer,\n booleanOperationTransformer,\n shouldInferAutoLayout,\n}: CodeGeneratorDeps): CodeGenerator {\n function traverse(node: NormalizedSceneNode): ElementNode | undefined {\n if (\"visible\" in node && !node.visible) {\n return;\n }\n\n const result = match(node)\n .with({ type: \"FRAME\" }, (node) =>\n shouldInferAutoLayout\n ? frameTransformer(applyInferredLayout(node, inferLayout(node)), traverse)\n : frameTransformer(node, traverse),\n )\n .with({ type: \"TEXT\" }, (node) => textTransformer(node, traverse))\n .with({ type: \"RECTANGLE\" }, (node) => rectangleTransformer(node, traverse))\n .with({ type: \"COMPONENT\" }, (node) => frameTransformer(node, traverse)) // NOTE: Treat component node as Frame for now\n .with({ type: \"INSTANCE\" }, (node) => instanceTransformer(node, traverse))\n .with({ type: \"VECTOR\" }, (node) => vectorTransformer(node, traverse))\n .with({ type: \"BOOLEAN_OPERATION\" }, (node) => booleanOperationTransformer(node, traverse))\n .with({ type: \"UNHANDLED\" }, () => createElement(\"UnhandledFigmaNode\"))\n .exhaustive();\n\n if (result) {\n return appendSource(result, node.id);\n }\n\n return;\n }\n\n function generateJsxTree(node: NormalizedSceneNode) {\n return traverse(node);\n }\n\n function generateCode(node: NormalizedSceneNode, options: { shouldPrintSource: boolean }) {\n const jsxTree = generateJsxTree(node);\n\n if (!jsxTree) {\n return undefined;\n }\n\n return stringifyElement(jsxTree, { printSource: options.shouldPrintSource });\n }\n\n return { generateJsxTree, generateCode };\n}\n","import type { NormalizedInstanceNode } from \"@/normalizer\";\nimport type { ElementNode } from \"./jsx\";\n\nexport interface ComponentHandler<\n T extends\n NormalizedInstanceNode[\"componentProperties\"] = NormalizedInstanceNode[\"componentProperties\"],\n> {\n key: string;\n transform: (node: NormalizedInstanceNode & { componentProperties: T }) => ElementNode;\n}\n\nexport function defineComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]>(\n key: string,\n transform: (node: NormalizedInstanceNode & { componentProperties: T }) => ElementNode,\n): ComponentHandler<T> {\n return { key, transform };\n}\n","import type { VariableValueResolved } from \"@/entities\";\nimport { objectEntries } from \"@/utils/common\";\n\nexport type PropsConverter<\n T extends Record<string, any> = Record<string, any>,\n R extends Record<string, any> = Record<string, any>,\n> = (node: T) => R;\n\nexport function definePropsConverter<T extends Record<string, any>, R extends Record<string, any>>(\n converter: PropsConverter<T, R>,\n) {\n return converter;\n}\n\ntype Handlers<\n TTrait extends Record<string, VariableValueResolved>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps = keyof TProps,\n> = {\n [K in HandlerKeys]: (node: TTrait) => TProps[K];\n};\n\ntype Shorthands<TProps extends Record<string, any>, HandlerKeys extends keyof TProps> = Record<\n Exclude<keyof TProps, HandlerKeys>,\n HandlerKeys[]\n>;\n\nexport interface CreatePropsConverterConfig<\n TTrait extends Record<string, any>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps,\n> {\n _types: {\n trait: TTrait;\n props: TProps;\n };\n handlers: Handlers<TTrait, TProps, HandlerKeys>;\n shorthands?: Shorthands<TProps, HandlerKeys>;\n defaults?: Partial<TProps>;\n}\n\nexport function createPropsConverter<\n TTrait extends Record<string, any>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps,\n>({\n handlers,\n shorthands,\n defaults,\n}: CreatePropsConverterConfig<TTrait, TProps, HandlerKeys>): PropsConverter<TTrait, TProps> {\n return definePropsConverter((node: TTrait) => {\n const result = {} as TProps;\n\n for (const [prop, handler] of objectEntries(handlers)) {\n const value = handler(node);\n if (value !== undefined && (!defaults || value !== defaults[prop as keyof TProps])) {\n result[prop as keyof TProps] = value as any;\n }\n }\n\n if (shorthands) {\n for (const [shorthand, props] of objectEntries(shorthands)) {\n const values = props.map((prop) => result[prop as keyof TProps]);\n const allDefined = values.every((value) => value !== undefined);\n const allEqual = allDefined && values.every((value) => value === values[0]);\n\n if (allEqual && values[0] !== undefined) {\n result[shorthand as keyof TProps] = values[0] as any;\n for (const prop of props) {\n delete result[prop as keyof TProps];\n }\n }\n }\n }\n\n return result;\n });\n}\n","import type { StyleService, VariableValueResolved } from \"@/entities\";\nimport type {\n NormalizedCornerTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasLayoutTrait,\n NormalizedIsLayerTrait,\n NormalizedTypePropertiesTrait,\n} from \"@/normalizer\";\nimport {\n getFirstFillVariable,\n getFirstSolidFill,\n getFirstStroke,\n getFirstStrokeVariable,\n} from \"@/utils/figma-node\";\nimport type { RGBA } from \"@figma/rest-api-spec\";\nimport type { VariableService } from \"../../entities/variable.service\";\n\nexport interface ValueResolver<TColor, TDimension, TFontDimension, TFontWeight> {\n getFormattedValue: {\n frameFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n shapeFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n textFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n stroke: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n width: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n height: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n minWidth: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n minHeight: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n maxWidth: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n maxHeight: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingLeft: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingRight: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingTop: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingBottom: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n itemSpacing: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n topLeftRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n topRightRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n bottomLeftRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n bottomRightRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n fontSize: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontDimension | undefined;\n fontWeight: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontWeight | undefined;\n lineHeight: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontDimension | undefined;\n };\n getTextStyleValue: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | undefined; // TODO: we might turn this into a generic; not sure yet\n}\n\nexport interface ValueResolverDeps<TColor, TDimension, TFontDimension, TFontWeight> {\n variableService: VariableService;\n variableNameFormatter: (props: { slug: string[] }) => string;\n styleService: StyleService;\n styleNameFormatter: (props: { slug: string[] }) => string;\n rawValueFormatters: {\n color: (value: RGBA) => string | TColor;\n dimension: (value: number) => string | TDimension;\n fontDimension: (value: number) => string | TFontDimension;\n fontWeight: (value: number) => string | TFontWeight;\n };\n shouldInferVariableName: boolean;\n}\n\nexport function createValueResolver<TColor, TDimension, TFontDimension, TFontWeight>({\n variableService,\n variableNameFormatter,\n styleService,\n styleNameFormatter,\n rawValueFormatters,\n shouldInferVariableName,\n}: ValueResolverDeps<TColor, TDimension, TFontDimension, TFontWeight>): ValueResolver<\n TColor,\n TDimension,\n TFontDimension,\n TFontWeight\n> {\n function getVariableName(key: string) {\n const slug = variableService.getSlug(key);\n\n if (!slug) {\n return undefined;\n }\n\n return variableNameFormatter({ slug });\n }\n\n function inferVariableName(value: VariableValueResolved, scope: VariableScope) {\n if (!shouldInferVariableName) {\n return undefined;\n }\n\n const inferred = variableService.infer(value, scope);\n\n if (!inferred) {\n return undefined;\n }\n\n return getVariableName(inferred.key);\n }\n\n function getStyleName(key: string) {\n const slug = styleService.getSlug(key);\n\n if (!slug) {\n return undefined;\n }\n\n return styleNameFormatter({ slug });\n }\n\n function processColor(\n key: string | undefined,\n value: RGBA | undefined,\n scope: \"FRAME_FILL\" | \"SHAPE_FILL\" | \"STROKE_COLOR\" | \"TEXT_FILL\",\n ) {\n if (key) {\n return getVariableName(key);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.color(value);\n }\n\n return undefined;\n }\n\n function processDimension(\n key: string | undefined,\n value: number | undefined,\n scope: \"WIDTH_HEIGHT\" | \"GAP\" | \"CORNER_RADIUS\",\n ) {\n if (key) {\n return getVariableName(key);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.dimension(value);\n }\n\n return undefined;\n }\n\n function processFontDimension(\n key: string | undefined,\n value: number | undefined,\n scope: \"FONT_SIZE\" | \"LINE_HEIGHT\",\n ) {\n if (key) {\n return getVariableName(key);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.fontDimension(value);\n }\n\n return undefined;\n }\n\n function processFontWeight(key: string | undefined, value: number | undefined) {\n if (key) {\n return getVariableName(key);\n }\n\n if (value !== undefined) {\n const fontWeightToString: Record<number, string> = {\n 100: \"thin\",\n 200: \"extra-light\",\n 300: \"light\",\n 400: \"regular\",\n 500: \"medium\",\n 600: \"semi-bold\",\n 700: \"bold\",\n 800: \"extra-bold\",\n 900: \"black\",\n };\n\n return (\n inferVariableName(value, \"FONT_WEIGHT\") ??\n inferVariableName(fontWeightToString[value], \"FONT_STYLE\") ??\n rawValueFormatters.fontWeight(value)\n );\n }\n\n return undefined;\n }\n\n const getFormattedValue: ValueResolver<\n TColor,\n TDimension,\n TFontDimension,\n TFontWeight\n >[\"getFormattedValue\"] = {\n width: (node) =>\n processDimension(\n node.boundVariables?.size?.x?.id,\n node.absoluteBoundingBox?.width,\n \"WIDTH_HEIGHT\",\n ),\n height: (node) =>\n processDimension(\n node.boundVariables?.size?.y?.id,\n node.absoluteBoundingBox?.height,\n \"WIDTH_HEIGHT\",\n ),\n minWidth: (node) =>\n processDimension(node.boundVariables?.minWidth?.id, node.minWidth, \"WIDTH_HEIGHT\"),\n minHeight: (node) =>\n processDimension(node.boundVariables?.minHeight?.id, node.minHeight, \"WIDTH_HEIGHT\"),\n maxWidth: (node) =>\n processDimension(node.boundVariables?.maxWidth?.id, node.maxWidth, \"WIDTH_HEIGHT\"),\n maxHeight: (node) =>\n processDimension(node.boundVariables?.maxHeight?.id, node.maxHeight, \"WIDTH_HEIGHT\"),\n paddingLeft: (node) =>\n processDimension(node.boundVariables?.paddingLeft?.id, node.paddingLeft, \"GAP\"),\n paddingRight: (node) =>\n processDimension(node.boundVariables?.paddingRight?.id, node.paddingRight, \"GAP\"),\n paddingTop: (node) =>\n processDimension(node.boundVariables?.paddingTop?.id, node.paddingTop, \"GAP\"),\n paddingBottom: (node) =>\n processDimension(node.boundVariables?.paddingBottom?.id, node.paddingBottom, \"GAP\"),\n itemSpacing: (node) =>\n processDimension(node.boundVariables?.itemSpacing?.id, node.itemSpacing, \"GAP\"),\n frameFill: (node) =>\n processColor(getFirstFillVariable(node)?.id, getFirstSolidFill(node)?.color, \"FRAME_FILL\"),\n shapeFill: (node) =>\n processColor(getFirstFillVariable(node)?.id, getFirstSolidFill(node)?.color, \"SHAPE_FILL\"),\n textFill: (node) =>\n processColor(getFirstFillVariable(node)?.id, getFirstSolidFill(node)?.color, \"TEXT_FILL\"),\n stroke: (node) =>\n processColor(getFirstStrokeVariable(node)?.id, getFirstStroke(node)?.color, \"STROKE_COLOR\"),\n topLeftRadius: (node) =>\n processDimension(\n node.boundVariables?.topLeftRadius?.id,\n node.rectangleCornerRadii?.[0] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n topRightRadius: (node) =>\n processDimension(\n node.boundVariables?.topRightRadius?.id,\n node.rectangleCornerRadii?.[1] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n bottomLeftRadius: (node) =>\n processDimension(\n node.boundVariables?.bottomLeftRadius?.id,\n node.rectangleCornerRadii?.[2] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n bottomRightRadius: (node) =>\n processDimension(\n node.boundVariables?.bottomRightRadius?.id,\n node.rectangleCornerRadii?.[3] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n fontSize: (node) =>\n processFontDimension(\n node.boundVariables?.fontSize?.[0]?.id,\n node.style.fontSize,\n \"FONT_SIZE\",\n ),\n fontWeight: (node) =>\n processFontWeight(node.boundVariables?.fontWeight?.[0]?.id, node.style.fontWeight),\n lineHeight: (node) =>\n processFontDimension(\n node.boundVariables?.lineHeight?.[0]?.id,\n node.style.lineHeightPx,\n \"LINE_HEIGHT\",\n ),\n };\n\n function getTextStyleValue(node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait) {\n if (node.textStyleKey) {\n return getStyleName(node.textStyleKey);\n }\n\n return undefined;\n }\n\n return {\n getFormattedValue,\n getTextStyleValue,\n };\n}\n","import type { ValueResolver } from \"@/codegen/core\";\nimport { camelCasePreserveUnderscoreBetweenNumbers } from \"@/utils/common\";\nimport { toCssPixel, toCssRgba } from \"@/utils/css\";\nimport { camelCase } from \"change-case\";\n\nexport type ReactValueResolver = ValueResolver<string, string, string, number>;\n\nexport const defaultVariableNameFormatter = ({ slug }: { slug: string[] }) =>\n slug\n .filter(\n (s) =>\n !(\n s === \"dimension\" ||\n s === \"radius\" ||\n s === \"font-size\" ||\n s === \"font-weight\" ||\n s === \"line-height\"\n ),\n )\n .map((s) => s.replaceAll(\",\", \"_\"))\n .map(camelCasePreserveUnderscoreBetweenNumbers)\n .join(\".\");\n\nexport const defaultStyleNameFormatter = ({ slug }: { slug: string[] }) =>\n camelCase(slug[slug.length - 1]!, { mergeAmbiguousCharacters: true });\n\nexport const defaultRawValueFormatters = {\n color: (value: RGBA) => toCssRgba(value),\n dimension: (value: number) => toCssPixel(value),\n fontDimension: (value: number) => toCssPixel(value),\n fontWeight: (value: number) => value,\n};\n","import { createPropsConverter, definePropsConverter, type PropsConverter } from \"@/codegen/core\";\nimport type {\n NormalizedCornerTrait,\n NormalizedHasChildrenTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasLayoutTrait,\n NormalizedIsLayerTrait,\n NormalizedTypePropertiesTrait,\n} from \"@/normalizer\";\nimport { match } from \"ts-pattern\";\nimport type { ReactValueResolver } from \"./value-resolver\";\n\nexport interface PropsConverters {\n containerLayout: PropsConverter<ContainerLayoutTrait, ContainerLayoutProps>;\n selfLayout: PropsConverter<SelfLayoutTrait, SelfLayoutProps>;\n iconSelfLayout: PropsConverter<SelfLayoutTrait, IconSelfLayoutProps>;\n radius: PropsConverter<RadiusTrait, RadiusProps>;\n frameFill: PropsConverter<FillTrait, FrameFillProps>;\n shapeFill: PropsConverter<FillTrait, ShapeFillProps>;\n textFill: PropsConverter<FillTrait, TextFillProps>;\n vectorChildrenFill: PropsConverter<ContainerLayoutTrait, VectorChildrenFillProps>;\n stroke: PropsConverter<StrokeTrait, StrokeProps>;\n typeStyle: PropsConverter<TypeStyleTrait, TypeStyleProps>;\n}\n\nexport type ContainerLayoutTrait = NormalizedHasFramePropertiesTrait &\n NormalizedHasChildrenTrait &\n NormalizedHasLayoutTrait &\n NormalizedIsLayerTrait;\n\nexport type SelfLayoutTrait = NormalizedIsLayerTrait & NormalizedHasLayoutTrait;\n\nexport type RadiusTrait = NormalizedCornerTrait & NormalizedIsLayerTrait;\n\nexport type FillTrait = NormalizedIsLayerTrait & NormalizedHasGeometryTrait;\n\nexport type StrokeTrait = NormalizedIsLayerTrait & NormalizedHasGeometryTrait;\n\nexport type TypeStyleTrait = NormalizedTypePropertiesTrait & NormalizedIsLayerTrait;\n\nexport interface ContainerLayoutProps {\n direction?: \"row\" | \"column\";\n justify?: \"flex-start\" | \"center\" | \"flex-end\" | \"space-between\";\n align?: \"stretch\" | \"flex-start\" | \"center\" | \"flex-end\" | \"baseline\";\n wrap?: \"wrap\" | \"nowrap\" | true;\n gap?: string | 0;\n pb?: string | 0;\n pl?: string | 0;\n pr?: string | 0;\n pt?: string | 0;\n px?: string | 0;\n py?: string | 0;\n p?: string | 0;\n}\n\nexport function createContainerLayoutPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<ContainerLayoutTrait, ContainerLayoutProps> {\n return createPropsConverter({\n _types: {\n trait: {} as ContainerLayoutTrait,\n props: {} as ContainerLayoutProps,\n },\n handlers: {\n direction: ({ layoutMode }) =>\n match(layoutMode)\n .with(\"HORIZONTAL\", () => \"row\" as const)\n .with(\"VERTICAL\", () => \"column\" as const)\n .with(\"NONE\", () => undefined)\n .with(undefined, () => undefined)\n .exhaustive(),\n justify: ({ primaryAxisAlignItems }) =>\n match(primaryAxisAlignItems)\n .with(\"MIN\", () => \"flex-start\" as const)\n .with(\"CENTER\", () => \"center\" as const)\n .with(\"MAX\", () => \"flex-end\" as const)\n .with(\"SPACE_BETWEEN\", () => \"space-between\" as const)\n .with(undefined, () => undefined)\n .exhaustive(),\n align: ({ counterAxisAlignItems, children }) => {\n const isStretch = children.every((child) => {\n if (!(\"layoutAlign\" in child)) {\n return false;\n }\n\n return child.layoutAlign === \"STRETCH\";\n });\n\n if (isStretch) {\n return \"stretch\";\n }\n\n return match(counterAxisAlignItems)\n .with(\"MIN\", () => \"flex-start\" as const)\n .with(\"CENTER\", () => \"center\" as const)\n .with(\"MAX\", () => \"flex-end\" as const)\n .with(\"BASELINE\", () => \"baseline\" as const)\n .with(undefined, () => undefined)\n .exhaustive();\n },\n wrap: ({ layoutWrap }) =>\n match(layoutWrap)\n .with(\"WRAP\", () => true as const)\n .with(\"NO_WRAP\", () => \"nowrap\" as const)\n .with(undefined, () => undefined)\n .exhaustive(),\n gap: (node) => {\n if (node.children.length <= 1) {\n return undefined;\n }\n\n if (node.primaryAxisAlignItems === \"SPACE_BETWEEN\") {\n return undefined;\n }\n\n return valueResolver.getFormattedValue.itemSpacing(node);\n },\n pt: (node) => valueResolver.getFormattedValue.paddingTop(node),\n pb: (node) => valueResolver.getFormattedValue.paddingBottom(node),\n pl: (node) => valueResolver.getFormattedValue.paddingLeft(node),\n pr: (node) => valueResolver.getFormattedValue.paddingRight(node),\n },\n shorthands: {\n p: [\"pt\", \"pb\", \"pl\", \"pr\"],\n px: [\"pl\", \"pr\"],\n py: [\"pt\", \"pb\"],\n },\n defaults: {\n justify: \"flex-start\",\n align: \"stretch\",\n wrap: \"nowrap\",\n gap: \"0px\",\n p: \"0px\",\n px: \"0px\",\n py: \"0px\",\n pb: \"0px\",\n pl: \"0px\",\n pr: \"0px\",\n pt: \"0px\",\n },\n });\n}\n\nexport interface SelfLayoutProps {\n grow?: 0 | 1 | true;\n alignSelf?: \"stretch\";\n width?: string | number;\n height?: string | number;\n minWidth?: string | number;\n minHeight?: string | number;\n maxWidth?: string | number;\n maxHeight?: string | number;\n}\n\nexport function createSelfLayoutPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<SelfLayoutTrait, SelfLayoutProps> {\n return createPropsConverter({\n _types: {\n trait: {} as SelfLayoutTrait,\n props: {} as SelfLayoutProps,\n },\n handlers: {\n grow: ({ layoutGrow }) => (layoutGrow === 1 ? true : layoutGrow),\n alignSelf: ({ layoutAlign }) =>\n match(layoutAlign)\n .with(\"STRETCH\", () => \"stretch\" as const)\n .with(\"INHERIT\", () => undefined)\n .with(\"MIN\", () => undefined) // Deprecated in Figma\n .with(\"CENTER\", () => undefined) // Deprecated in Figma\n .with(\"MAX\", () => undefined) // Deprecated in Figma\n .with(undefined, () => undefined)\n .exhaustive(),\n height: (node) =>\n node.layoutSizingVertical === \"FIXED\"\n ? valueResolver.getFormattedValue.height(node)\n : undefined,\n width: (node) =>\n node.layoutSizingHorizontal === \"FIXED\"\n ? valueResolver.getFormattedValue.width(node)\n : undefined,\n minHeight: (node) =>\n node.layoutSizingVertical === \"HUG\"\n ? valueResolver.getFormattedValue.minHeight(node)\n : undefined,\n maxHeight: (node) =>\n node.layoutSizingVertical === \"HUG\"\n ? valueResolver.getFormattedValue.maxHeight(node)\n : undefined,\n minWidth: (node) =>\n node.layoutSizingHorizontal === \"HUG\"\n ? valueResolver.getFormattedValue.minWidth(node)\n : undefined,\n maxWidth: (node) =>\n node.layoutSizingHorizontal === \"HUG\"\n ? valueResolver.getFormattedValue.maxWidth(node)\n : undefined,\n },\n defaults: {\n grow: 0,\n },\n });\n}\n\nexport interface IconSelfLayoutProps {\n size?: string | number;\n}\n\nexport function createIconSelfLayoutPropsConverter(valueResolver: ReactValueResolver) {\n return createPropsConverter({\n _types: {\n trait: {} as SelfLayoutTrait,\n props: {} as IconSelfLayoutProps,\n },\n handlers: {\n size: (node) => valueResolver.getFormattedValue.width(node),\n },\n });\n}\n\nexport interface RadiusProps {\n borderRadius?: string | 0;\n borderTopLeftRadius?: string | 0;\n borderTopRightRadius?: string | 0;\n borderBottomLeftRadius?: string | 0;\n borderBottomRightRadius?: string | 0;\n}\n\nexport function createRadiusPropsConverter(valueResolver: ReactValueResolver) {\n return createPropsConverter({\n _types: {\n trait: {} as RadiusTrait,\n props: {} as RadiusProps,\n },\n handlers: {\n borderTopLeftRadius: (node) => valueResolver.getFormattedValue.topLeftRadius(node),\n borderTopRightRadius: (node) => valueResolver.getFormattedValue.topRightRadius(node),\n borderBottomLeftRadius: (node) => valueResolver.getFormattedValue.bottomLeftRadius(node),\n borderBottomRightRadius: (node) => valueResolver.getFormattedValue.bottomRightRadius(node),\n },\n shorthands: {\n borderRadius: [\n \"borderTopLeftRadius\",\n \"borderTopRightRadius\",\n \"borderBottomLeftRadius\",\n \"borderBottomRightRadius\",\n ],\n },\n defaults: {\n borderRadius: \"0px\",\n borderTopLeftRadius: \"0px\",\n borderTopRightRadius: \"0px\",\n borderBottomLeftRadius: \"0px\",\n borderBottomRightRadius: \"0px\",\n },\n });\n}\n\nexport interface TypeStyleProps {\n textStyle?: string;\n fontSize?: string;\n fontWeight?: string | number;\n lineHeight?: string;\n maxLines?: number;\n}\n\nexport function createTypeStylePropsConverter({\n valueResolver,\n}: {\n valueResolver: ReactValueResolver;\n}): PropsConverter<TypeStyleTrait, TypeStyleProps> {\n return definePropsConverter((node) => {\n const styleName = valueResolver.getTextStyleValue(node);\n const maxLines =\n node.style.textTruncation === \"ENDING\" ? (node.style.maxLines ?? undefined) : undefined;\n\n if (styleName) {\n return {\n textStyle: styleName,\n maxLines,\n };\n }\n\n return {\n fontSize: valueResolver.getFormattedValue.fontSize(node),\n fontWeight: valueResolver.getFormattedValue.fontWeight(node),\n lineHeight: valueResolver.getFormattedValue.lineHeight(node),\n maxLines,\n };\n });\n}\n\nexport interface FrameFillProps {\n bg?: string;\n}\n\nexport function createFrameFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, FrameFillProps>((node) => {\n const bg = valueResolver.getFormattedValue.frameFill(node);\n\n return {\n bg,\n };\n });\n}\n\nexport interface ShapeFillProps {\n color?: string;\n}\n\nexport function createShapeFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, ShapeFillProps>((node) => {\n const color = valueResolver.getFormattedValue.shapeFill(node);\n\n return {\n color,\n };\n });\n}\n\nexport interface TextFillProps {\n color?: string;\n}\n\nexport function createTextFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, TextFillProps>((node) => {\n const color = valueResolver.getFormattedValue.textFill(node);\n\n return {\n color,\n };\n });\n}\n\nexport interface VectorChildrenFillProps {\n color?: string;\n}\n\nexport function createVectorChildrenFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<ContainerLayoutTrait, VectorChildrenFillProps>((node) => {\n if (node.children.length === 0) {\n console.warn(\n `createVectorChildrenFillPropsConverter: Node has no children. Name:${node.name}, ID:${node.id}`,\n );\n return {};\n }\n\n const vectors = node.children.filter(\n (child) => child.type === \"VECTOR\" || child.type === \"BOOLEAN_OPERATION\",\n );\n\n const colors = vectors.map((vector) => valueResolver.getFormattedValue.shapeFill(vector));\n\n const fills = new Set(colors.filter((color) => color !== undefined));\n\n // If there are more than 1 color, colors are likely pre-defined in the icon component; we should ignore the color prop.\n if (fills.size > 1) {\n return {};\n }\n\n return { color: fills.values().next().value };\n });\n}\n\nexport interface StrokeProps {\n borderWidth?: number;\n borderColor?: string;\n}\n\nexport function createStrokePropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<StrokeTrait, StrokeProps> {\n return definePropsConverter((node) => {\n const borderColor = valueResolver.getFormattedValue.stroke(node);\n const borderWidth = borderColor ? node.strokeWeight : undefined;\n\n return {\n borderColor,\n borderWidth,\n };\n });\n}\n","import type {\n NormalizedBooleanOperationNode,\n NormalizedRectangleNode,\n NormalizedVectorNode,\n} from \"@/normalizer\";\nimport { createElement, defineElementTransformer, type ElementTransformer } from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { PropsConverters } from \"./props\";\n\nexport interface RectangleTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createRectangleTransformer({\n propsConverters,\n}: RectangleTransformerDeps): ElementTransformer<NormalizedRectangleNode> {\n return defineElementTransformer((node: NormalizedRectangleNode) => {\n return createSeedReactElement(\n \"Box\",\n { ...propsConverters.selfLayout(node), background: \"palette.gray200\" },\n undefined,\n {\n comment: \"Rectangle Node Placeholder\",\n },\n );\n });\n}\n\nexport function createVectorTransformer(): ElementTransformer<NormalizedVectorNode> {\n return defineElementTransformer(() => {\n return createElement(\"svg\", {}, [], {\n comment: \"Vector Node Placeholder\",\n });\n });\n}\n\nexport function createBooleanOperationTransformer(): ElementTransformer<NormalizedBooleanOperationNode> {\n return defineElementTransformer(() => {\n return createElement(\"svg\", {}, [], {\n comment: \"Boolean Operation Node Placeholder\",\n });\n });\n}\n","import type {\n NormalizedComponentNode,\n NormalizedFrameNode,\n NormalizedInstanceNode,\n} from \"@/normalizer\";\nimport { cloneElement, defineElementTransformer, type ElementTransformer } from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { ContainerLayoutProps, PropsConverters } from \"./props\";\n\nexport interface FrameTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createFrameTransformer({\n propsConverters,\n}: FrameTransformerDeps): ElementTransformer<\n NormalizedFrameNode | NormalizedInstanceNode | NormalizedComponentNode\n> {\n function inferLayoutComponent(props: ContainerLayoutProps, isFlex: boolean) {\n if (!isFlex) {\n return \"Box\";\n }\n\n if (props.direction === \"column\") {\n return \"VStack\";\n }\n\n return \"HStack\";\n }\n\n return defineElementTransformer(\n (node: NormalizedFrameNode | NormalizedInstanceNode | NormalizedComponentNode, traverse) => {\n const children = node.children;\n const transformedChildren = children.map(traverse);\n const isFlex = node.layoutMode === \"HORIZONTAL\" || node.layoutMode === \"VERTICAL\";\n\n const props = {\n ...propsConverters.radius(node),\n ...(isFlex ? propsConverters.containerLayout(node) : {}),\n ...propsConverters.selfLayout(node),\n ...propsConverters.frameFill(node),\n ...propsConverters.stroke(node),\n };\n\n const isStretch = props.align === undefined || props.align === \"stretch\";\n const processedChildren = isStretch\n ? transformedChildren.map((child) =>\n child ? cloneElement(child, { alignSelf: undefined }) : child,\n )\n : transformedChildren;\n\n const layoutComponent = inferLayoutComponent(props, isFlex);\n\n if (layoutComponent === \"VStack\") {\n const { direction, ...rest } = props;\n\n return createSeedReactElement(\"VStack\", rest, processedChildren);\n }\n\n if (layoutComponent === \"HStack\") {\n const { direction, ...rest } = props;\n\n return createSeedReactElement(\"HStack\", rest, processedChildren);\n }\n\n if (layoutComponent === \"Box\") {\n return createSeedReactElement(\"Box\", props, processedChildren);\n }\n },\n );\n}\n","import type { IconService } from \"@/entities\";\nimport { pascalCase } from \"change-case\";\nimport { type ElementNode, createElement } from \"../../core\";\nimport { createMonochromeIconElement, createMulticolorIconElement } from \"./element-factories\";\n\nexport interface IconHandler {\n isIconInstance: (node: { componentKey: string }) => boolean;\n transform: (node: { componentKey: string }) => ElementNode;\n}\n\nexport interface IconHandlerDeps {\n iconService: IconService;\n iconNameFormatter?: (props: { name: string; weight?: string }) => string;\n}\n\nconst defaultIconNameFormatter = ({ name, weight }: { name: string; weight?: string }) =>\n pascalCase(`${name}${weight ? weight : \"\"}`);\n\nexport function createIconHandler({\n iconService,\n iconNameFormatter = defaultIconNameFormatter,\n}: IconHandlerDeps): IconHandler {\n function isIconInstance(node: { componentKey: string }): boolean {\n const key = node.componentKey;\n\n if (!key) {\n return false;\n }\n\n return iconService.isAvailable(key);\n }\n\n function transform(node: { componentKey: string }): ElementNode {\n const key = node.componentKey;\n const iconData = iconService.getOne(key);\n if (!iconData) {\n return createElement(\"UnknownIcon\");\n }\n\n const { name, weight, type } = iconData;\n\n const tagName = iconNameFormatter({ name, weight });\n\n if (type === \"multicolor\") {\n return createMulticolorIconElement(tagName);\n }\n\n return createMonochromeIconElement(tagName);\n }\n\n return {\n isIconInstance,\n transform,\n };\n}\n","import type { NormalizedInstanceNode } from \"@/normalizer\";\nimport {\n defineElementTransformer,\n type ComponentHandler,\n type ElementTransformer,\n} from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { IconHandler } from \"./icon\";\nimport type { PropsConverters } from \"./props\";\n\nexport interface InstanceTransformerDeps {\n iconHandler?: IconHandler;\n propsConverters: PropsConverters;\n componentHandlers: Record<string, ComponentHandler>;\n frameTransformer: ElementTransformer<NormalizedInstanceNode>;\n}\n\nexport function createInstanceTransformer({\n iconHandler,\n propsConverters,\n componentHandlers,\n frameTransformer,\n}: InstanceTransformerDeps): ElementTransformer<NormalizedInstanceNode> {\n const transform = defineElementTransformer((node: NormalizedInstanceNode, traverse) => {\n const { componentKey, componentSetKey } = node;\n\n if (iconHandler?.isIconInstance(node)) {\n const props = {\n ...propsConverters.iconSelfLayout(node),\n ...propsConverters.vectorChildrenFill(node),\n };\n return createSeedReactElement(\"Icon\", { svg: iconHandler.transform(node), ...props });\n }\n\n const componentHandler = componentSetKey\n ? componentHandlers[componentSetKey]\n : componentHandlers[componentKey];\n\n if (componentHandler) {\n return componentHandler.transform(node);\n }\n\n return frameTransformer(node, traverse);\n });\n\n return transform;\n}\n","import type { NormalizedTextNode } from \"@/normalizer\";\nimport { compactObject } from \"@/utils/common\";\nimport { defineElementTransformer, type ElementTransformer } from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { PropsConverters } from \"./props\";\n\nexport interface TextTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createTextTransformer({\n propsConverters,\n}: TextTransformerDeps): ElementTransformer<NormalizedTextNode> {\n return defineElementTransformer((node: NormalizedTextNode) => {\n const hasMultipleFills = node.fills.length > 1;\n\n const fillProps = propsConverters.textFill(node);\n const typeStyleProps = propsConverters.typeStyle(node);\n\n const props = compactObject({\n ...typeStyleProps,\n ...fillProps,\n });\n\n return createSeedReactElement(\"Text\", props, node.characters.replace(/\\n/g, \"<br />\"), {\n comment: hasMultipleFills\n ? \"Multiple fills in Text node encountered, only the first fill is used.\"\n : undefined,\n });\n });\n}\n","import type { IconHandler } from \"../icon\";\nimport type { ReactValueResolver } from \"../value-resolver\";\n\nexport interface ComponentHandlerDeps {\n iconHandler: IconHandler;\n valueResolver: ReactValueResolver;\n}\n","import type { ComponentHandler } from \"@/codegen/core\";\nimport type { NormalizedInstanceNode } from \"@/normalizer\";\nimport type { ComponentHandlerDeps } from \"./deps.interface\";\nimport { createActionButtonHandler } from \"./handlers/action-button\";\nimport { createActionChipHandler } from \"./handlers/action-chip\";\nimport { createActionSheetHandler } from \"./handlers/action-sheet\";\nimport { createAppBarHandler } from \"./handlers/app-bar\";\nimport { createAvatarHandler } from \"./handlers/avatar\";\nimport { createAvatarStackHandler } from \"./handlers/avatar-stack\";\nimport { createBadgeHandler } from \"./handlers/badge\";\nimport { createCalloutHandler } from \"./handlers/callout\";\nimport { createCheckboxHandler } from \"./handlers/checkbox\";\nimport { createChipTabsHandler } from \"./handlers/chip-tabs\";\nimport { createControlChipHandler } from \"./handlers/control-chip\";\nimport { createErrorStateHandler } from \"./handlers/error-state\";\nimport { createExtendedActionSheetHandler } from \"./handlers/extended-action-sheet\";\nimport { createExtendedFabHandler } from \"./handlers/extended-fab\";\nimport { createFabHandler } from \"./handlers/fab\";\nimport { createHelpBubbleHandler } from \"./handlers/help-bubble\";\nimport { createIdentityPlaceholderHandler } from \"./handlers/identity-placeholder\";\nimport { createInlineBannerHandler } from \"./handlers/inline-banner\";\nimport { createMannerTempBadgeHandler } from \"./handlers/manner-temp-badge\";\nimport { createMultilineTextFieldHandler } from \"./handlers/multiline-text-field\";\nimport { createProgressCircleHandler } from \"./handlers/progress-circle\";\nimport { createReactionButtonHandler } from \"./handlers/reaction-button\";\nimport { createSegmentedControlHandler } from \"./handlers/segmented-control\";\nimport { createSelectBoxGroupHandler, createSelectBoxHandler } from \"./handlers/select-box\";\nimport { createSkeletonHandler } from \"./handlers/skeleton\";\nimport { createSnackbarHandler } from \"./handlers/snackbar\";\nimport { createSwitchHandler } from \"./handlers/switch\";\nimport { createTabsHandler } from \"./handlers/tabs\";\nimport { createTextButtonHandler } from \"./handlers/text-button\";\nimport { createTextFieldHandler } from \"./handlers/text-field\";\nimport { createToggleButtonHandler } from \"./handlers/toggle-button\";\n\nexport type { ComponentHandlerDeps };\nexport type UnboundComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]> = (\n deps: ComponentHandlerDeps,\n) => ComponentHandler<T>;\n\nexport function bindComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]>(\n unbound: UnboundComponentHandler<T>,\n deps: ComponentHandlerDeps,\n): ComponentHandler<T> {\n return unbound(deps);\n}\n\nexport const unboundSeedComponentHandlers: Array<UnboundComponentHandler<any>> = [\n createActionButtonHandler,\n createActionChipHandler,\n createActionSheetHandler,\n createAppBarHandler,\n createAvatarHandler,\n createAvatarStackHandler,\n createBadgeHandler,\n createCalloutHandler,\n createCheckboxHandler,\n createChipTabsHandler,\n createControlChipHandler,\n createErrorStateHandler,\n createExtendedActionSheetHandler,\n createExtendedFabHandler,\n createFabHandler,\n createHelpBubbleHandler,\n createIdentityPlaceholderHandler,\n createInlineBannerHandler,\n createMannerTempBadgeHandler,\n createMultilineTextFieldHandler,\n createProgressCircleHandler,\n createReactionButtonHandler,\n createSegmentedControlHandler,\n createSelectBoxGroupHandler,\n createSelectBoxHandler,\n createSkeletonHandler,\n createSnackbarHandler,\n createSwitchHandler,\n createTabsHandler,\n createTextButtonHandler,\n createTextFieldHandler,\n createToggleButtonHandler,\n];\n","import { createCodeGenerator, createValueResolver } from \"@/codegen/core\";\nimport { iconService, styleService, variableService } from \"@/codegen/default-services\";\nimport {\n type UnboundComponentHandler,\n bindComponentHandler,\n unboundSeedComponentHandlers,\n} from \"./component\";\nimport { createFrameTransformer } from \"./frame\";\nimport { createIconHandler } from \"./icon\";\nimport { createInstanceTransformer } from \"./instance\";\nimport {\n createContainerLayoutPropsConverter,\n createFrameFillPropsConverter,\n createIconSelfLayoutPropsConverter,\n createRadiusPropsConverter,\n createSelfLayoutPropsConverter,\n createShapeFillPropsConverter,\n createStrokePropsConverter,\n createTextFillPropsConverter,\n createTypeStylePropsConverter,\n createVectorChildrenFillPropsConverter,\n} from \"./props\";\nimport {\n createBooleanOperationTransformer,\n createRectangleTransformer,\n createVectorTransformer,\n} from \"./shape\";\nimport { createTextTransformer } from \"./text\";\nimport {\n defaultRawValueFormatters,\n defaultStyleNameFormatter,\n defaultVariableNameFormatter,\n} from \"./value-resolver\";\n\nexport interface CreatePipelineConfig {\n shouldInferAutoLayout?: boolean;\n shouldInferVariableName?: boolean;\n extend?: {\n componentHandlers?: Array<UnboundComponentHandler<any>>;\n };\n}\n\nconst iconHandler = createIconHandler({\n iconService,\n});\n\nexport function createPipeline(options: CreatePipelineConfig = {}) {\n const { shouldInferAutoLayout = true, shouldInferVariableName = true, extend = {} } = options;\n\n const valueResolver = createValueResolver({\n variableService,\n variableNameFormatter: defaultVariableNameFormatter,\n styleService,\n styleNameFormatter: defaultStyleNameFormatter,\n rawValueFormatters: defaultRawValueFormatters,\n shouldInferVariableName,\n });\n\n const containerLayoutPropsConverter = createContainerLayoutPropsConverter(valueResolver);\n const selfLayoutPropsConverter = createSelfLayoutPropsConverter(valueResolver);\n const iconSelfLayoutPropsConverter = createIconSelfLayoutPropsConverter(valueResolver);\n const frameFillPropsConverter = createFrameFillPropsConverter(valueResolver);\n const shapeFillPropsConverter = createShapeFillPropsConverter(valueResolver);\n const textFillPropsConverter = createTextFillPropsConverter(valueResolver);\n const vectorChildrenFillPropsConverter = createVectorChildrenFillPropsConverter(valueResolver);\n const radiusPropsConverter = createRadiusPropsConverter(valueResolver);\n const strokePropsConverter = createStrokePropsConverter(valueResolver);\n const typeStylePropsConverter = createTypeStylePropsConverter({\n valueResolver,\n });\n const propsConverters = {\n containerLayout: containerLayoutPropsConverter,\n selfLayout: selfLayoutPropsConverter,\n iconSelfLayout: iconSelfLayoutPropsConverter,\n frameFill: frameFillPropsConverter,\n shapeFill: shapeFillPropsConverter,\n textFill: textFillPropsConverter,\n vectorChildrenFill: vectorChildrenFillPropsConverter,\n radius: radiusPropsConverter,\n stroke: strokePropsConverter,\n typeStyle: typeStylePropsConverter,\n };\n\n const componentHandlers = Object.fromEntries(\n [...unboundSeedComponentHandlers, ...(extend.componentHandlers ?? [])]\n .map((h) =>\n bindComponentHandler(h, {\n valueResolver,\n iconHandler,\n }),\n )\n .map((t) => [t.key, t]),\n );\n\n const frameTransformer = createFrameTransformer({\n propsConverters,\n });\n const instanceTransformer = createInstanceTransformer({\n iconHandler,\n propsConverters,\n componentHandlers,\n frameTransformer,\n });\n const textTransformer = createTextTransformer({\n propsConverters,\n });\n const rectangleTransformer = createRectangleTransformer({\n propsConverters,\n });\n const vectorTransformer = createVectorTransformer();\n const booleanOperationTransformer = createBooleanOperationTransformer();\n\n const codeGenerator = createCodeGenerator({\n frameTransformer,\n textTransformer,\n rectangleTransformer,\n instanceTransformer,\n vectorTransformer,\n booleanOperationTransformer,\n shouldInferAutoLayout,\n });\n\n return codeGenerator;\n}\n"],"names":[],"mappings":";;AACO;AACA;AACA;AACP;AACA;AACO;AACA;AACA;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;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACO;;ACnEA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACRO;;ACUA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AClBO;AACP;AACA;AACA;AACA;AACA;;ACNO;;ACGA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC7BO;;ACEA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACA;AACA;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACA;AACO;;ACtFA;AACP;AACA;AACO;AACA;AACA;;ACLA;AACP;AACA;AACO;;ACJA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;;ACLO;AACP;AACA;AACA;AACA;AACA;AACO;;ACPA;AACP;AACA;AACO;;ACJA;AACP;AACA;AACA;;ACDO;AACA;AACA;;ACLA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;;;"}
@@ -228,10 +228,10 @@ function inferLayout(parentNode) {
228
228
  }
229
229
  }
230
230
  // Heuristic: Prefer axis with more non-negative gaps and lower variance
231
- const hVariance = calculateVariance(horizontalGaps.filter((g)=>g >= -1));
232
- const vVariance = calculateVariance(verticalGaps.filter((g)=>g >= -1));
233
- const hCount = horizontalGaps.filter((g)=>g >= -1).length;
234
- const vCount = verticalGaps.filter((g)=>g >= -1).length;
231
+ const hVariance = calculateVariance(horizontalGaps.filter((g)=>g >= -EPSILON));
232
+ const vVariance = calculateVariance(verticalGaps.filter((g)=>g >= -EPSILON));
233
+ const hCount = horizontalGaps.filter((g)=>g >= -EPSILON).length;
234
+ const vCount = verticalGaps.filter((g)=>g >= -EPSILON).length;
235
235
  let primaryAxisSortedNodes = sortedByX; // Default guess
236
236
  // Basic variance check (lower is better). Add slight bias for horizontal if equal.
237
237
  if (vCount > 0 && (hCount === 0 || vVariance < hVariance - EPSILON && vCount >= hCount || vVariance <= hVariance && vCount > hCount)) {
@@ -258,7 +258,7 @@ function inferLayout(parentNode) {
258
258
  }
259
259
  }
260
260
  const primaryGaps = result.layoutMode === "HORIZONTAL" ? horizontalGaps : verticalGaps;
261
- const validGaps = primaryGaps.filter((g)=>g >= -1); // Allow slight overlap
261
+ const validGaps = primaryGaps.filter((g)=>g >= -EPSILON); // Allow slight overlap
262
262
  // --- 2. Calculate Spacing & Primary Alignment ---
263
263
  let isSpaceBetween = false;
264
264
  const collectiveBox = getCollectiveBoundingBox(children);
@@ -302,7 +302,7 @@ function inferLayout(parentNode) {
302
302
  // Use median spacing for robustness against outliers
303
303
  result.itemSpacing = calculateMedian(validGaps);
304
304
  // Clamp negative spacing if it's very small (likely float error)
305
- if (result.itemSpacing < 0 && result.itemSpacing > -1) {
305
+ if (result.itemSpacing < 0 && result.itemSpacing > -EPSILON) {
306
306
  result.itemSpacing = 0;
307
307
  }
308
308
  } else {
package/lib/index.d.ts CHANGED
@@ -179,3 +179,4 @@ declare function getFigmaColorVariableNames(scopes: Array<"fg" | "bg" | "stroke"
179
179
 
180
180
  export { componentRepository, createIconService, createPluginNormalizer, createRestNormalizer, createStaticComponentRepository, createStaticIconRepository, createStaticStyleRepository, createStaticVariableRepository, createStyleService, createVariableService, getFigmaColorVariableNames, getFigmaStyleKey, getFigmaVariableKey, iconRepository, styleRepository, variableRepository };
181
181
  export type { ComponentMetadata, ComponentRepository, IconData, IconRepository, IconService, NormalizedBooleanOperationNode, NormalizedComponentNode, NormalizedCornerTrait, NormalizedDefaultShapeTrait, NormalizedFrameNode, NormalizedFrameTrait, NormalizedHasChildrenTrait, NormalizedHasFramePropertiesTrait, NormalizedHasGeometryTrait, NormalizedHasLayoutTrait, NormalizedInstanceNode, NormalizedIsLayerTrait, NormalizedRectangleNode, NormalizedSceneNode, NormalizedTextNode, NormalizedTextSegment, NormalizedTypePropertiesTrait, NormalizedUnhandledNode, NormalizedVectorNode, Style, StyleRepository, StyleService, StyleType, Variable, VariableCollection, VariableRepository, VariableService, VariableServiceDeps, VariableType, VariableValue, VariableValueResolved };
182
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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\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 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\";\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 ...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 ...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 ...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 ...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 ]);\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 ...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 ...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 ...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 };\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 if (paint.type === \"SOLID\") {\n return normalizeSolidPaint(paint);\n }\n if (paint.type === \"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 }\n throw new Error(`Unimplemented paint type: ${paint.type}`);\n }\n\n function normalizePaints(fills: readonly Paint[] | PluginAPI[\"mixed\"]): FigmaRestSpec.Paint[] {\n if (fills === figma.mixed) {\n throw new Error(\"Mixed fills are not supported\");\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 function normalizeShapeProps(\n node: Pick<\n RectangleNode,\n | \"fills\"\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 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 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 function normalizeAutolayoutProps(node: Omit<FrameNode, \"type\" | \"clone\">) {\n return {\n ...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(node: Pick<FrameNode, \"boundVariables\">) {\n return {\n ...node.boundVariables,\n fills: await Promise.all(\n node.boundVariables?.fills?.map((fill) =>\n figma.variables.getVariableByIdAsync(fill.id).then((res) => {\n return {\n ...fill,\n id: res?.key ?? fill.id,\n };\n }),\n ) ?? [],\n ),\n size: {\n x: node.boundVariables?.width,\n y: node.boundVariables?.height,\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}: { iconRepository: IconRepository }): 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_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(FIGMA_TEXT_STYLES);\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;AACA;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;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACO;;ACjEA;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;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seed-design/figma",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/daangn/seed-design.git",
@@ -38,13 +38,13 @@
38
38
  "lint:publish": "bun publint"
39
39
  },
40
40
  "dependencies": {
41
- "@seed-design/css": "0.1.2",
41
+ "@seed-design/css": "0.1.3",
42
42
  "change-case": "^5.4.4",
43
43
  "ts-pattern": "^5.7.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@figma/plugin-typings": "^1.110.0",
47
- "@figma/rest-api-spec": "^0.32.0",
47
+ "@figma/rest-api-spec": "^0.33.0",
48
48
  "@seed-design/figma-extractor": "^0.0.4",
49
49
  "typescript": "^5.8.3"
50
50
  },