fix(super-editor): repair caret, keyboard, and drag interactions around block SDTs (SD-3237)#3536
Merged
caio-pizzol merged 48 commits intoMay 27, 2026
Conversation
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.
81d60b6 to
f3fe907
Compare
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.
…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)
b5db967
into
luccas/sd-3274-disable-styling-toolbar-when-selection-includes-a-locked-sdt
5 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
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 apm-start/pm-endrange with its content.moveIntoBlockSdtBeforeTextBlockStart(backspace chain) andmoveIntoBlockSdtAfterTextBlockEnd(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.internal-node-move):resolveInsertionBoundarynow 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 asavoidPos, so a moved block lands at the next nearest valid boundary rather than snapping back to its original slot.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::beforedrag-handle indicator (the three-dot vertical grip), and reduces the rule blocks to positioning, border-radius, and the inlinedisplay: inline-flexoverride. Caps block label width at 130px.findFirstTextPosInNode/findLastTextPosInNodeout oftableBoundaryNavigation.jsintocore/commands/helpers/textPositions.jsand addsfindFirstContentCursorPosInNode/findLastContentCursorPosInNodeplus anisZeroWidthMarkerpredicate 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 indicatorpackages/super-editor/src/editors/v1/dom-observer/DomPositionIndex.ts+ test — chrome-label exclusionpackages/super-editor/src/editors/v1/core/commands/helpers/textPositions.js— new shared helperpackages/super-editor/src/editors/v1/core/commands/moveIntoBlockSdtAtTextBlockBoundary.js+ thin re-export shims + ~1.2k lines of unit testspackages/super-editor/src/editors/v1/core/commands/{core-command-map.d.ts,core-command-map.test.ts,index.js}— command registrationpackages/super-editor/src/editors/v1/core/extensions/keymap.js+ chain test — wires both commands into the Backspace/Delete chainspackages/super-editor/src/editors/v1/core/presentation-editor/input/internal-node-move.ts+ test — distance-sorted boundary resolution withavoidPospackages/super-editor/src/editors/v1/extensions/table/tableHelpers/tableBoundaryNavigation.js— switches to shared helperstests/behavior/tests/sdt/sd-3237-sdt-interactions.spec.ts— two new behavior tests for Backspace/Delete into a block SDT containing a table