component-processor.ts•4.35 kB
import type { SimplifiedNode } from "../types/figma.js";
import type { FlatElement } from "../types/generator.js";
import {
resolveAppearance,
resolveText,
resolveLayout,
} from "./style-processor.js";
import { getSimplifiedFigmaData, simplifyType } from "./node-processor.js";
/**
* Resolves component-related properties of a node.
* Identifies instances, retrieves variants, and extracts component properties.
*/
export const resolveComponent = ({
node,
components,
}: {
node: SimplifiedNode;
components: Record<string, any>;
}): FlatElement["component"] | undefined => {
if (
node.type !== "INSTANCE" &&
!node.componentId &&
!node.componentProperties
) {
return undefined;
}
const component: FlatElement["component"] = {
isInstance: node.type === "INSTANCE" || !!node.componentId,
};
component.componentName = node.name;
if (node.componentId) {
component.componentId = node.componentId;
const componentDef = components[node.componentId];
if (componentDef) {
if (componentDef.description) {
component.description = componentDef.description;
}
}
}
if (node.componentProperties) {
component.variantProperties = Object.entries(node.componentProperties).map(
([name, prop]) => {
const value =
typeof prop === "object" && "value" in prop
? prop.value.toString()
: String(prop);
return {
name,
value,
};
}
);
}
if (node.exposedInstances && node.exposedInstances.length > 0) {
component.exposedInstances = node.exposedInstances;
}
if (node.isExposedInstance !== undefined) {
component.isExposed = node.isExposedInstance;
}
if (node.componentSetId) {
component.componentSetId = node.componentSetId;
}
if (node.overrides && node.overrides.length > 0) {
component.overrides = node.overrides.map((override) => ({
id: override.id,
overriddenFields: override.overriddenFields,
}));
}
if (node.componentPropertyReferences) {
const references = Object.entries(node.componentPropertyReferences).map(
([field, reference]) => ({
field,
reference,
})
);
component.propertyReferences = references;
}
return component;
};
/**
* Recursively processes a node and its children to build a flat element structure.
* Converts the hierarchical Figma nodes into a flat array with parent-child relationships.
*/
export const processNode = ({
data,
elements,
node,
parentId,
level = 0,
pathPrefix = "",
}: {
data: ReturnType<typeof getSimplifiedFigmaData>;
elements: FlatElement[];
node: SimplifiedNode;
parentId?: string;
level?: number;
pathPrefix?: string;
}): void => {
const path = pathPrefix ? `${pathPrefix} > ${node.id}` : node.id;
const appearance = resolveAppearance({ node, styles: data.styles });
const text = resolveText({ node, styles: data.styles });
const layout = resolveLayout({ node, styles: data.styles });
const component = resolveComponent({ node, components: data.components });
let parentComponentReferences: FlatElement["parentComponentReferences"] = [];
if (node.componentPropertyReferences) {
parentComponentReferences = Object.entries(
node.componentPropertyReferences
).map(([name, value]) => ({ name, value }));
}
const flatElement: FlatElement = {
id: node.id,
name: node.name,
type: simplifyType(node.type),
parentId,
childIds: node.children?.map((child) => child.id) || [],
level,
path,
appearance,
text,
layout,
component,
parentComponentReferences,
};
elements.push(flatElement);
if (node.children) {
node.children.forEach((child, index) => {
processNode({
data,
elements,
node: child,
parentId: node.id,
level: level + 1,
pathPrefix: path,
});
});
}
};
/**
* Processes all nodes in the data to create a flat array of elements.
* This is the main entry point for converting Figma nodes to processable elements.
*/
export const flattenElements = (
data: ReturnType<typeof getSimplifiedFigmaData>
): FlatElement[] => {
const elements: FlatElement[] = [];
data.nodes.forEach((node) => processNode({ data, elements, node }));
return elements;
};