fix(editor): selection highlight flickers when dragging across mark boundaries (SD-2024)#2205
Conversation
…oundaries (SD-2024) DomPositionIndex.findEntriesInRange used half-open [start, end) semantics. When a selection range fell exactly on a run boundary (the 2-position gap between adjacent text spans with different marks), neither adjacent entry matched — producing zero DOM rects and clearing the selection overlay for one frame before the next pointer event restored it. Add a `boundaryInclusive` option that switches to closed [start, end] semantics, and use it in DomSelectionGeometry so entries touching the boundary are always found. Also add a safety net in PresentationEditor that preserves the last overlay when a non-empty selection yields no rects.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 389e1203b1
ℹ️ 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".
packages/super-editor/src/core/presentation-editor/PresentationEditor.ts
Outdated
Show resolved
Hide resolved
…ies (SD-2024) Add two computeSelectionRectsFromDom tests that exercise the exact edge case: selection range falls on the structural gap between two differently-marked runs, verifying the boundaryInclusive path produces non-empty rects.
caio-pizzol
left a comment
There was a problem hiding this comment.
@tupizz boundaryInclusive fix looks solid — right root cause, clean API extension, good tests. left two inline comments: a stale overlay edge case worth a debug log, and a JSDoc that needs reverting to "exclusive". nothing blocking.
packages/super-editor/src/core/presentation-editor/PresentationEditor.ts
Outdated
Show resolved
Hide resolved
packages/super-editor/src/core/presentation-editor/dom/DomPositionIndex.ts
Outdated
Show resolved
Hide resolved
…ection-flicker-across-marks
…-rect path - Revert findElementsInRange JSDoc @param to back to "exclusive" (method does not pass boundaryInclusive) - Add debugLog warn when zero-rect early return fires so stale overlay cases are diagnosable
caio-pizzol
left a comment
There was a problem hiding this comment.
@tupizz the debugLog and the JSDoc fix look good. one thing still open — left an inline comment on the zero-rect guard.
packages/super-editor/src/core/presentation-editor/PresentationEditor.ts
Show resolved
Hide resolved
Narrows the safety net to isDragging so non-drag zero-rect cases (virtualized/disconnected DOM) still clear the overlay correctly.
…ction Refactor clickToPosition to utilize DOM-based page detection via elementsFromPoint, enhancing accuracy in multi-page layouts. This change ensures that containerPoint is treated as page-relative when applicable, addressing issues with virtualization and layout gaps. Additionally, clamp head position in EditorInputManager to prevent selection from entering isolating nodes like tables during drag operations.
…ping (SD-2024) Fix backward drag clamping to resolve to the table boundary instead of an inner cell boundary. Also add behavioral tests for DOM-based page coordinate resolution and selection clamping at isolating nodes.
caio-pizzol
left a comment
There was a problem hiding this comment.
@tupizz all three items from the previous rounds are addressed — the isDragging guard, the JSDoc fix, and the debugLog. the new clickToPosition refactor and isolating-boundary clamping look solid, good test coverage across the board. lgtm, approving.
|
one thing worth adding as a follow-up: a behavior test that reproduces the actual user interaction. something like: 1. load a doc with mixed inline formatting (bold → italic) adjacent to a table |
|
🎉 This PR is included in superdoc-cli v0.2.0-next.37 The release is available on GitHub release |
|
🎉 This PR is included in superdoc v1.17.0-next.42 The release is available on GitHub release |
# [1.17.0](v1.16.0...v1.17.0) (2026-02-28) ### Bug Fixes * active track change ([#2163](#2163)) ([108c14d](108c14d)) * add currentTotalPages getter and pagination-update event ([#2202](#2202)) ([95b4579](95b4579)), closes [#958](#958) * always call resolveComment after custom TC bubble handlers (SD-2049) ([#2204](#2204)) ([34fb4e0](34fb4e0)) * backward replace insert text ([#2172](#2172)) ([66f0849](66f0849)) * **collaboration:** deduplicate updateYdocDocxData during replaceFile (SD-1920) ([#2162](#2162)) ([52962fc](52962fc)) * **comments:** cross-page collision avoidance for floating comment bubbles (SD-1998) ([#2180](#2180)) ([6cfbeca](6cfbeca)) * **comments:** remove synchronous dispatch from plugin apply() (SD-1940) ([#2157](#2157)) ([887175b](887175b)) * **css:** scope ProseMirror CSS to prevent bleeding into host apps (SD-1850) ([#2134](#2134)) ([b9d98fa](b9d98fa)) * document-api improvements, plan mode, query.match, mutations ([6221580](6221580)) * **document-api:** delete table cell fix ([#2209](#2209)) ([5e5c43f](5e5c43f)) * **document-api:** distribute columns command fixes ([#2207](#2207)) ([8f4eaf7](8f4eaf7)) * **document-api:** fix cell shading in document api ([#2215](#2215)) ([456f60e](456f60e)) * **document-api:** insert table cell ([#2210](#2210)) ([357ee90](357ee90)) * **document-api:** plan-engine reliability fixes and error diagnostics ([#2185](#2185)) ([abfd81b](abfd81b)) * **document-api:** split table cell command ([#2217](#2217)) ([0b3e2b4](0b3e2b4)) * **document-api:** split table command ([#2214](#2214)) ([ec31699](ec31699)) * **editor:** render styles applied inside SDT fields (SD-2011) ([#2188](#2188)) ([9c34be3](9c34be3)) * **editor:** selection highlight flickers when dragging across mark boundaries (SD-2024) ([#2205](#2205)) ([ba03e76](ba03e76)) * extract duplicate block identity normalization from docxImporter ([7f7ff93](7f7ff93)) * improve backspace behavior near run boundaries for tracked changes ([#2175](#2175)) ([6c9c7a3](6c9c7a3)) * **layout:** per-section footer constraints for multi-section docs (SD-1837) ([#2022](#2022)) ([e11acc5](e11acc5)) * normalize review namespace into trackChanges, harden input validation ([33e907b](33e907b)) * outside click for toolbar dropdown ([#2174](#2174)) ([5f859c7](5f859c7)) * preserve line spacing and indentation on Google Docs paste ([#2183](#2183)) ([b9a7357](b9a7357)), closes [#2151](#2151) * **shapes:** render grouped DrawingML shapes with custom geometry (SD-1877) ❇️ ([#2105](#2105)) ([14985a5](14985a5)) * support cell spacing ([#1879](#1879)) ([1639967](1639967)) * **tables:** expand auto-width tables to fill available page width ([#2109](#2109)) ([15f36bc](15f36bc)) * text highlight on export ([#2189](#2189)) ([9cbd022](9cbd022)) * track highlight changes ([#2192](#2192)) ([e164625](e164625)) * undo/redo actions ([#2161](#2161)) ([495e92f](495e92f)) ### Features * allow custom accept/reject handlers for TC bubbles ([#1921](#1921)) ([e30abf6](e30abf6)) * **document-api:** add format operations font size alignment color font family ([#2179](#2179)) ([f19c688](f19c688)) * **document-api:** add plan-based mutation engine with query.match and style capture ([#2160](#2160)) ([365293a](365293a)) * **document-api:** doc default initial styles ([#2184](#2184)) ([f25e41f](f25e41f)) * **document-api:** include anchored text in comments list response ([#2177](#2177)) ([b3a2912](b3a2912)) * **document-api:** inline formatting parity core end-to-end ([#2197](#2197)) ([b405b03](b405b03)) * **document-api:** inline formatting rpr parity ([#2198](#2198)) ([41ab771](41ab771)) * **document-api:** section commands ([#2199](#2199)) ([ec4abe3](ec4abe3)) * **document-api:** support deleting entire block nodes not only text ([#2181](#2181)) ([2897246](2897246)) * **document-api:** table of contents commands ([#2200](#2200)) ([baa72c4](baa72c4)) * **document-api:** tables namespace and commands ([#2182](#2182)) ([b80ee31](b80ee31)) * **markdown:** add markdown override to sdk, improve conversion ([#2196](#2196)) ([04a1c71](04a1c71)) * preserve w:view setting through DOCX round-trip ([#2190](#2190)) ([48b4210](48b4210)), closes [#2070](#2070) * **track-changes:** clear comment bubbles when bulk accept or reject TCs ([#2159](#2159)) ([27fbe8e](27fbe8e)) ### Performance Improvements * **comments:** batch tracked change comment creation on load ([#2166](#2166)) ([0c2eca5](0c2eca5)) * **comments:** batch tracked change creation and virtualize floating bubbles (SD-1997) ([#2168](#2168)) ([70fd7d9](70fd7d9))
# [1.17.0](v1.16.0...v1.17.0) (2026-03-03) ### Bug Fixes * active track change ([#2163](#2163)) ([108c14d](108c14d)) * add currentTotalPages getter and pagination-update event ([#2202](#2202)) ([95b4579](95b4579)), closes [#958](#958) * always call resolveComment after custom TC bubble handlers (SD-2049) ([#2204](#2204)) ([34fb4e0](34fb4e0)) * backward replace insert text ([#2172](#2172)) ([66f0849](66f0849)) * before paragraph spacing inside table cells ([#1842](#1842)) ([c7efa85](c7efa85)) * **collaboration:** deduplicate updateYdocDocxData during replaceFile (SD-1920) ([#2162](#2162)) ([52962fc](52962fc)) * **comments:** cross-page collision avoidance for floating comment bubbles (SD-1998) ([#2180](#2180)) ([6cfbeca](6cfbeca)) * **comments:** emit empty comment positions so undo clears orphan bubbles ([#2235](#2235)) ([12ba727](12ba727)) * **comments:** improve multiline comment input styling ([#2242](#2242)) ([e6a0dab](e6a0dab)) * **comments:** prevent comment mark from extending to adjacent typed text ([#2241](#2241)) ([07fecd8](07fecd8)) * **comments:** reduce sidebar jitter when clicking comments (SD-2034) ([#2250](#2250)) ([c3568d2](c3568d2)) * **comments:** remove synchronous dispatch from plugin apply() (SD-1940) ([#2157](#2157)) ([887175b](887175b)) * **css:** scope ProseMirror CSS to prevent bleeding into host apps (SD-1850) ([#2134](#2134)) ([b9d98fa](b9d98fa)) * document-api improvements, plan mode, query.match, mutations ([6221580](6221580)) * **document-api:** delete table cell fix ([#2209](#2209)) ([5e5c43f](5e5c43f)) * **document-api:** distribute columns command fixes ([#2207](#2207)) ([8f4eaf7](8f4eaf7)) * **document-api:** fix cell shading in document api ([#2215](#2215)) ([456f60e](456f60e)) * **document-api:** insert table cell ([#2210](#2210)) ([357ee90](357ee90)) * **document-api:** plan-engine reliability fixes and error diagnostics ([#2185](#2185)) ([abfd81b](abfd81b)) * **document-api:** split table cell command ([#2217](#2217)) ([0b3e2b4](0b3e2b4)) * **document-api:** split table command ([#2214](#2214)) ([ec31699](ec31699)) * **editor:** render styles applied inside SDT fields (SD-2011) ([#2188](#2188)) ([9c34be3](9c34be3)) * **editor:** selection highlight flickers when dragging across mark boundaries (SD-2024) ([#2205](#2205)) ([ba03e76](ba03e76)) * extract duplicate block identity normalization from docxImporter ([7f7ff93](7f7ff93)) * improve backspace behavior near run boundaries for tracked changes ([#2175](#2175)) ([6c9c7a3](6c9c7a3)) * **layout:** per-section footer constraints for multi-section docs (SD-1837) ([#2022](#2022)) ([e11acc5](e11acc5)) * markdown block-separator blank lines and heading split style-mark normalization ([e988adc](e988adc)) * normalize review namespace into trackChanges, harden input validation ([33e907b](33e907b)) * outside click for toolbar dropdown ([#2174](#2174)) ([5f859c7](5f859c7)) * prefer full decoration range ([#2239](#2239)) ([ac15e31](ac15e31)), closes [#collectDesiredState](https://github.com/superdoc-dev/superdoc/issues/collectDesiredState) [#resolveEffectiveRanges](https://github.com/superdoc-dev/superdoc/issues/resolveEffectiveRanges) [#setPreviousRanges](https://github.com/superdoc-dev/superdoc/issues/setPreviousRanges) * preserve line spacing and indentation on Google Docs paste ([#2183](#2183)) ([b9a7357](b9a7357)), closes [#2151](#2151) * preserve text-align on paste from Google Docs ([#2208](#2208)) ([762231b](762231b)) * rollback comments colors / ui ([#2216](#2216)) ([a99b5ab](a99b5ab)) * **scroll:** wait for virtualized page mount and center text element ([#2221](#2221)) ([95f634e](95f634e)) * **shapes:** render grouped DrawingML shapes with custom geometry (SD-1877) ❇️ ([#2105](#2105)) ([14985a5](14985a5)) * splitting run with header adds empty row ([#2229](#2229)) ([e1965fc](e1965fc)) * **super-converter:** handle empty pic:spPr in image import ([#2254](#2254)) ([2b8dbce](2b8dbce)) * **super-editor:** backspace across run boundaries without splitting list items ([#2258](#2258)) ([27ccb64](27ccb64)) * support cell spacing ([#1879](#1879)) ([1639967](1639967)) * **tables:** defaultTableStyle support, cell fixes ([#2246](#2246)) ([74fca9c](74fca9c)) * **tables:** expand auto-width tables to fill available page width ([#2109](#2109)) ([15f36bc](15f36bc)) * **tables:** preserve TableGrid defaults and style-driven spacing/bor… ([#2230](#2230)) ([b0a482f](b0a482f)) * text highlight on export ([#2189](#2189)) ([9cbd022](9cbd022)) * track highlight changes ([#2192](#2192)) ([e164625](e164625)) * **track-changes:** correct format change description for already-formatted text (SD-2077) ([#2253](#2253)) ([b2ffc0d](b2ffc0d)) * **track-changes:** handle ReplaceAroundStep in tracked changes mode (SD-2061) ([#2225](#2225)) ([8f3cbe4](8f3cbe4)) * **track-changes:** remove ghost TrackFormat on multi-node format cancel ([#2233](#2233)) ([e925ef9](e925ef9)) * undo/redo actions ([#2161](#2161)) ([495e92f](495e92f)) * **virtualization:** correct scroll mapping and viewport sizing at non-100% zoom ([#2171](#2171)) ([84af4c0](84af4c0)), closes [#registryKey](https://github.com/superdoc-dev/superdoc/issues/registryKey) ### Features * allow custom accept/reject handlers for TC bubbles ([#1921](#1921)) ([e30abf6](e30abf6)) * **comments:** improve floating comments ui ([#2195](#2195)) ([e870cfb](e870cfb)) * **document-api:** add format operations font size alignment color font family ([#2179](#2179)) ([f19c688](f19c688)) * **document-api:** add get markdown to sdks ([e42b56d](e42b56d)) * **document-api:** add plan-based mutation engine with query.match and style capture ([#2160](#2160)) ([365293a](365293a)) * **document-api:** default table style setting ([#2248](#2248)) ([3ad4e9f](3ad4e9f)) * **document-api:** default target-less insert to document end ([#2244](#2244)) ([c717e2b](c717e2b)) * **document-api:** doc default initial styles ([#2184](#2184)) ([f25e41f](f25e41f)) * **document-api:** format.paragraph for w:pPr formatting ([#2218](#2218)) ([32c9991](32c9991)) * **document-api:** history name space ([#2219](#2219)) ([41dea37](41dea37)) * **document-api:** include anchored text in comments list response ([#2177](#2177)) ([b3a2912](b3a2912)) * **document-api:** inline formatting parity core end-to-end ([#2197](#2197)) ([b405b03](b405b03)) * **document-api:** inline formatting rpr parity ([#2198](#2198)) ([41ab771](41ab771)) * **document-api:** lists namespace ([#2223](#2223)) ([09ebfcb](09ebfcb)) * **document-api:** section commands ([#2199](#2199)) ([ec4abe3](ec4abe3)) * **document-api:** support deleting entire block nodes not only text ([#2181](#2181)) ([2897246](2897246)) * **document-api:** table of contents commands ([#2200](#2200)) ([baa72c4](baa72c4)) * **document-api:** tables namespace and commands ([#2182](#2182)) ([b80ee31](b80ee31)) * **document-api:** toc commands ([#2220](#2220)) ([767e010](767e010)) * **images:** allow drag-and-drop for images in editor ([#2227](#2227)) ([4b36780](4b36780)) * **layout-engine:** render table headers, tblLook support ([#2256](#2256)) ([db6a2ff](db6a2ff)) * **link-popover:** custom link popovers ([#2222](#2222)) ([070190f](070190f)) * **markdown:** add markdown override to sdk, improve conversion ([#2196](#2196)) ([04a1c71](04a1c71)) * preserve w:view setting through DOCX round-trip ([#2190](#2190)) ([48b4210](48b4210)), closes [#2070](#2070) * real time collab in python sdk ([#2243](#2243)) ([dc3b4fd](dc3b4fd)) * **tables:** allow resizing table rows ([#2226](#2226)) ([2c6da10](2c6da10)) * **tables:** improve cell color application (context), column dragging, table pasting ([#2228](#2228)) ([066b9eb](066b9eb)) * **table:** toggle header row sets both cell types and repeatHeader atomically ([#2245](#2245)) ([2f5899d](2f5899d)) * **track-changes:** clear comment bubbles when bulk accept or reject TCs ([#2159](#2159)) ([27fbe8e](27fbe8e)) ### Performance Improvements * **comments:** batch tracked change comment creation on load ([#2166](#2166)) ([0c2eca5](0c2eca5)) * **comments:** batch tracked change creation and virtualize floating bubbles (SD-1997) ([#2168](#2168)) ([70fd7d9](70fd7d9))
|
🎉 This PR is included in superdoc v1.17.0 The release is available on GitHub release |
Demo
CleanShot.2026-02-27.at.12.58.28.mp4
Summary
Fixes the selection highlight flickering/disappearing when drag-selecting across inline mark boundaries (e.g. bold → italic, colored → uncolored text).
Problem
When a user drags to select text that spans two adjacent runs with different marks (e.g., bold followed by italic), the blue selection overlay flickers — it briefly disappears for one frame, then reappears on the next pointer event. This makes drag selection feel broken on any document with mixed inline formatting.
How we debugged it
Reproduced in browser — loaded a complex DOCX (
SuperDoc (5).docx) with mixed marks (bold, italic, colored text, tracked changes). Drag-selected across formatting boundaries and observed the highlight flash.Instrumented selection monitoring — injected a 3ms-interval monitor on
window.editor.state.selectionto track every selection change during drag. The PM selection was stable — the flicker was purely visual, not a state issue.Traced the rendering path — followed the selection overlay pipeline:
PresentationEditor.#updateSelectionFromEditor()reads the PM selection{from, to}computeSelectionRectsFromDom()→DomPositionIndex.findEntriesInRange(from, to)to find DOM elements in the selection rangeDOMRangeobjects from those elements →range.getClientRects()→ draws overlay divsFound the gap — when the selection boundary falls exactly on a run boundary, the PM positions land in the 2-position structural gap between adjacent text spans. Example:
A selection at
{from: 5, to: 7}spans only structural tokens — no text content.Identified the root cause —
findEntriesInRangeuses half-open[start, end)semantics. For the range[5, 7):[1,5]:pmEnd (5) <= start (5)→ excluded (boundary touch)[7,12]:pmStart (7) >= end (7)→ excluded (boundary touch)Solution
Three targeted changes:
1.
DomPositionIndex.findEntriesInRange— addboundaryInclusiveoptionAdded an optional
{ boundaryInclusive: true }parameter that switches from half-open[start, end)to closed[start, end]overlap checks:This is opt-in — the default half-open behavior is preserved for all other callers.
2.
DomSelectionGeometry.computeSelectionRectsFromDom— use inclusive boundariesAll calls to
findEntriesInRangein the selection rect computation now pass{ boundaryInclusive: true }. This ensures the two adjacent text spans are both found when a selection crosses a run boundary.3.
PresentationEditor.#updateSelectionFromEditor— safety netIf a non-empty selection (
from !== to) produces zero DOM rects (edge case where even inclusive boundaries don't find entries), the method now returns early instead of clearing the overlay. This preserves the last valid highlight.Test plan
DomPositionIndex.test.ts:boundaryInclusiveincludes both adjacent entries at a run gapboundaryInclusiveincludes entry whosepmEndequals query startboundaryInclusiveincludes entry whosepmStartequals query end