feat: Drop Guide (compass) drag-drop overlay#1383
Merged
Merged
Conversation
Add a DropGuideModule that shows a VS Code-style "compass" while dragging over a
group: a cross of cells painted over the group, with the dragged item snapping
to whichever cell the cursor is over instead of the cursor-quadrant of the
target. Drop resolution + commit stay in core — the module installs a
`PositionResolver` (cell hit-test) at the merged drop-target seam and paints the
widget; the existing overlay renders the aimed cell's preview.
- core: `dndGuide` option (`boolean | { zones }`), `IDropGuideHost` /
`IDropGuideService` contracts + service slot, a composed
`getDropPositionResolver()` (app `dropPositionResolver` option ?? the module's
compass resolver) threaded into the group content drop targets, and the
`.dv-drop-guide*` theme styles. Off by default ⇒ cursor-quadrant unchanged.
- module (`dependsOn: AdvancedDnDModule`): CompassResolver (centred-cross
hit-test, gated by accepted zones ∩ `dndGuide.zones`), CompassWidget (cross
painted on the hovered group's content), lifecycle via `onWillShowOverlay` +
drag-end teardown.
Phase 1 = inner cells (split/merge this group) on the content target. Deferred:
outer cells → whole-layout-edge dock, per-cell veto gating, theming polish.
Tests: 7 unit (hit-test, zone gating, disabled-state, widget lifecycle) + a
component integration test + a real-drag e2e (compass appears, centre-cell drop
merges the groups). core+modules 1220 green; e2e 9 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the position resolver's `edge` flag behavior-bearing so an "outer" cell can dock against the whole layout from over a group, and add the compass's outer ring on top of it. - core (generic seam capability): the drop targets surface the resolver's `edge` flag on their overlay/drop events (both DnD backends); an edge cell renders no group overlay (it isn't a group split), and an `edge`-flagged content drop routes to a new `dockToLayoutEdge` (factored from the root-edge drop: `orthogonalize` + `moveGroupOrPanel` to the layout edge). Default (no resolver / no edge) is byte-for-byte unchanged. - module: the compass gains an outer ring of directional cells that return `edge:true`; `dndGuide.edges` (default on) toggles them; outer cells read visually distinct (`.dv-drop-guide-cell-edge`). Deferred to polish: the separate root-edge *preview* overlay while aiming an outer cell (the cell highlight is the affordance for now), and per-cell veto gating. Tests: core edge-cell unit test (reports edge, renders no overlay) + module outer-cell/`edges` tests; e2e drops on an outer cell and asserts a layout-edge dock (stays 2 groups, vs the centre-cell merge to 1). core+modules 1223 green; e2e 10 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- outer-cell edge preview: aiming an outer cell now paints a band over the whole-layout edge it would dock against. Module-owned (drawn into the layout element via a new `getLayoutElement` host method) rather than reusing the root drop target's overlay — the root target clears its shared anchor every pointer frame while the cursor is over a group, so it can't be borrowed for a preview. - per-cell gating: the compass only paints cells whose drop is actually allowed, via a new `canDropOnGroup` host method that mirrors the content drop target's `canDisplayOverlay` (same-component drag always allowed; otherwise the per-position veto). The `edge` flag is surfaced on `DockviewWillShowOverlayLocationEvent` so the module knows an outer cell is aimed. - demo: enable `dndGuide` in the dockview demo so the compass is tryable. Tests: module gating + edge-preview lifecycle units; e2e asserts the edge band appears while aiming an outer cell. core+modules 1225 green; e2e 10 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- share the content-overlay predicate: extract `canDisplayContentOverlay` onto the group model as the single source of truth, used by both the content drop target (content.ts) and the compass cell gating (`canDropOnGroup`). Removes the duplicated locked/shift/same-component/veto logic that could drift. - align edge-cell gating with how edge cells commit: the drop target routes an edge drop to the layout edge only after the group's content veto passes, so the compass now gates outer cells by the same veto (was unconditional) — no more painting an outer cell that does nothing on drop. - strengthen the outer-cell e2e: assert the dragged panel actually relocated to the layout edge (its tab is now right of the sibling), not just that the group count stayed 2 — a silent dock no-op would have passed before. - hoist the per-frame edge-preview inset map to module scope; note the band is a half-size approximation of the real dock. core+modules 1225 green; e2e 10 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… frame The compass cells drifted from where a drop actually resolved — worst toward the edges. The content drop target measures its quadrants against the `dndPanelOverlay` outline (the whole group, including the tab header, when `dndPanelOverlay: 'group'` — which every built-in theme sets), but the compass widget measured only the content container. Different origin and size, so the painted cross and the hit-test diverged. Expose that outline via a new `getDropOverlayElement` host method and paint the cells in the outline's frame, translated into the widget's own box (a no-op when the two coincide, e.g. `dndPanelOverlay: 'content'` or a themeless component). Now the cell you see is the cell you drop on. core+modules 1226 green (added a translation unit test); e2e 10 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…et parent The whole-layout-edge band was sized with `50%` insets and appended to the grid element — which is `position: static`, so the percentages resolved against a larger positioned ancestor (the dockview root, offset by any titlebar). The band escaped the grid box and that overflow shifted the content, so the compass appeared to jump the moment the edge overlay showed. Position the band with explicit pixels computed from the layout element's rect relative to the band's offset parent (the same translation the compass cells use), so it stays inside the layout box and never reflows the content. core+modules 1226 green; e2e 10 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hovering an outer (edge) cell painted a half-layout band at z-index 1001 in a subtree above the compass, covering half the cross — which read as the compass "shifting" the moment the band appeared. The band was a deferred nice-to-have and has been a repeated source of layering/positioning trouble for no functional gain. Drop it: the outer cells still dock to the whole-layout edge (the core `edge`-flag routing is untouched), and stay visually distinct via the dashed edge-cell style. Removes the `getLayoutElement` host method, `_showEdgePreview` machinery, and the `.dv-drop-guide-edge-preview` rule. core+modules 1225 green; e2e 10 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verified the real cause with a Playwright probe: hovering an outer cell moved
the compass element's own box from {y:35,h:685} (the content container) to
{y:0,h:720} (a higher ancestor) — it re-anchored, the cells stayed put relative
to it, so the whole cross jumped.
Why: the compass is `position: absolute; inset: 0`, so it needs a positioned
ancestor. The only one it had was the element the drop target marks with
`.dv-drop-target` (which carries `position: relative`) — and that class is added
for inner-cell overlays but removed by the edge-cell `removeDropTarget()`. So on
an outer cell the ancestor went static and the compass re-anchored.
Fix: the widget pins `position: relative` on its own mount container for its
lifetime (restored on dispose), giving the cross a stable containing block
independent of the drop target's transient class. Measured delta after the fix:
{dx:0, dy:0}. New e2e asserts the cross holds position inner -> outer.
core+modules 1225 green; e2e 11 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The drop target draws its overlay only for inner cells; outer (edge) cells skip it, and with the edge-preview band gone they had no hover feedback at all. Light up whichever cell the cursor is over instead: the per-drag-over event already carries position + edge, so the widget toggles a `.dv-drop-guide-cell-active` class on the matching cell (edge flag disambiguates the inner vs outer cell of a shared direction). Themeable via `--dv-drop-guide-active-cell-color`. core+modules 1226 green; e2e 11 green (asserts the outer cell lights up when aimed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Visual Studio always shades the region a guide would dock into; our inner cells already do (the drop target's own overlay), but outer cells had no landing preview. Restore it: aiming an outer cell now shades the half of the layout the panel will occupy, styled with the same `--dv-drag-over-*` theme variables as a real drop overlay so it reads identically, drawn beneath the compass cross. This is the band removed earlier — but that removal was chasing the wrong cause (the "jump" was the compass re-anchoring on the `.dv-drop-target` class removal, fixed separately by pinning the containing block). With that fixed, the preview is safe: verified the cross stays put (dx/dy 0) and the band covers the correct half over the positioned layout root. Not driven through the root drop target directly: it clears its shared anchor every pointer frame while the cursor is mid-layout, so a module-owned element with shared styling is the clean equivalent. core+modules 1227 green; e2e 11 green (asserts the region previews on an outer aim). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review follow-ups: - if the compass can't mount (no `.dv-content-container`), bail before driving the active-cell highlight or the edge preview, so a stray band can't show with no compass behind it. - strengthen the "compass holds position" e2e: assert an outer (edge) cell is actually active at the probe point, so the regression (the `.dv-drop-target` removal that once re-anchored the cross) is genuinely exercised, not silently skipped if the geometry shifts. core+modules 1228 green; e2e 11 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The two real findings from the review: - Stale feedback (#2): the active-cell highlight + edge preview are set from `onWillShowOverlay`, which only fires while the cursor is over a cell — so they lingered when you moved into a dead zone (the cross is small relative to the group, so most of it is dead zone). Add a move listener that clears the feedback when the resolver reports no cell. It only ever clears (setting stays with `onWillShowOverlay`), so the two never race. Verified: band/highlight show on an outer cell, clear off the cross, return on the cell. - onUnhandledDragOver fan-out (#1): cell gating called `canDropOnGroup` once per cell (9), and a cross-component drag fires `onUnhandledDragOver` from it. The inner and outer cell of a direction share a position, so cache per direction — at most one veto query per position (9 -> <=5). core+modules 1229 green; e2e 12 green (dead-zone clear + once-per-direction veto). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…compass # Conflicts: # e2e/fixtures/index.html # packages/dockview-core/src/dockview/dockviewComponent.ts # packages/dockview-core/src/dockview/moduleContracts.ts # packages/dockview-core/src/dockview/modules.ts # packages/dockview-modules/src/index.ts
…eat/drop-guide-compass # Conflicts: # e2e/fixtures/index.html # packages/dockview-core/src/dockview/dockviewComponent.ts # packages/dockview-core/src/dockview/moduleContracts.ts # packages/dockview-core/src/dockview/modules.ts # packages/dockview-modules/src/index.ts # packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx
|
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.



Drop Guide ("compass")
A VS Code / Visual Studio–style aim-at-a-cell drop overlay for group docking. While a panel or group is dragged over a group, a cross of cells is painted over it and the drop snaps to whichever cell the cursor is on — instead of the cursor-quadrant of the target.
Opt-in via a single option; off by default, so existing drag behaviour is byte-for-byte unchanged.
Behaviour
centertabs into the hovered group;top/bottom/left/rightsplit it. These reuse the drop target's own overlay as the landing preview.top/bottom/left/rightdock against the whole layout edge, with a translucent landing-region preview styled from the existing--dv-drag-over-*theme variables (reads identically to any other drop preview).Options
dndGuide: boolean | { zones?: Position[]; edges?: boolean }— restrict the inner cells (zones) or hide the outer ring (edges: false).--dv-drop-guide-color,--dv-drop-guide-cell-color,--dv-drop-guide-edge-cell-color,--dv-drop-guide-active-cell-color.Core seam
The feature is a module (
DropGuideModule, depends onAdvancedDnDModule); the only core additions are reusable, neutral primitives:PositionResolverat the drop-target seam (dropPositionResolver/ lazy getter) — both DnD backends consult it; unset ⇒ the default quadrant logic, unchanged.edgeflag on the resolver result that core routes to a whole-layout-edge dock (factored intodockToLayoutEdge, shared with the existing root-edge drop).canDisplayContentOverlaypredicate on the group model (the content drop target and the compass gating now use one source of truth).Tests
edgeflag plumbing in both DnD backends.All green (core+modules unit + e2e). Enabled in the dockview demo.
🤖 Generated with Claude Code