Skip to content

Render parity: content-width image sizing, containerPadding, unique IDs#32

Merged
ivoIturrieta merged 4 commits into
mainfrom
fix/render-parity-contentwidth-images
Jun 28, 2026
Merged

Render parity: content-width image sizing, containerPadding, unique IDs#32
ivoIturrieta merged 4 commits into
mainfrom
fix/render-parity-contentwidth-images

Conversation

@ivoIturrieta

@ivoIturrieta ivoIturrieta commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

Fixes the rendering-engine gaps surfaced by testing cold-start agents on 0.1.12. Each change is verified to match what the editor does.

Fixes

1. Images size against the real content width (no more 500px cap)

Item exporters got no column/body context, so the width-aware image exporter fell back to ~500px regardless of contentWidth or column count — full-width images rendered small and images in multi-column rows could overflow. Column now threads its index, row cells, and row/column/body values to its items, and renderComponent surfaces them on the exporter meta, so the exporter computes the available width (contentWidth × column fraction, minus padding) exactly as the editor does (the computation already lives in @unlayer/exporters). A standalone item defaults contentWidth to 500 to match the schema default.
Result: full-width image fills the content width, a 3-column image sizes to ~1/3, nothing overflows.

2. containerPadding is a typed item prop (number → px)

It was threaded at runtime but only typed as a string and never exposed on item props — so containerPadding="10px" was a type error and a bare number rendered unitless. Now typed as SizeInput on the item base props, with number→px normalization in Column. Renders identically to the px string.

3. Unique element IDs in renderToHtml

renderToHtml reset ids per element position, so multi-row designs repeated u_row_1/u_column_1/u_content_*_1 — invalid HTML5 and out of step with both renderToJson (a global counter) and the editor (unique ids). A per-render id counter on _config (reset by Body, shared by reference, SSR-safe) now makes every id unique. Snapshot updates are id-attributes only.

Already at editor parity (verified, no change)

  • Page.backgroundColor in web mode — the editor's web body exporter doesn't put it on the wrapper either (only email() does); web pages set it on the host document <body>. Confirmed against @unlayer/exporters@1.415.0.
  • Heading text entity handling — matches the editor's unescape(text).
  • Flat border* on Column — already type-checks and renders.

Tests & CI

  • New regression tests: contentWidth-aware image sizing (Image.test.tsx), containerPadding render + type (dx-behaviors + dx-types gate), unique-id guard (render-to-html.test.tsx).
  • All run in CI already: vitest via pnpm test, the type gate via the "Type contract" step.
  • build ✅ · typecheck ✅ · 339 tests ✅. Verified against a packed build and against the editor's own exporter output.

Backward-compatible (image sizes are now correct, not different by intent) — ships as a patch.


📖 Storybook Preview: https://unlayer.github.io/elements/pr/32/

ivoIturrieta and others added 3 commits June 28, 2026 13:38
Item exporters received no column/body context, so the width-aware image exporter
fell back to a fixed ~500px regardless of contentWidth or column count — full-width
images rendered small and images in multi-column rows could overflow their column.

Column now threads its index, the row cells, and the row/column/body values to its
item children, and renderComponent surfaces them on the exporter `meta`, so the
exporter computes the available width (contentWidth × column fraction, minus
padding) the same way the editor does. A standalone item (no Body) now defaults
contentWidth to 500 to match the schema default. Result: a full-width image fills
the content width, an image in a 3-column row sizes to ~1/3, and nothing overflows.

Updates the golden snapshot to the corrected sizes; adds regression tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… px)

containerPadding (an item's content-wrapper padding) was threaded at runtime but
only typed as a string and never exposed on item props — so containerPadding="10px"
was a type error, and a bare number would render unitless. Type it as SizeInput on
the item base props and normalize a number to px in Column, matching the other size
props. Renders identically to the equivalent px string. Adds render + type tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + editor)

renderToHtml generated ids per element position, so multi-row designs repeated
u_row_1 / u_column_1 / u_content_*_1 — invalid HTML5 and out of step with both
renderToJson (a global counter) and the editor (unique stored ids). Thread a
per-render id counter on _config (reset by Body, shared by reference down the
tree, SSR-safe) so every body/row/column/content id is unique. Updates the
multi-element snapshots (id attributes only); adds a uniqueness test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 28, 2026 12:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR closes several render-parity gaps between renderToHtml output and the Unlayer editor by threading full body/row/column context into item exporters (notably for width-aware image sizing), normalizing containerPadding as a typed item prop, and making generated HTML element IDs unique across an entire render.

Changes:

  • Thread row/column/body context into item exporters via meta to compute available widths correctly (fixing image sizing vs. real content width and column fractions).
  • Add containerPadding to base item props (SizeInput) and normalize numeric values to px in Column.
  • Introduce per-render ID counters on _config and use them for Body, Row, Column, and content wrapper IDs to avoid duplicates.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/react/src/utils/render-to-html.test.tsx Adds regression test asserting no duplicate u_* IDs across a multi-row tree.
packages/react/src/utils/create-component.tsx Adds containerPadding to item base props, threads metaContext to exporters, and introduces nextHtmlId plus standalone body width default.
packages/react/src/dx-types.test-d.tsx Type-gate coverage for containerPadding accepting number and string values.
packages/react/src/dx-behaviors.test.tsx Behavioral tests for containerPadding number→px normalization and string pass-through.
packages/react/src/components/Row.tsx Switches row htmlID generation to nextHtmlId (global per-render uniqueness).
packages/react/src/components/Image.test.tsx Regression tests for contentWidth-aware image sizing in 1-col and 3-col layouts.
packages/react/src/components/Column.tsx Threads column/body context to items, normalizes numeric containerPadding, and uses nextHtmlId for column/content IDs.
packages/react/src/components/Body.tsx Resets per-render ID counters on _config and uses nextHtmlId for body ID.
packages/react/src/components/snapshots/snapshots.test.tsx.snap Snapshot updates for changed content wrapper IDs.
packages/react/src/snapshots/golden-template.test.tsx.snap Snapshot updates reflecting unique IDs and corrected image width/max-width sizing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/react/src/utils/create-component.tsx Outdated
…ped "500px"

Use the CSS-string "500px" (matching BodyDefaults.contentWidth) for the
standalone-item contentWidth default instead of a bare number, so the value is a
CSS string everywhere it might be consumed. Behavior-neutral — the image exporter
parseFloats it either way; addresses a review note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ivoIturrieta ivoIturrieta merged commit 963da73 into main Jun 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants