Skip to content

fix(super-editor): repair caret, keyboard, and drag interactions around block SDTs (SD-3237)#3536

Merged
caio-pizzol merged 48 commits into
luccas/sd-3274-disable-styling-toolbar-when-selection-includes-a-locked-sdtfrom
luccas/sdt-table-navigation
May 27, 2026
Merged

fix(super-editor): repair caret, keyboard, and drag interactions around block SDTs (SD-3237)#3536
caio-pizzol merged 48 commits into
luccas/sd-3274-disable-styling-toolbar-when-selection-includes-a-locked-sdtfrom
luccas/sdt-table-navigation

Conversation

@luccas-harbour
Copy link
Copy Markdown
Contributor

Summary

Repairs hover, click-to-place, Backspace/Delete, and drag-and-drop behaviors around block-level Structured Document Tags (SDTs). The core defect was that block SDTs were "sticky" boundaries: the chrome label was picked up by caret lookups, Backspace/Delete at adjacent paragraph boundaries deleted into protected content (or did nothing useful), and dragging an SDT could snap it back to its source position.

  • Caret lookup (DomPositionIndex): excludes structured-content chrome labels from the body position index so hover and click-to-place no longer resolve to the label element when it shares a pm-start/pm-end range with its content.
  • Backspace/Delete chain: adds moveIntoBlockSdtBeforeTextBlockStart (backspace chain) and moveIntoBlockSdtAfterTextBlockEnd (delete chain). When the caret is at the textblock boundary adjacent to a block SDT, the caret jumps to the last/first content cursor position inside the SDT instead of deleting into it. Handles nested SDTs, tables inside SDTs, hidden markers (bookmarks, comment ranges, perm markers, hidden field annotations), block atoms, marker-only paragraphs, and empty block SDTs as edge cases.
  • Drag-and-drop boundary (internal-node-move): resolveInsertionBoundary now sorts candidate boundaries by distance from the requested target and only uses the bias side as a tie-break. The drop path passes the mapped source start as avoidPos, so a moved block lands at the next nearest valid boundary rather than snapping back to its original slot.
  • SDT label CSS (layout-engine/painters/dom/styles.ts): consolidates block and inline label rules into one shared box-model declaration (size, padding, border, background, text color), adds a shared ::before drag-handle indicator (the three-dot vertical grip), and reduces the rule blocks to positioning, border-radius, and the inline display: inline-flex override. Caps block label width at 130px.
  • Shared helpers: lifts findFirstTextPosInNode / findLastTextPosInNode out of tableBoundaryNavigation.js into core/commands/helpers/textPositions.js and adds findFirstContentCursorPosInNode / findLastContentCursorPosInNode plus an isZeroWidthMarker predicate covering bookmarks, comment ranges, perm markers, ToC/index/authority entries, passthroughs, and hidden field annotations.

Files

  • packages/layout-engine/painters/dom/src/styles.ts + styles.test.ts — shared label box model and drag-handle indicator
  • packages/super-editor/src/editors/v1/dom-observer/DomPositionIndex.ts + test — chrome-label exclusion
  • packages/super-editor/src/editors/v1/core/commands/helpers/textPositions.js — new shared helper
  • packages/super-editor/src/editors/v1/core/commands/moveIntoBlockSdtAtTextBlockBoundary.js + thin re-export shims + ~1.2k lines of unit tests
  • packages/super-editor/src/editors/v1/core/commands/{core-command-map.d.ts,core-command-map.test.ts,index.js} — command registration
  • packages/super-editor/src/editors/v1/core/extensions/keymap.js + chain test — wires both commands into the Backspace/Delete chains
  • packages/super-editor/src/editors/v1/core/presentation-editor/input/internal-node-move.ts + test — distance-sorted boundary resolution with avoidPos
  • packages/super-editor/src/editors/v1/extensions/table/tableHelpers/tableBoundaryNavigation.js — switches to shared helpers
  • tests/behavior/tests/sdt/sd-3237-sdt-interactions.spec.ts — two new behavior tests for Backspace/Delete into a block SDT containing a table

Structured-content chrome labels share pm-start/pm-end ranges with their
underlying content. Without exclusion, DomPositionIndex could return the
label element for caret lookups, breaking hover and click-to-place
interactions over block SDTs.
Adds moveIntoBlockSdtBeforeTextBlockStart to the backspace chain so
that Backspace at the start of a textblock following a block SDT moves
the caret to the last text position inside the SDT instead of deleting
into protected content. Lifts findFirstTextPosInNode / findLastTextPosInNode
out of the table boundary navigation plugin into a shared helper module.
Adds moveIntoBlockSdtAfterTextBlockEnd to the delete chain so that
Delete at the end of a textblock preceding a block SDT moves the caret
to the first text position inside the SDT instead of deleting into
protected content. Mirrors the existing backspace-side handler.
resolveInsertionBoundary now sorts candidate boundaries by distance from
the requested target and falls back to side-of-bias only as a tie-break.
The drop path passes the mapped source start as a position to avoid, so
the moved node lands on the next nearest valid boundary instead of
snapping back to where it came from.
… labels

Consolidates structured content label rules so block and inline SDT
labels share a single declaration for size, padding, border, background,
and a new drag-handle ::before indicator. Scope-specific rules now only
carry positioning, border-radius, and the inline display: inline-flex
override.
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.

No issues found across 19 files

Re-trigger cubic

@luccas-harbour luccas-harbour force-pushed the luccas/sdt-table-navigation branch from 81d60b6 to f3fe907 Compare May 27, 2026 17:16
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 changed the title fix(super-editor): repair caret, keyboard, and drag interactions around block SDTs fix(super-editor): repair caret, keyboard, and drag interactions around block SDTs (SD-3237) May 27, 2026
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 27, 2026

SD-3237

luccas-harbour and others added 6 commits May 27, 2026 18:59
…int modes

Remove CSS rules that blanked the ::before content for empty SDT placeholders
in viewing and print modes so the placeholder prompt stays visible. Update
tests to assert the rules are absent.
…ments

fix(sdt): empty SDT placeholder text + cursor/keyboard interactions (SD-3237)
@caio-pizzol caio-pizzol merged commit b5db967 into luccas/sd-3274-disable-styling-toolbar-when-selection-includes-a-locked-sdt May 27, 2026
5 checks passed
@caio-pizzol caio-pizzol deleted the luccas/sdt-table-navigation 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