Skip to content

feat(layout): opt-in RTL layout mirroring#50

Open
chiefcll wants to merge 1 commit into
mainfrom
feat/rtl-layout-mirroring
Open

feat(layout): opt-in RTL layout mirroring#50
chiefcll wants to merge 1 commit into
mainfrom
feat/rtl-layout-mirroring

Conversation

@chiefcll
Copy link
Copy Markdown
Contributor

@chiefcll chiefcll commented Jun 1, 2026

Summary

Adds an inherited, opt-in rtl flag to nodes that mirrors layout horizontally — child x is measured from the parent's right edge, and left/right text alignment is reversed. Setting rtl on the root mirrors the whole app; an explicit rtl: false opts a sub-tree back out.

Scope: this is RTL layout structure only (rails/nav mirroring, alignment). Bidirectional text reordering (Hebrew/Arabic glyph order) is a separate opt-in follow-up (Phase 2) and is intentionally out of scope here. Until that lands, rtl should be documented as "layout mirroring", not full RTL.

How it works

  • New rtl: boolean | null prop (null = inherit, the default), exposed via INodeProps/INode.
  • _ownRtl (local override) + resolved _rtl on CoreNode; inheritance resolves top-down through a new UpdateType.Direction pass that reuses the existing dirty-flag traversal. The root is resolved eagerly in its setter (it's driven by Stage.drawFrame, not update()).
  • The mirror (lt.tx += parentW − 2·x − w·scaleX) is applied as a temporary shift/restore of the local transform during the world-transform compose, so the cached transform stays pristine and isn't double-mirrored next frame.
  • Hot-path cost: LTR apps pay a single parent._rtl === true check per node; no bundle weight worth gating.
  • textAlign left/right flips via a shared resolveTextAlign() used by both Canvas and SDF renderers and text-node containment; SDF layout cache key now includes rtl. Text nodes resolve direction before generating layout, so a change reflows in the same frame (no flash).

Caveat

Mirroring requires a known parent width (w). A parent without w lays out LTR (matches the upstream Lightning behavior).

Test plan

  • 11 new unit tests (CoreNode.test.ts): inheritance, explicit override, mirror math, scaleX, LTR/zero-width no-mirror cases, local-transform restoration.
  • Full unit suite green (254 passing).
  • tsc --build clean; Prettier clean.
  • New visual-regression example examples/tests/rtl-layout.ts with a CI-certified snapshot generated via Docker. Visually verified: positions mirror under an RTL parent, child markers mirror by each box's own resolved rtl, the rtl: false override works, and RTL text right-aligns.

🤖 Generated with Claude Code

Introduce an inherited `rtl` flag on nodes that mirrors child x-positions
within the parent's width and reverses `left`/`right` text alignment. The
flag resolves by inheritance (own override, else parent), propagated via a
new `UpdateType.Direction` pass; the mirror is applied as a temporary
shift/restore of the local transform in the world-transform step, so LTR
apps pay only a single boolean check on the hot path.

This covers RTL layout structure only (rails/nav mirroring, alignment).
Bidirectional text reordering (Hebrew/Arabic glyph order) is a separate
opt-in follow-up and is intentionally out of scope here.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Comment thread src/core/CoreNode.ts
}
}

if (rtlMirrored === true) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Maybe could have gone with if (rtlOrigTx !== 0) and drop the bool

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