Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 isolated type / accept strings
  • 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: arrayMove from @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 .base document
  • 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, grip attachHandle)
  • 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-dnd in 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 Workspace drop helpers (moveWorkspaceChildToTabIndex, dropWorkspaceItemOnTabs, moveLeafToSidebarGroupIndex, moveLeafToSidebarGroup) instead of mutating the model directly in Svelte
    • DragState resolves workspace DnD transport through auto, pointer, and html5 modes: default desktop mouse drags keep the existing HTML5 path, while touch and pen drags use pointer events plus registered drop targets resolved through elementsFromPoint()
    • 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 shared Workspace helpers
    • 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 WorkspaceWindow overlays above the docked root split, keep persisted bounds and z-order in workspace.json, and reuse the same drop targets for redocking back into the main or sidebar layout
    • Renderer drop targets fire layout-will-show-overlay before exposing a target, then use cancelable layout-will-drop and layout-did-drop events around committed mutations; successful drops emit one final layout-change tagged with source: "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 WorkspaceLeaf under that WorkspaceSidebarGroup; 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-region markers 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

AspectNew (@dnd-kit/svelte)Old (@dnd-kit-svelte)
API styleComposable hooksComponent wrapper
Modifiers@dnd-kit/modifiers@dnd-kit-svelte/modifiers
Utilities@dnd-kit/helpers@dnd-kit-svelte/utilities
Event handlingDragEndEvent typeEvent slot bindings
ReorderingarrayMove() helperManual array manipulation
ProviderDragDropProvider<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/svelte
  • plugin-markdown: New package only
  • workspace: Custom (no dnd-kit)

Immediate

  1. 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)

  1. Audit markdown table against bases table patterns

    • Ensure consistent implementation
    • Consider if they should share a base component
  2. Document desktop-specific drag concerns

    • Electron window dragging vs. internal drag-drop
    • Update workspace shell README with -webkit-app-region guidance

Long-term (Infrastructure)

  1. 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