diff --git a/.changeset/violet-otters-impress.md b/.changeset/violet-otters-impress.md new file mode 100644 index 0000000..3b5abd3 --- /dev/null +++ b/.changeset/violet-otters-impress.md @@ -0,0 +1,5 @@ +--- +"penpot-exporter": minor +--- + +Register Text Variables as Instance override diff --git a/plugin-src/transformers/transformInstanceNode.ts b/plugin-src/transformers/transformInstanceNode.ts index 2d1639c..d9336be 100644 --- a/plugin-src/transformers/transformInstanceNode.ts +++ b/plugin-src/transformers/transformInstanceNode.ts @@ -18,22 +18,28 @@ import { transformStrokes } from '@plugin/transformers/partials'; -import { ComponentInstance } from '@ui/types'; +import { ComponentInstance, ComponentTextPropertyOverride } from '@ui/types'; export const transformInstanceNode = async ( node: InstanceNode, baseRotation: number ): Promise => { const mainComponent = await node.getMainComponentAsync(); - - if (mainComponent === null || isUnprocessableComponent(mainComponent)) { + if (mainComponent === null) { return; } - if (isExternalComponent(mainComponent)) { - registerExternalComponents(mainComponent); + const primaryComponent = getPrimaryComponent(mainComponent); + if (isUnprocessableComponent(primaryComponent)) { + return; } + if (primaryComponent.remote) { + registerExternalComponents(primaryComponent); + } + + registerTextVariableOverrides(node, primaryComponent); + if (node.overrides.length > 0) { node.overrides.forEach(override => overridesLibrary.register(override.id, override.overriddenFields) @@ -64,25 +70,67 @@ export const transformInstanceNode = async ( }; }; -const registerExternalComponents = (mainComponent: ComponentNode): void => { - let component: ComponentSetNode | ComponentNode = mainComponent; - - if (component.parent?.type === 'COMPONENT_SET') { - component = component.parent; +const getPrimaryComponent = (mainComponent: ComponentNode): ComponentNode | ComponentSetNode => { + if (mainComponent.parent?.type === 'COMPONENT_SET') { + return mainComponent.parent; } - if (remoteComponentLibrary.get(component.id) !== undefined) { + return mainComponent; +}; + +const registerExternalComponents = (primaryComponent: ComponentNode | ComponentSetNode): void => { + if (remoteComponentLibrary.get(primaryComponent.id) !== undefined) { return; } - remoteComponentLibrary.register(component.id, component); + remoteComponentLibrary.register(primaryComponent.id, primaryComponent); }; -const isExternalComponent = (mainComponent: ComponentNode): boolean => { - return ( - mainComponent.remote || - (mainComponent.parent?.type === 'COMPONENT_SET' && mainComponent.parent.remote) +const getComponentTextPropertyOverrides = ( + node: InstanceNode, + primaryComponent: ComponentNode | ComponentSetNode +): ComponentTextPropertyOverride[] => { + const componentPropertyDefinitions = Object.entries( + primaryComponent.componentPropertyDefinitions + ).filter(([, value]) => value.type === 'TEXT'); + + const instanceComponentProperties = new Map( + Object.entries(node.componentProperties).filter(([, value]) => value.type === 'TEXT') ); + + return componentPropertyDefinitions + .map(([key, value]) => { + const nodeValue = instanceComponentProperties.get(key); + return { + id: key, + ...value, + value: nodeValue ? nodeValue.value : value.defaultValue + } as ComponentTextPropertyOverride; + }) + .filter(({ value, defaultValue }) => value !== defaultValue); +}; + +const registerTextVariableOverrides = ( + node: InstanceNode, + primaryComponent: ComponentNode | ComponentSetNode +) => { + const mergedOverridden = getComponentTextPropertyOverrides(node, primaryComponent); + + if (mergedOverridden.length > 0) { + const textNodes = node + .findChildren(child => child.type === 'TEXT') + .filter(textNode => { + const componentPropertyReference = textNode.componentPropertyReferences?.characters; + return ( + componentPropertyReference && + mergedOverridden.some(property => property.id === componentPropertyReference) + ); + }); + + textNodes.forEach(textNode => { + overridesLibrary.register(textNode.id, ['text']); + }); + } }; /** @@ -91,13 +139,8 @@ const isExternalComponent = (mainComponent: ComponentNode): boolean => { * 1. If the component does not have a parent. (it's been removed) * 2. Main component can be in a ComponentSet (the same logic applies to the parent). */ -const isUnprocessableComponent = (mainComponent: ComponentNode): boolean => { - return ( - (mainComponent.parent === null && !mainComponent.remote) || - (mainComponent.parent?.type === 'COMPONENT_SET' && - mainComponent.parent.parent === null && - !mainComponent.parent.remote) - ); +const isUnprocessableComponent = (primaryComponent: ComponentNode | ComponentSetNode): boolean => { + return primaryComponent.parent === null && !primaryComponent.remote; }; const isComponentRoot = (node: InstanceNode): boolean => { diff --git a/ui-src/types/component.ts b/ui-src/types/component.ts index e28b5f6..189ace0 100644 --- a/ui-src/types/component.ts +++ b/ui-src/types/component.ts @@ -7,6 +7,13 @@ export type ComponentRoot = { type: 'component'; }; +export type ComponentTextPropertyOverride = { + id: string; + type: 'TEXT'; + value: string; + defaultValue: string; +}; + export type ComponentInstance = ShapeGeomAttributes & ShapeAttributes & LayoutAttributes &