Skip to content

fix(core): sash and scrollbar cannot be dragged inside a popout window#1478

Merged
mathuo merged 2 commits into
mathuo:masterfrom
lobobeebe:fix/popout-sash-owner-document
Jul 16, 2026
Merged

fix(core): sash and scrollbar cannot be dragged inside a popout window#1478
mathuo merged 2 commits into
mathuo:masterfrom
lobobeebe:fix/popout-sash-owner-document

Conversation

@lobobeebe

@lobobeebe lobobeebe commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes #1477.

Inside a popout window (api.addPopoutGroup), a splitview sash cannot be dragged — the drag arms and then nothing moves, so two groups docked side by side in a popout are stuck at their initial ratio. Scrollbar has the identical defect, so the tab-bar overflow scrollbar can't be dragged there either.

Cause. Splitview registers the sash's pointerdown on the sash element, but registers the follow-up listeners on the global document (splitview.ts ~L563):

document.addEventListener('pointermove', onPointerMove);
document.addEventListener('pointerup', end);
document.addEventListener('pointercancel', end);
document.addEventListener('contextmenu', end);

A popout window has its own document. addPopoutGroup moves the group's element into it (popoutContainer.appendChild(...), which adopts the node), so a sash living there dispatches pointer events into the popout's document, never the opener's. pointerdown still fires (it's on the element, which travelled with it), so the drag arms — but pointermove/pointerup never arrive, resize() is never called and end() never runs. scrollbar.ts (~L100) repeats the pattern.

Why it's easy to miss: the symptom discriminates purely on document, and fails silently — no error, no console output.

placement element's document sash drag
grid main window's works
floating group main window's works
popout group the popout window's dead

Floating groups render into the main document, so the global document happens to be right for them. Only popout groups cross a document boundary.

Fix. Resolve the owning document from the element — sash.ownerDocument / this.element.ownerDocument. It's captured once per drag into a const closed over by both the addEventListener and its matching removeEventListener, so a listener can't be added on one document and removed from another (strictly no worse than today, which uses the same global for both). ownerDocument is correct at pointerdown: cross-document appendChild adopts the node, so a sash inside the popout container already carries that window's document by the time it can receive a pointer event.

Reproduction

  1. api.addPopoutGroup(group) to open a group in its own window.
  2. Drag a second panel into that window and drop it to the side, so the popout hosts two groups with a sash between them.
  3. Try to drag the sash — it doesn't move. The same steps in a floating group work.

Found against 7.0.2 (latest published) and reproduced in current master.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Refactor / cleanup
  • Build / CI / tooling

Affected packages

  • dockview-core
  • dockview (vanilla JS)
  • dockview-react
  • dockview-vue
  • dockview-angular
  • docs

Notes

  • Kept deliberately minimal. The same ownerDocument resolution is duplicated across the two call sites rather than factored out. I have a variant that adds an addPointerDragListeners(element, { onPointerMove, onEnd, endOnContextMenu }) helper to dom.ts (where getDockviewTheme / isInDocument already live): it owns the listener lifecycle and auto-removes on end, so both call sites lose their add/remove bookkeeping (net +83/−29 across three files, with the call sites shrinking), and it makes the invariant structural — the document is resolved once per drag, so the add and the matching remove can't target different documents. Happy to push that instead if you'd prefer it; I led with the minimal fix so the bug fix isn't blocked on a refactor opinion.
  • overlay.ts still binds pointermove/pointerup to the global window for floating-group move/resize. Not a bug today (floating groups live in the main window), but it's the same class and would bite if a floating group inside a popout window ever became reachable — left untouched here.

Test

splitview.spec.ts gains dnd: sash drag follows pointer events in the element's own document — it builds a Splitview inside a second document (document.implementation.createHTMLDocument()), which is the shape a popout window has, and drives a real sash drag from that document.

It fails on master (the views never resize — [200, 200] instead of [220, 180], because the pointermove never reaches the listener bound to the global document) and passes with the fix.

The existing dnd: pointer events to move sash test dispatches on the global document, where sash.ownerDocument === document, so it passes either way and cannot catch this — which is why the bug survived.

No real second window is needed: the bug is about which document the listeners land on, and jsdom's second Document reproduces that exactly.

dockview-core: 1079 tests / 61 suites pass, and the change is prettier-clean.

…s document

Splitview registers a sash's pointerdown on the sash element, but registers the
follow-up pointermove/pointerup/pointercancel/contextmenu listeners on the
global document. An element inside a popout window dispatches its pointer events
into that window's document, so those listeners never fire: the drag arms on
pointerdown and then nothing moves, and the sash cannot be dragged. Scrollbar
repeats the pattern, so the tab-bar overflow scrollbar is affected too.

Floating groups render into the main document, which is why the global document
happens to be correct for them and only popout groups break.

Resolve the owning document from the element instead. It is captured once per
drag into a const closed over by both the add and the matching remove, so a
listener can never be added on one document and removed from another.
Drives a sash drag on a Splitview built inside a second document
(document.implementation.createHTMLDocument), which is the shape a popout window
has: the element lives in that window's document, so its pointer events are
dispatched there.

Fails before the ownerDocument fix — the pointermove never reaches the listener
bound to the global document, so the views never resize.

The existing sash-drag test dispatches on the global document, where
sash.ownerDocument === document, so it passes either way and cannot catch this.
@mathuo
mathuo self-requested a review July 16, 2026 18:58
@mathuo
mathuo merged commit 0006ab0 into mathuo:master Jul 16, 2026
7 checks passed
mathuo added a commit that referenced this pull request Jul 16, 2026
Merge master into v8-branch (reconcile popout sash/scrollbar fix + add PR #1478 regression test)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sashes cannot be dragged inside a popout window (splitview + scrollbar bind pointer events to the global document)

2 participants