diff --git a/craft/src/components/layout/ColumnLayout.toHtml.test.ts b/craft/src/components/layout/ColumnLayout.toHtml.test.ts new file mode 100644 index 0000000..c5d6799 --- /dev/null +++ b/craft/src/components/layout/ColumnLayout.toHtml.test.ts @@ -0,0 +1,31 @@ +import { describe, test, expect } from 'vitest'; +import { ColumnLayout } from './ColumnLayout'; + +const toHtml = (ColumnLayout as any).toHtml; + +describe('ColumnLayout.toHtml width export from split', () => { + test('non-default split (70-30) exports per-column width CSS matching each column', () => { + const { html } = toHtml({ columns: 2, split: '70-30', gap: '16px' }, '
A
B
'); + // First column gets 70%, second gets 30% (same mapping as getWidths()). + expect(html).toMatch(/nth-child\(1\)[^}]*calc\(70% - 16px\)/); + expect(html).toMatch(/nth-child\(2\)[^}]*calc\(30% - 16px\)/); + }); + + test('default 50-50 split still exports equal widths', () => { + const { html } = toHtml({ columns: 2, split: '50-50', gap: '16px' }, '
A
B
'); + expect(html).toMatch(/nth-child\(1\)[^}]*calc\(50% - 16px\)/); + expect(html).toMatch(/nth-child\(2\)[^}]*calc\(50% - 16px\)/); + }); + + test('3-column 33-33-33 split exports three width rules', () => { + const { html } = toHtml({ columns: 3, split: '33-33-33', gap: '16px' }, '
A
B
C
'); + expect(html).toMatch(/nth-child\(1\)[^}]*calc\(33\.333% - 16px\)/); + expect(html).toMatch(/nth-child\(2\)[^}]*calc\(33\.333% - 16px\)/); + expect(html).toMatch(/nth-child\(3\)[^}]*calc\(33\.333% - 16px\)/); + }); + + test('childrenHtml is preserved in the output', () => { + const { html } = toHtml({ columns: 2, split: '70-30', gap: '16px' }, '
A
B
'); + expect(html).toContain('
A
B
'); + }); +}); diff --git a/craft/src/components/layout/ColumnLayout.tsx b/craft/src/components/layout/ColumnLayout.tsx index 7593382..a27a500 100644 --- a/craft/src/components/layout/ColumnLayout.tsx +++ b/craft/src/components/layout/ColumnLayout.tsx @@ -115,7 +115,11 @@ ColumnLayout.craft = { /* ---------- HTML export ---------- */ (ColumnLayout as any).toHtml = (props: ColumnLayoutProps, childrenHtml: string) => { + const columns = props.columns || 2; + const split = props.split || '50-50'; const gap = props.gap || '16px'; + const widths = getWidths(split, columns); + const outerStyle = cssPropsToString({ display: 'flex', flexWrap: 'wrap', @@ -124,7 +128,20 @@ ColumnLayout.craft = { ...props.style, }); const idAttr = props.anchorId ? ` id="${escapeAttr(props.anchorId)}"` : ''; + + // Each column is exported as an independently-serialized child node, so + // toHtml has no direct handle on individual children to rewrite their + // inline flex-basis. Instead, scope an nth-child CSS rule (with + // !important, to win over any stale inline flex baked into a child at + // creation time) to a generated class -- same width mapping (getWidths) + // the editor render uses. Precedent: Menu/Navbar toHtml already emit + // scoped \n
${childrenHtml}
`, }; };