fix(fig): preserve imported layout metadata
- Keep Figma stack sizing fields such as RESIZE_TO_FIT_WITH_IMPLICIT_SIZE - Avoid writing default auto-layout fields onto imported instances when Figma omitted them - Cover instance layout metadata roundtrips
This commit is contained in:
parent
68ebef5f9b
commit
9a6e29cf6f
|
|
@ -355,6 +355,7 @@ function convertLayoutProps(
|
|||
): Pick<
|
||||
SceneNode,
|
||||
| 'layoutMode'
|
||||
| 'figmaLayout'
|
||||
| 'itemSpacing'
|
||||
| 'paddingTop'
|
||||
| 'paddingBottom'
|
||||
|
|
@ -387,6 +388,27 @@ function convertLayoutProps(
|
|||
|
||||
return {
|
||||
layoutMode,
|
||||
figmaLayout: {
|
||||
stackMode: nc.stackMode,
|
||||
stackSpacing: nc.stackSpacing,
|
||||
stackPadding: nc.stackPadding,
|
||||
stackPaddingRight: nc.stackPaddingRight,
|
||||
stackPaddingBottom: nc.stackPaddingBottom,
|
||||
stackCounterAlign: nc.stackCounterAlign,
|
||||
stackJustify: nc.stackJustify,
|
||||
stackCounterAlignItems: nc.stackCounterAlignItems,
|
||||
stackPrimaryAlignItems: nc.stackPrimaryAlignItems,
|
||||
stackPrimarySizing: nc.stackPrimarySizing,
|
||||
stackCounterSizing: nc.stackCounterSizing,
|
||||
stackVerticalPadding: nc.stackVerticalPadding,
|
||||
stackHorizontalPadding: nc.stackHorizontalPadding,
|
||||
stackWrap: nc.stackWrap,
|
||||
stackPositioning: nc.stackPositioning,
|
||||
stackChildPrimaryGrow: nc.stackChildPrimaryGrow,
|
||||
stackChildAlignSelf: nc.stackChildAlignSelf,
|
||||
stackCounterSpacing: nc.stackCounterSpacing,
|
||||
bordersTakeSpace: nc.bordersTakeSpace as boolean | undefined
|
||||
},
|
||||
itemSpacing: nc.stackSpacing ?? 0,
|
||||
...convertLayoutPadding(nc),
|
||||
primaryAxisSizing,
|
||||
|
|
|
|||
|
|
@ -322,8 +322,53 @@ function serializeTextProps(
|
|||
}
|
||||
}
|
||||
|
||||
function normalizeStackMode(
|
||||
value: string | undefined
|
||||
): KiwiNodeChange['stackMode'] {
|
||||
return value === 'HORIZONTAL' || value === 'VERTICAL' || value === 'NONE' ? value : undefined
|
||||
}
|
||||
|
||||
function normalizeStackSizing(
|
||||
value: string | undefined
|
||||
): KiwiNodeChange['stackPrimarySizing'] {
|
||||
return value === 'FIXED' || value === 'RESIZE_TO_FIT' || value === 'RESIZE_TO_FIT_WITH_IMPLICIT_SIZE'
|
||||
? value
|
||||
: undefined
|
||||
}
|
||||
|
||||
function normalizeStackJustify(value: string | undefined): string | undefined {
|
||||
return value === 'SPACE_EVENLY' ? 'SPACE_BETWEEN' : value
|
||||
}
|
||||
|
||||
function normalizeStackCounterAlign(value: string | undefined): string | undefined {
|
||||
return value === 'SPACE_EVENLY' ? 'SPACE_BETWEEN' : value
|
||||
}
|
||||
|
||||
function serializeLayoutProps(node: SceneNode, nc: KiwiNodeChange): void {
|
||||
upsertPluginData(node, LAYOUT_DIRECTION_PLUGIN_KEY, node.layoutDirection)
|
||||
const figmaLayout = node.figmaLayout
|
||||
if (figmaLayout) {
|
||||
nc.stackMode = normalizeStackMode(figmaLayout.stackMode)
|
||||
nc.stackSpacing = figmaLayout.stackSpacing
|
||||
nc.stackPadding = figmaLayout.stackPadding
|
||||
nc.stackPaddingRight = figmaLayout.stackPaddingRight
|
||||
nc.stackPaddingBottom = figmaLayout.stackPaddingBottom
|
||||
nc.stackCounterAlign = normalizeStackCounterAlign(figmaLayout.stackCounterAlign)
|
||||
nc.stackJustify = normalizeStackJustify(figmaLayout.stackJustify)
|
||||
nc.stackCounterAlignItems = normalizeStackCounterAlign(figmaLayout.stackCounterAlignItems)
|
||||
nc.stackPrimaryAlignItems = normalizeStackJustify(figmaLayout.stackPrimaryAlignItems)
|
||||
nc.stackPrimarySizing = normalizeStackSizing(figmaLayout.stackPrimarySizing)
|
||||
nc.stackCounterSizing = normalizeStackSizing(figmaLayout.stackCounterSizing)
|
||||
nc.stackVerticalPadding = figmaLayout.stackVerticalPadding
|
||||
nc.stackHorizontalPadding = figmaLayout.stackHorizontalPadding
|
||||
nc.stackWrap = figmaLayout.stackWrap
|
||||
nc.stackPositioning = figmaLayout.stackPositioning
|
||||
nc.stackChildPrimaryGrow = figmaLayout.stackChildPrimaryGrow
|
||||
nc.stackChildAlignSelf = figmaLayout.stackChildAlignSelf
|
||||
nc.stackCounterSpacing = figmaLayout.stackCounterSpacing
|
||||
nc.bordersTakeSpace = figmaLayout.bordersTakeSpace
|
||||
return
|
||||
}
|
||||
if (node.layoutMode !== 'NONE' && node.layoutMode !== 'GRID') {
|
||||
nc.stackMode = node.layoutMode
|
||||
nc.stackSpacing = node.itemSpacing
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ export function createDefaultNode(
|
|||
lineHeight: null,
|
||||
letterSpacing: 0,
|
||||
layoutMode: 'NONE',
|
||||
figmaLayout: null,
|
||||
layoutDirection: 'AUTO',
|
||||
layoutWrap: 'NO_WRAP',
|
||||
primaryAxisAlign: 'MIN',
|
||||
|
|
|
|||
|
|
@ -229,6 +229,34 @@ export interface VariantPropSpec {
|
|||
value: string
|
||||
}
|
||||
|
||||
export type FigmaLayoutMetadata = Partial<
|
||||
Record<
|
||||
| 'stackMode'
|
||||
| 'stackCounterAlign'
|
||||
| 'stackJustify'
|
||||
| 'stackCounterAlignItems'
|
||||
| 'stackPrimaryAlignItems'
|
||||
| 'stackPrimarySizing'
|
||||
| 'stackCounterSizing'
|
||||
| 'stackWrap'
|
||||
| 'stackPositioning'
|
||||
| 'stackChildAlignSelf',
|
||||
string
|
||||
> &
|
||||
Record<
|
||||
| 'stackSpacing'
|
||||
| 'stackPadding'
|
||||
| 'stackPaddingRight'
|
||||
| 'stackPaddingBottom'
|
||||
| 'stackVerticalPadding'
|
||||
| 'stackHorizontalPadding'
|
||||
| 'stackChildPrimaryGrow'
|
||||
| 'stackCounterSpacing',
|
||||
number
|
||||
> &
|
||||
Record<'bordersTakeSpace', boolean>
|
||||
>
|
||||
|
||||
export interface SceneNode {
|
||||
id: string
|
||||
type: NodeType
|
||||
|
|
@ -283,6 +311,7 @@ export interface SceneNode {
|
|||
verticalConstraint: ConstraintType
|
||||
|
||||
layoutMode: LayoutMode
|
||||
figmaLayout: FigmaLayoutMetadata | null
|
||||
layoutDirection: LayoutDirection
|
||||
layoutWrap: LayoutWrap
|
||||
primaryAxisAlign: LayoutAlign
|
||||
|
|
|
|||
|
|
@ -130,6 +130,63 @@ describe('Fix 1: auto-layout child transforms', () => {
|
|||
expect(childNc.transform.m12).toBe(45)
|
||||
})
|
||||
|
||||
test('preserves imported Figma layout metadata for instance roundtrips', () => {
|
||||
const graph = new SceneGraph()
|
||||
const parent = graph.createNode('FRAME', pageId(graph), {
|
||||
name: 'Checked List',
|
||||
width: 878,
|
||||
height: 24,
|
||||
layoutMode: 'HORIZONTAL',
|
||||
counterAxisSizing: 'HUG',
|
||||
layoutAlignSelf: 'STRETCH',
|
||||
figmaLayout: {
|
||||
stackMode: 'HORIZONTAL',
|
||||
stackPrimarySizing: 'FIXED',
|
||||
stackCounterSizing: 'RESIZE_TO_FIT_WITH_IMPLICIT_SIZE',
|
||||
stackChildAlignSelf: 'STRETCH'
|
||||
}
|
||||
})
|
||||
|
||||
graph.createNode('INSTANCE', parent.id, {
|
||||
name: 'Lists',
|
||||
width: 282.6666564941406,
|
||||
height: 24,
|
||||
layoutMode: 'VERTICAL',
|
||||
layoutGrow: 1,
|
||||
figmaLayout: {
|
||||
stackMode: 'VERTICAL',
|
||||
stackChildPrimaryGrow: 1
|
||||
}
|
||||
})
|
||||
|
||||
const blobs: Uint8Array[] = []
|
||||
const changes = sceneNodeToKiwi(
|
||||
getNodeOrThrow(graph, parent.id),
|
||||
ROOT_GUID,
|
||||
0,
|
||||
{ value: 100 },
|
||||
graph,
|
||||
blobs
|
||||
) as Record<string, unknown>[]
|
||||
|
||||
const parentNc = expectDefined(
|
||||
changes.find((nc) => nc.name === 'Checked List'),
|
||||
'parent node change'
|
||||
)
|
||||
expect(parentNc.stackCounterSizing).toBe('RESIZE_TO_FIT_WITH_IMPLICIT_SIZE')
|
||||
|
||||
const instanceNc = expectDefined(
|
||||
changes.find((nc) => nc.name === 'Lists'),
|
||||
'instance node change'
|
||||
)
|
||||
expect(instanceNc.stackMode).toBe('VERTICAL')
|
||||
expect(instanceNc.stackChildPrimaryGrow).toBe(1)
|
||||
expect(instanceNc.stackPrimarySizing).toBeUndefined()
|
||||
expect(instanceNc.stackCounterSizing).toBeUndefined()
|
||||
expect(instanceNc.stackPrimaryAlignItems).toBeUndefined()
|
||||
expect(instanceNc.stackCounterAlignItems).toBeUndefined()
|
||||
})
|
||||
|
||||
test('horizontal auto-layout child also preserves its stored transform offsets', () => {
|
||||
const graph = new SceneGraph()
|
||||
const parent = graph.createNode('FRAME', pageId(graph), {
|
||||
|
|
|
|||
Loading…
Reference in a new issue