Skip to content

fix(sdt): empty SDT placeholder text + cursor/keyboard interactions (SD-3237)#3542

Merged
caio-pizzol merged 26 commits into
luccas/sdt-table-navigationfrom
luccas/sdt-deletion-adjustments
May 27, 2026
Merged

fix(sdt): empty SDT placeholder text + cursor/keyboard interactions (SD-3237)#3542
caio-pizzol merged 26 commits into
luccas/sdt-table-navigationfrom
luccas/sdt-deletion-adjustments

Conversation

@luccas-harbour
Copy link
Copy Markdown
Contributor

Summary

Stacks on top of luccas/sdt-table-navigation. Fixes hover/click cursor placement and keyboard interactions for empty inline and block structured-content controls (SDTs), and replaces the invisible 8px spacer with a real "Click or tap here to enter text" placeholder.

Changes

Placeholder rendering (visible "Click or tap here to enter text")

  • contracts: introduce EMPTY_SDT_PLACEHOLDER_TEXT constant, broaden visualPlaceholder to 'emptyInlineSdt' | 'emptyBlockSdt', and add isEmptySdtPlaceholderRun helper (keeps isEmptyInlineSdtPlaceholderRun as a narrow alias).
  • pm-adapter/sdt/structured-content-block.ts: emit a placeholder paragraph for empty block SDTs (including empty-paragraph and vanished-paragraph cases). Empty block paragraphs route through paragraphToFlowBlocks so the placeholder run inherits the paragraph's resolved font, size, and color.
  • painters/dom: render placeholder via CSS pseudo-element (::before content from data-placeholder-text), not document text. New shared class superdoc-empty-sdt-placeholder plus per-variant superdoc-empty-inline-sdt-placeholder / superdoc-empty-block-sdt-placeholder. Selected-node highlight inherits system Highlight color. Placeholder is suppressed in viewing/print modes and when appearance='hidden'.
  • measuring/dom: measure the actual placeholder text width (with letter-spacing) instead of a fixed 8px spacer; trust the measured value when non-zero, fall back to a length-based estimate otherwise.
  • layout-bridge/remeasure: treat the placeholder as visible content during line breaking — return its text from runText (or empty string when appearance='hidden') and include its measured width in the line-fit loop so it can wrap atomically.
  • contracts/pm-range: keep the placeholder run's PM range atomic when computing line ranges (don't expand by character).
  • painters/dom/utils/sdt-helpers: skip container chrome styling when appearance='hidden'.
  • painters/dom/styles: size block SDT labels to max-content (capped at 130px) so short labels don't span the chrome width; inner span flexes with min-width: 0 for ellipsis.
  • painters/dom/renderer: include appearance in the SDT dataset and expose it on the DOM so CSS/JS can target hidden SDTs.

Pointer / caret / keyboard interactions

  • DomPointerMapping: clicks on the placeholder snap to its pmStart (inside the SDT) instead of the nearest wrapper edge.
  • DomSelectionGeometry: caret geometry for the placeholder uses the surrounding line rect and only treats positions strictly past pmEnd as "at end".
  • EditorInputManager: a click on an empty SDT placeholder no longer redirects to the wrapper's before/after boundary.
  • structured-content-select-plugin: ArrowLeft from immediately after an empty inline SDT re-enters it.
  • structured-content-lock-plugin:
    • Backspace/Delete inside an unlocked inline SDT (in a run) stays inside and deletes within the SDT instead of escaping into surrounding text.
    • Backspace/Delete targeting an sdtContentLocked inline SDT collapses the selection to the wrapper boundary rather than letting the keystroke fall through.
    • Allow historyUndo/historyRedo transactions through the lock guard so SDT content can be recovered after deletion.

Undo/redo transactions were being blocked by the structured-content lock
plugin, preventing recovery of SDT content after deletion. Bypass the
lock guard when the transaction is a history undo or redo.
When backspace or delete targets an sdtContentLocked structured-content
SDT, collapse the selection to the wrapper boundary instead of letting
the keystroke fall through. Prevents the locked inline content from
being mutated while keeping the caret in a usable position for the next
edit.
Replace the invisible 8px spacer with a full "Click or tap here to enter text"
placeholder for both inline and block structured-content controls. The
placeholder is layout-only (no document text), styled via CSS pseudo-element,
and selected-node highlight inherits the system Highlight color.

Wire the new chrome into pointer mapping, caret geometry, and the input
manager so clicks on the placeholder land inside the SDT instead of snapping
to the wrapper boundary. ArrowLeft from the trailing boundary now re-enters
an empty inline SDT, and Backspace/Delete inside an unlocked inline SDT no
longer escapes into surrounding text.
Route empty block-SDT paragraphs through paragraphToFlowBlocks so the
placeholder run picks up the paragraph's resolved font, size, and color
instead of falling back to the document defaults. The painter now applies
those run styles to the placeholder span, keeping "Click or tap here to
enter text" visually consistent with the surrounding paragraph chrome.
Use `max-content` with `max-width: 130px` instead of stretching block SDT
labels to the chrome width, so short labels no longer span the entire
block. Inner span now flexes with `min-width: 0` to keep ellipsis behavior.
Stop maxing the measured placeholder width with the fallback — for empty
SDTs we now trust the measured value and only fall back when measurement
returns zero. Also treat empty-SDT placeholder runs as visible content so
chrome geometry (--sd-sdt-chrome-left/width) is emitted for them.
@luccas-harbour luccas-harbour requested a review from a team as a code owner May 27, 2026 21:44
@luccas-harbour luccas-harbour changed the title fix(sdt): empty SDT placeholder text + cursor/keyboard interactions fix(sdt): empty SDT placeholder text + cursor/keyboard interactions (SD-3237) May 27, 2026
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 27, 2026

SD-3237

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 826a5a153f

ℹ️ 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/painters/dom/src/styles.ts Outdated
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 25 files

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

@caio-pizzol caio-pizzol merged commit c361468 into luccas/sdt-table-navigation May 27, 2026
40 checks passed
@caio-pizzol caio-pizzol deleted the luccas/sdt-deletion-adjustments branch May 27, 2026 23:13
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.

2 participants