Internal SDE Drag & Drop over Pointer Events — no native HTML5 API.
Module dnd | Source packages/front/fw/src/dom/query/dnd.js | Deps none | Worker-safe no
Implements drag & drop between SDE windows, lists, and tabs entirely via Pointer Events (W3C) with setPointerCapture. Does not use the HTML5 DnD API (incompatible with sanity/Pointer Events). The preview is positioned as fixed with pointer-events: none and disappears with a short configurable animation.
Out of scope: file drag from the OS (use fsAccess + pickFile).
Resolve
const dnd = runtime.resolve('dnd');
// Returns: { draggable, dropTarget, active }
API
| Method | Signature | Returns |
|---|---|---|
draggable |
(el: Element, opts: DraggableOpts) => DraggableHandle |
Drag controls |
dropTarget |
(el: Element, opts: DropTargetOpts) => DropHandle |
Drop zone control |
active |
() => ActiveDrag | null |
Current drag context |
dnd.draggable(el, opts)
Makes el draggable via Pointer Events. The drag only starts after the pointer has moved threshold px (avoids accidental drags on a click).
DraggableOpts options
| Option | Type | Default | Description |
|---|---|---|---|
data |
() => any |
— | Required. Serialisable drag data, called at start. |
preview |
(data) => HTMLElement |
clone of el at opacity 0.6 |
Visual element following the pointer. |
handle |
string | Element |
el |
Selector or element — drag only starts from this sub-element. |
axis |
'x' | 'y' | 'both' |
'both' |
Axis constraint. |
threshold |
number (px) |
5 |
Distance before the drag is recognised. |
cancelMs |
number (ms) |
150 |
Duration of the preview fade-out animation. |
onStart |
Function |
— | ({ data, x, y }) => void — at start. |
onMove |
Function |
— | ({ data, x, y }) => void — on each pointermove. |
onEnd |
Function |
— | ({ data, x, y, cancelled: boolean }) => void — on pointerup or cancel. |
onError |
Function |
— | (err) => void — if opts.data() throws (drag silently cancelled). |
Returns { disable, enable, dispose }.
dnd.dropTarget(el, opts)
Registers el as a drop zone.
DropTargetOpts options
| Option | Type | Default | Description |
|---|---|---|---|
accept |
(data) => boolean |
— | Required. Filters accepted drags. |
onDrop |
(data, { x, y, target }) => void |
— | Required. Called on pointerup if the zone accepts. |
hoverClass |
string |
— | CSS class added to el while an accepted drag hovers over it. |
onEnter |
Function |
— | (data) => void — when the drag enters the zone. |
onOver |
Function |
— | (data, { x, y }) => void — while hovering. |
onLeave |
Function |
— | (data) => void — when the drag leaves the zone. |
Returns { dispose }.
dnd.active()
Returns null if no drag is in progress, otherwise:
{ data: any, preview: HTMLElement, source: Element, x: number, y: number }
Examples
Moving a list item
const dnd = runtime.resolve('dnd');
const item = document.querySelector('.list-item');
const bin = document.querySelector('.drop-zone');
dnd.draggable(item, {
data: () => ({ id: item.dataset.id }),
onStart: ({ data }) => console.log('drag start', data),
});
dnd.dropTarget(bin, {
accept: (d) => typeof d.id === 'string',
onDrop: (d, { x, y }) => console.log('dropped', d.id, 'at', x, y),
hoverClass: 'drop-zone--active',
});
Custom preview
dnd.draggable(card, {
data: () => ({ title: card.textContent }),
preview: (data) => {
const el = document.createElement('div');
el.className = 'drag-ghost';
el.textContent = data.title;
return el;
},
});
Drag constrained to the Y axis (reorder a column)
dnd.draggable(row, { data: () => ({ row: row.dataset.index }), axis: 'y' });
Notes
- Only one drag is active at a time (module-scope state). A
pointerdownduring an active drag is ignored. pointercancelis treated as a clean cancellation:onDropdoes not fire,onEndis called withcancelled: true. Required for touch pointer loss (OS gesture, navigation, pointer-lock loss).dispose()during a drag: full state reset +onEnd({ cancelled: true }).opts.data()is wrapped in try/catch — an exception silently cancels the drag and invokesopts.onErrorif provided.dnd:startanddnd:endare emitted via nativedispatchEventondocumentto allow observability without a dependency oneventBus.- Hovered zone detection uses
document.elementsFromPoint; in the absence of this API (test environment without a full DOM), no drop target fires. pointer-events: noneon the preview prevents interference withelementsFromPoint.