Skip to content

feat: textbox content editing - rendering and interaction (phase 0 and 1)#3700

Merged
luccas-harbour merged 27 commits into
mainfrom
artem/textbox-v4
Jun 10, 2026
Merged

feat: textbox content editing - rendering and interaction (phase 0 and 1)#3700
luccas-harbour merged 27 commits into
mainfrom
artem/textbox-v4

Conversation

@artem-harbour

@artem-harbour artem-harbour commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Main task: SD-3395

Sub-tasks (phases): SD-3402, SD-3404

Summary

This PR adds real text editing inside text boxes.

Text boxes imported from DOCX can now behave much more like normal document
content: you can click inside them, place the caret, type, delete text, and
move the caret in body content, headers/footers, and table cells.

It also improves import/export so textbox content and positioning survive
round-trip more reliably for both DrawingML and VML textboxes.

What’s included

Editable textbox content

Text inside supported text boxes is now laid out and rendered as addressable
document content rather than as a flat text blob. That makes caret placement,
click-to-position, and text editing work inside the textbox.

Better import/export round-trip

DrawingML textboxes are now imported into the same editable structure we already
use for VML textboxes, and exported back from live editor state. VML textbox
positioning is also preserved more reliably across save/reload.

Works in more contexts

Textbox editing now works not only in the main document flow, but also inside:

  • headers and footers
  • table cells
  • behindDoc=1 header/footer textboxes

Implementation notes

  • Normalized DrawingML textbox import to the same PM structure as VML textboxes
  • Added textbox paragraph layout/measuring in the layout engine
  • Rendered textbox lines with PM position metadata for DOM-based hit testing
  • Routed clicks and caret geometry through textbox DOM content
  • Added layout invalidation for textbox content changes
  • Fixed header/footer interaction issues for behindDoc textboxes

@github-actions

Copy link
Copy Markdown
Contributor

I've tried the ecma-spec MCP tools repeatedly (attributes, enum, children, search) and every call is being denied permission in this environment, so I couldn't run live spec lookups. I reviewed the OOXML element/attribute usage against ECMA-376 from knowledge instead. Flagging that up front for transparency.


Status: PASS

I went through the six handler files you listed, focusing on what XML elements/attributes they read on import and emit on export. Everything maps to real ECMA-376 / VML constructs with correct defaults.

DrawingML side (import-drawingml-textbox.js, encode-image-node-helpers.js, textbox-content-helpers.js):

  • wps:cNvSpPr/@txBox and wps:bodyPr/@fromWordArt are read as '1' booleans — both are legitimate boolean attributes (CT_NonVisualDrawingShapeProps.txBox, CT_TextBodyProperties.fromWordArt). Correct.
  • wps:bodyPr insets lIns/tIns/rIns/bIns and anchor are valid a:CT_TextBodyProperties attributes.
  • The default insets in extractBodyPrProperties (lIns/rIns = 91440 EMU, tIns/bIns = 45720 EMU) match the spec's schema defaults exactly, and the EMU→px math (×96/914400 → 9.6px / 4.8px) is right.
  • anchor mapping t→top, ctr→center, b→bottom with a top default is consistent with ST_TextAnchoringType and the documented default. just/dist fall through to top — incomplete but not a violation.
  • wps:txbxw:txbxContentw:p traversal is correct.

VML side (handle-shape-textbox-import.js, translate-shape-container.js):

  • Reads/writes v:shape, v:textbox, w10:wrap, and the mso-position-horizontal[-relative] / mso-position-vertical[-relative] / margin-left / margin-top style props. These are all valid VML / Office-drawing style properties (Part 4), and the import→export round-trip keeps the values in their native VML form, so no cross-vocabulary mismatch.

Export wrapper (translate-drawingml-textbox.js):

  • Emits mc:AlternateContent > mc:Choice[@Requires="wps"] > w:drawing. Requires="wps" referencing the wordprocessingShape prefix is the standard form, and mc:Choice without an mc:Fallback is schema-valid (Fallback is optional in the MC content model).

r-translator.js: The block-hoist of shapeContainer out of w:r is internal PM-node plumbing, not an XML-schema concern.

A couple of non-blocking notes (not spec violations): the DrawingML export drops the VML mc:Fallback branch, so older non-wps readers won't get a fallback shape; and convertToPixels treats unitless VML margins as px, where VML's nominal default unit is points — worth a sanity check but unlikely to matter for Word-authored files which always carry units.

If you want, I can re-run the actual ECMA-376 lookups to harden these conclusions once the mcp__ecma-spec__* tools are permitted.

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@linear-code

linear-code Bot commented Jun 10, 2026

Copy link
Copy Markdown

SD-3404

SD-3402

SD-3395

@artem-harbour artem-harbour marked this pull request as ready for review June 10, 2026 12:48
@artem-harbour artem-harbour requested a review from a team as a code owner June 10, 2026 12:48

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

if (fragment.kind === 'para' && fragment.pmStart != null && fragment.pmEnd != null) {

P2 Badge Resolve DOM hits inside textbox fragments

Now that textbox content is rendered as .superdoc-line[data-pm-start], clicks on textbox text usually succeed through the DOM-first path, but this enrichment loop only recognizes para fragments. For a textbox DOM position it falls through to the default { blockId: '', pageIndex: 0, lineIndex: -1 }, so body textboxes on later pages (and drag selection/virtualization state that uses the returned page/block metadata) are attributed to page 0 instead of the textbox fragment. The DOM-success path needs to identify drawingKind === 'textboxShape' content ranges just like the geometry fallback does.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/layout-engine/layout-bridge/src/diff.ts
Comment thread packages/layout-engine/layout-bridge/src/incrementalLayout.ts
Comment thread packages/layout-engine/layout-bridge/src/position-hit.ts Outdated
Comment thread packages/layout-engine/layout-bridge/src/position-hit.ts Outdated
Comment thread packages/layout-engine/painters/dom/src/renderer.ts
@luccas-harbour

Copy link
Copy Markdown
Contributor

hey @artem-harbour! I had a look, tested everything manually in the browser and it's looking good.

claude/codex found a few things that I left as inline comments.

@artem-harbour

Copy link
Copy Markdown
Contributor Author

Hi @luccas-harbour,

All comments were addressed.

@luccas-harbour luccas-harbour left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@luccas-harbour luccas-harbour enabled auto-merge (squash) June 10, 2026 14:59
@luccas-harbour luccas-harbour merged commit 52d0f16 into main Jun 10, 2026
69 checks passed
@luccas-harbour luccas-harbour deleted the artem/textbox-v4 branch June 10, 2026 15:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants