Drag-and-Drop Patterns
The application uses drag-and-drop interactions across multiple packages and contexts. This page documents the current implementation landscape, completed migrations, pending work, and standardization guidance.
Overview
Drag-and-drop functionality spans:
- Workspace layout — pane resizing and tab reordering (custom implementation)
- Tasks plugin — Fizzy board card moves, column reordering (Bases integration)
- Bases plugin — table column reordering, sort/group popover reordering (
@dnd-kit/svelte) - CSV plugin — table preview row/column reordering (
@dnd-kit/svelte) - File explorer — file/folder drag-and-drop tree operations
- Canvas plugin — node/edge dragging in visual editor
The codebase now uses @dnd-kit/svelte for first-party generic reorder interactions, while workspace layout, file explorer, and canvas surfaces keep their package-specific drag systems.
Migration Status
Completed Migrations to @dnd-kit/svelte ✅
Tasks Plugin — Fizzy Board
- Location:
packages/plugins/plugin-tasks/src/bases/fizzy/ - Components:
board.svelte,card.svelte,column.svelte - Package:
@dnd-kit/svelte: ^0.4.0 - Capability: Drag cards across status columns; drop mutates note frontmatter
- Status: Production-ready; E2E tested
Shared UI — Table Row/Column Reorder
- Location:
packages/ui/src/lib/components/ui/table-dnd/ - Exports:
@lapis-notes/ui/table-dnd,@lapis-notes/ui/table-dnd/sensors,@lapis-notes/ui/table-dnd/utils - Package:
@dnd-kit/dom,@dnd-kit/svelte: ^0.4.0 - Capability:
tableReorderSensors(5px pointer distance),TableDragGrip(attachHandle),dropIndicatorClasses,parseTableDragData,resolveTableDragTargetIndex; callers pass a namespace prefix (csv,markdown) for isolatedtype/acceptstrings - Status: Production-ready; consumed by CSV and GFM markdown live-preview tables
CSV Plugin — Table Preview Row/Column Reorder
- Location:
packages/plugins/plugin-csv/src/lib/components/ - Components:
csv-data-table.svelte,csv-data-table-column-head.svelte,csv-data-table-data-cell.svelte - Shared helpers:
@lapis-notes/ui/table-dnd - Package:
@dnd-kit/svelte: ^0.4.0 - Capability: Drag row/column grips to reorder CSV preview rows and columns; grip-only activation via
attachHandle; accent drop-indicator borders during drag - Status: Production-ready in CSV table preview
Bases Plugin — Sort Popover
- Location:
packages/plugins/plugin-bases/src/bases-view/components/sort/ - Components:
sort-root.svelte,sort-row.svelte - Package:
@dnd-kit/svelte: ^0.4.0 - Capability: Drag-reorder sort clauses in the toolbar Sort menu
- Status: Production-ready; used for groupBy/sort configuration
- Helpers Used:
arrayMovefrom@dnd-kit/helpers
Bases Plugin — Table Column Reordering
- Location:
packages/plugins/plugin-bases/src/bases-view/table-view/ - Files:
table-view.svelte,table-view/components/table.svelte,table-view/components/div-table.svelte,table-view/components/table-header.svelte,table-view-placeholder.svelte,table-view/components/table-placeholder.svelte - Package:
@dnd-kit/svelte: ^0.4.0 - Capability: Drag to reorder columns horizontally; persists column order in
.basedocument - Status: Production-ready; package-checked after migrating off the legacy namespace
Markdown Plugin — Standard (GFM) Tables Row/Column Reorder
- Location:
packages/plugins/plugin-markdown/src/lib/codemirror-extensions/table/ - Components:
editor-table.svelte,markdown-table-column-head.svelte,markdown-table-row-gutter-cell.svelte,markdown-table-data-cell.svelte,markdown-table-dnd.ts - Shared helpers:
@lapis-notes/ui/table-dnd(same dnd-kit wiring as CSV:tableReorderSensors, droppables on cell/head refs,onDragOver+dragOverIndex, gripattachHandle) - Package:
@dnd-kit/svelte: ^0.4.0 - Capability: Drag row/column grips in live-preview GFM pipe tables to reorder rows/columns and persist updated markdown source
- Status: Focused workspace regression tested
Markdown Plugin — Grid Tables (follow-up)
- Location:
packages/plugins/plugin-markdown/src/lib/codemirror-extensions/grid-table/ - Status: Still on package-local HTML5 / legacy drag; migrate to
@lapis-notes/ui/table-dndin a separate backlog item
Legacy/Custom Implementations
Workspace Layout
- Location:
packages/workspace/src/lib/ - Components:
tabs-drop.svelte,tabs-move.svelte,sidebar-*.svelte,pane-resizer - Package: Custom implementation + shared HTML5/pointer transport
- Capability: Tab reordering, tab-to-edge pane splits, sidebar tab moves, grouped sidebar tab/panel moves, drag-out floating panes, floating-pane redock, pane resizing, file drag-to-split
- Status: Mature and stable; not scheduled for migration (layout operations are workspace-specific)
- Notes:
- Workspace shell keeps visual drop indicators and animating marker lines
- Drag geometry and hover visuals still live in renderer components, but committed layout mutations now go through shared
Workspacedrop helpers (moveWorkspaceChildToTabIndex,dropWorkspaceItemOnTabs,moveLeafToSidebarGroupIndex,moveLeafToSidebarGroup) instead of mutating the model directly in Svelte DragStateresolves workspace DnD transport throughauto,pointer, andhtml5modes: default desktop mouse drags keep the existing HTML5 path, while touch and pen drags use pointer events plus registered drop targets resolved throughelementsFromPoint()- Pointer drags use a renderer ghost instead of
DataTransfer, keep the same overlay/drop lifecycle events as HTML5, and route the final committed mutation through the same sharedWorkspacehelpers - Touch drags use a long-press activation delay plus a pre-activation movement tolerance so quick flicks cancel cleanly while held drags can still reorder tabs, split panes, move sidebar tabs, and reorder grouped sidebar panels
- When a top tab, stacked tab, sidebar tab, sidebar group, or grouped sidebar panel drag ends without any registered workspace drop target, the source-side drag handler now converts that gesture into
Workspace.moveWorkspaceChildToFloating(...), creating an in-app floating pane instead of dropping the gesture on the floor - Floating panes render as shell-level
WorkspaceWindowoverlays above the docked root split, keep persisted bounds and z-order inworkspace.json, and reuse the same drop targets for redocking back into the main or sidebar layout - Renderer drop targets fire
layout-will-show-overlaybefore exposing a target, then use cancelablelayout-will-dropandlayout-did-dropevents around committed mutations; successful drops emit one finallayout-changetagged withsource: "drag-drop" - Sidebar groups reuse the same custom drag state instead of introducing a second drag framework; whole groups reorder in the sidebar strip and child leaves reorder vertically within grouped panels
- Dropping a leaf into a sidebar group moves the real
WorkspaceLeafunder thatWorkspaceSidebarGroup; dropping a grouped leaf back onto the sidebar strip detaches it from the group and restores it as a normal sidebar tab - Desktop (Electron) uses
data-desktop-drag-regionmarkers to avoid native window-drag conflicts
File Explorer
- Location:
packages/workspace/src/lib/sidebar/file-explorer - Package: Custom HTML5 drag-and-drop
- Capability: Drag files between folders, validate drop targets
- Status: Functional; not a candidate for dnd-kit (filesystem operations are workspace-specific)
Canvas Plugin
- Location:
packages/plugins/plugin-canvas/src/ - Package: Svelte Flow (not dnd-kit)
- Capability: Node and edge dragging in the visual editor
- Status: Separate drag-drop system via Svelte Flow; not in scope for dnd-kit standardization
API Differences
@dnd-kit/svelte (New, v0.4.0)
import { DragDropProvider, createDraggable, createDroppable } from "@dnd-kit/svelte";
import { arrayMove } from "@dnd-kit/helpers";
import { restrictToHorizontalAxis } from "@dnd-kit/modifiers";
// Wrap interactive area
<DragDropProvider {config}>
// Draggable item
{#each items as item (item.id)}
<div use:createDraggable={item.id}>
<!-- draggable content -->
</div>
{/each}
// Drop target
<div use:createDroppable={targetId}>
<!-- drop zone -->
</div>
</DragDropProvider>
// Reorder handler
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event;
if (over && active.id !== over.id) {
items = arrayMove(items, active.id, over.id);
}
}
@dnd-kit-svelte (Old, v0.0.11)
import {(Sortable, DndEvent, DndOptions)} from "@dnd-kit-svelte/sortable"; import
{restrictToHorizontalAxis} from "@dnd-kit-svelte/modifiers"; // Sortable wrapper
<Sortable items={columns} {modifiers} on:sort={(e) => handleSort(e.detail)}>
{#each columns as col (col.id)}
<div slot="item" let:item>
<!-- content -->
</div>
{/each}
</Sortable>
Key Differences
| Aspect | New (@dnd-kit/svelte) | Old (@dnd-kit-svelte) |
|---|---|---|
| API style | Composable hooks | Component wrapper |
| Modifiers | @dnd-kit/modifiers | @dnd-kit-svelte/modifiers |
| Utilities | @dnd-kit/helpers | @dnd-kit-svelte/utilities |
| Event handling | DragEndEvent type | Event slot bindings |
| Reordering | arrayMove() helper | Manual array manipulation |
| Provider | DragDropProvider | <Sortable> component |
The new package’s hook-based approach is more flexible and aligns better with Svelte 5 semantics.
Dependency Status
Single dnd-kit Generation In Use
plugin-bases/package.json now declares only the current stack:
{
"dependencies": {
"@dnd-kit/dom": "^0.4.0",
"@dnd-kit/helpers": "^0.4.0",
"@dnd-kit/svelte": "^0.4.0"
}
}
Other packages:
plugin-tasks:@dnd-kit/svelteplugin-markdown: New package onlyworkspace: Custom (no dnd-kit)
Recommended Path Forward
Immediate
- Document shared helper boundaries
Keep it explicit which drag helpers stay package-local versus shared across Bases, markdown, and CSV table-like surfaces.
Medium-term (Optional)
-
Audit markdown table against bases table patterns
- Ensure consistent implementation
- Consider if they should share a base component
-
Document desktop-specific drag concerns
- Electron window dragging vs. internal drag-drop
- Update workspace shell README with
-webkit-app-regionguidance
Long-term (Infrastructure)
- Workspace shell refactor (future, low priority)
- Eventually consider standardizing pane/tab reordering on dnd-kit
- Not urgent; current custom impl is stable and mature
- Would require significant rework of visual feedback (drop indicators, animations)
Known Issues & Workarounds
Electron Drag Region Conflicts
- Issue: Electron frameless window dragging (
-webkit-app-region) interferes with internal HTML5 drag-drop - Workaround: Use
data-desktop-drag-region="false"to opt interactive elements out of native drag regions - Files:
tabs-*.svelte,workspace-shell.css - Status: Resolved for tab/file drag-drop and pane resizing
sort-popover Popover Position
- Issue: When sort popover is at the bottom of the viewport, dnd-kit drag events can escape the provider bounds
- Status: Tested; no issues observed with current viewport constraints
- Monitoring: Watch for issues if sort row count significantly increases
Related Docs
- Bases Plugin — Sort popover implementation details
- Tasks Plugin — Fizzy board drag-and-drop
- Workspace App — Tab/pane reordering
- Desktop Electron — Window drag region configuration