fix(sdt): block image resize inside locked SDTs and align chrome to content (SD-3258)#3490
Merged
Conversation
…ragraph geometry Move block SDT border styling onto a ::after pseudo-element with pointer-events: none, and drop the 1px padding so the chrome no longer changes fragment geometry. Default inline image verticalAlign to 'top' so the image box stays within the measured line height.
When a paragraph line contains an inline image, set surrounding normal text runs to lineHeight: 'normal' and verticalAlign: 'bottom' so they sit beside the top-aligned image. Runs with explicit vertical positioning (vertAlign, baselineShift) are left untouched.
Compute content bounds from rendered lines (honoring paragraph alignment) and expose --sd-sdt-chrome-left/--sd-sdt-chrome-width on the fragment. Hover background moves to a ::before pseudo-element; both ::before and ::after use the chrome vars so the frame hugs the content instead of spanning the full fragment width.
Extend per-type field color CSS to target the structured-content block ::after pseudo-element so the label backdrop matches the field color in default, hover, and selected states.
Register data URI image sources as DOCX media parts during export so the relationship target resolves correctly. Map the svg+xml MIME subtype to a .svg extension when deriving the media filename.
…processing Detect SVG data URIs with known finite sizes and register them in place during browser-path image handling, skipping the canvas-based resize pipeline that strips vector content. Normalize svg+xml extensions to .svg when generating media filenames so the relationship target matches the stored media key.
…tadata Only emit the empty-inline-SDT placeholder when the resolved metadata describes a structuredContent node, so other inline SDT variants aren't collapsed into a placeholder text run when their content is empty.
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)
fix(super-editor): repair caret, keyboard, and drag interactions around block SDTs (SD-3237)
…ling-toolbar-when-selection-includes-a-locked-sdt feat(super-editor): disable toolbar mutations inside content-locked SDTs (SD-3274)
…et-as-presetcontent-in-sdt-field-does-not-persist fix(super-editor): persist data-URI images set as SDT preset content (SD-3116)
0174505
into
luccas/sd-3218-sdt-field-deletion-contract
6 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
Fixes image resizing and selection interactions inside structured-content (SDT) blocks, and overhauls the SDT block chrome so the border/hover backdrop hug the actual content instead of the full paragraph fragment. While the originating bug (SD-3258) was about image resize inside an SDT, the fix cascaded into geometry, diffing, versioning, and per-field-type styling work because the chrome was previously inflating paragraph geometry and the image runs were not being diffed/versioned at all.
Image resize inside SDTs
ImageResizeOverlay.vuenow walks the image's ancestor SDT chain (block + inline) and treats the resize as disabled whenever any wrapping SDT has a content-locked lock mode (contentLocked/sdtContentLocked). The "disabled" path also short-circuits transaction dispatch so a stuck drag cannot mutate the doc.isContentLockedMode/isSdtLockedModelive inextensions/structured-content/lockModes.jsand replace the duplicated string checks inStructuredContentViewBase.jsand the overlay.Block SDT chrome geometry
::afterpseudo, withpointer-events: noneand the 1px padding dropped — chrome no longer changes the fragment's measured size.::beforepseudo for the same reason.applyBlockSdtChromeBoundsinpainters/dom/renderer.tsmeasures the actual painted content per line (honoring alignment, indents, hanging/first-line offsets, explicit segment positioning, RTL, justified lines, list markers, and multi-fragment continuation) and exposes--sd-sdt-chrome-left/--sd-sdt-chrome-width. The::before/::afterand the block label all read those vars so the frame hugs the content.field-types.css,utils.ts) now also colors the::afterborder for default, hover, and selected states — so the field color matches the new chrome instead of leaking through to the now-invisible direct border.Selection / focus
PresentationEditor.#focusEditorAfterImageSelectionnow blurs/refocuses before scheduling a selection update, and the scheduled update runs immediately so the SDT-selected chrome paints in the same frame as the image NodeSelection.NodeSelectionis an image (or any non-structuredContentBlocknode), it walks the doc to find the enclosing block SDT and applies the selected class to that container. New PresentationEditor tests cover the image-inside-SDT and direct-SDT cases.Inline image vertical alignment
ImageRun.verticalAlignnow accepts'top' | 'bottom'; pm-adapter and the painter default to'top'so the image box stays inside the measured line height (previously'bottom'made it overflow the line).lineHeight: normal+verticalAlign: bottomon plain text runs that share a line with an inline image — runs with explicitvertAlign/baselineShiftare left alone.Diffing & versioning of inline images
layout-bridge/diff.tsnow comparesImageRunruns by all visible properties (src, size, alt, spacing, clipPath, rotation/flip/lum, hyperlink, sdt, dataAttrs, etc.) instead of treating them as opaquely equal — so resizing or relocking an inline image marks the paragraph dirty.layout-resolved/versionSignature.tsand the painter's localderiveBlockVersionnow fold the same fields into the per-block version hash via astableSerializeEvidenceValuehelper, so version-based memoization invalidates correctly.Tests
ImageResizeOverlay.test.jscovering the lock-mode/disabled path.PresentationEditor.test.tscases for image-NodeSelection-inside-block-SDT chrome.layout-bridge/diff.test.tsandlayout-resolved/versionSignature.test.tsfor the new image diff fields.painters/dom/index.test.tsandstyles.test.tscases covering chrome bounds, justified/indented/RTL/continuation cases, and the new pseudo-element styling.