@mk-drag-and-drop/dom 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +460 -0
- package/dist/controller/controller-internals.d.ts +5 -0
- package/dist/controller/controller-internals.js +11 -0
- package/dist/controller/create-drag-controller.d.ts +36 -0
- package/dist/controller/create-drag-controller.js +232 -0
- package/dist/draggable/create-draggable-binding.d.ts +8 -0
- package/dist/draggable/create-draggable-binding.js +52 -0
- package/dist/draggable/create-draggable.d.ts +46 -0
- package/dist/draggable/create-draggable.js +57 -0
- package/dist/droppable/create-drop-container-binding.d.ts +8 -0
- package/dist/droppable/create-drop-container-binding.js +28 -0
- package/dist/droppable/create-drop-container.d.ts +18 -0
- package/dist/droppable/create-drop-container.js +36 -0
- package/dist/droppable/create-droppable-binding.d.ts +9 -0
- package/dist/droppable/create-droppable-binding.js +29 -0
- package/dist/droppable/create-droppable.d.ts +17 -0
- package/dist/droppable/create-droppable.js +31 -0
- package/dist/geometry/measurement.d.ts +4 -0
- package/dist/geometry/measurement.js +33 -0
- package/dist/geometry/overlay.d.ts +19 -0
- package/dist/geometry/overlay.js +32 -0
- package/dist/geometry/rects.d.ts +17 -0
- package/dist/geometry/rects.js +34 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +9 -0
- package/dist/input/config.d.ts +35 -0
- package/dist/input/config.js +56 -0
- package/dist/input/create-drag-handle.d.ts +4 -0
- package/dist/input/create-drag-handle.js +4 -0
- package/dist/input/drag-handle.d.ts +10 -0
- package/dist/input/drag-handle.js +74 -0
- package/dist/input/keyboard-drag.d.ts +28 -0
- package/dist/input/keyboard-drag.js +77 -0
- package/dist/input/pointer-activation.d.ts +31 -0
- package/dist/input/pointer-activation.js +97 -0
- package/dist/integration/index.d.ts +10 -0
- package/dist/integration/index.js +6 -0
- package/dist/modifiers/built-ins.d.ts +6 -0
- package/dist/modifiers/built-ins.js +62 -0
- package/dist/modifiers/pipeline.d.ts +18 -0
- package/dist/modifiers/pipeline.js +34 -0
- package/dist/modifiers/types.d.ts +32 -0
- package/dist/modifiers/types.js +1 -0
- package/dist/runtime/drag-runtime-handle.d.ts +22 -0
- package/dist/runtime/drag-runtime-handle.js +33 -0
- package/dist/runtime/drag-runtime.d.ts +96 -0
- package/dist/runtime/drag-runtime.js +798 -0
- package/dist/runtime/drop-target-registry.d.ts +101 -0
- package/dist/runtime/drop-target-registry.js +855 -0
- package/dist/runtime/lifecycle.d.ts +44 -0
- package/dist/runtime/lifecycle.js +1 -0
- package/dist/runtime/types.d.ts +65 -0
- package/dist/runtime/types.js +1 -0
- package/dist/sortable/create-sortable-binding.d.ts +10 -0
- package/dist/sortable/create-sortable-binding.js +58 -0
- package/dist/sortable/create-sortable.d.ts +19 -0
- package/dist/sortable/create-sortable.js +55 -0
- package/dist/sortable/sortable-options.d.ts +17 -0
- package/dist/sortable/sortable-options.js +20 -0
- package/dist/sortable/sortable-preview.d.ts +79 -0
- package/dist/sortable/sortable-preview.js +357 -0
- package/dist/sortable/sortable-registry.d.ts +113 -0
- package/dist/sortable/sortable-registry.js +145 -0
- package/dist/targeting/algorithms.d.ts +6 -0
- package/dist/targeting/algorithms.js +56 -0
- package/dist/targeting/constraints.d.ts +15 -0
- package/dist/targeting/constraints.js +58 -0
- package/dist/targeting/types.d.ts +23 -0
- package/dist/targeting/types.js +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matthias Kramer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
# @mk-drag-and-drop/dom
|
|
2
|
+
|
|
3
|
+
`@mk-drag-and-drop/dom` is the DOM-first behavior package for
|
|
4
|
+
`mk-drag-and-drop`. It handles pointer and keyboard drag input, target
|
|
5
|
+
registration and measurement, targeting, lifecycle callbacks, drag overlays,
|
|
6
|
+
movement modifiers, sortable previews, and drop-container placement.
|
|
7
|
+
|
|
8
|
+
The package is headless. Apps own DOM structure, rendering, styling, data,
|
|
9
|
+
persistence, and final commits. The DOM package reports what happened; it does
|
|
10
|
+
not own application data.
|
|
11
|
+
|
|
12
|
+
## Import Paths
|
|
13
|
+
|
|
14
|
+
Use the root package import for application code:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import {
|
|
18
|
+
createDragController,
|
|
19
|
+
createDraggable,
|
|
20
|
+
createDroppable,
|
|
21
|
+
createSortable,
|
|
22
|
+
} from "@mk-drag-and-drop/dom";
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The package also exports `@mk-drag-and-drop/dom/integration` for adapter authors
|
|
26
|
+
who need lower-level DOM behavior factories and a runtime handle. Deep imports
|
|
27
|
+
into `src` or `dist` files are not part of the public package exports.
|
|
28
|
+
|
|
29
|
+
## Public Root API
|
|
30
|
+
|
|
31
|
+
The root export includes:
|
|
32
|
+
|
|
33
|
+
- `createDragController`
|
|
34
|
+
- `createDraggable`
|
|
35
|
+
- `createDroppable`
|
|
36
|
+
- `createDropContainer`
|
|
37
|
+
- `createSortable`
|
|
38
|
+
- `createDragHandle`
|
|
39
|
+
- lifecycle types: `DragStartEvent`, `DragUpdateEvent`, `DragEndEvent`,
|
|
40
|
+
`DropEvent`, `DragSource`, `DragEndResult`, `DragLifecycleCallbacks`,
|
|
41
|
+
`DragLifecycleHelpers`
|
|
42
|
+
- placement types: `SortableDropPlacement`, `RemeasureDropTargetsInput`
|
|
43
|
+
- targeting helpers and types: `pointerToCenter`, `centerToCenter`,
|
|
44
|
+
`pointerToRectDistance`, `getDistanceToRect`,
|
|
45
|
+
`maxPointerDistanceToRect`, `maxOverlayCenterDistanceToRect`, `DropTarget`,
|
|
46
|
+
`TargetingAlgorithm`, `TargetingAlgorithmInput`,
|
|
47
|
+
`TargetingConstraint`, `TargetingConstraintInput`
|
|
48
|
+
- modifiers and types: `lockToXAxis`, `lockToYAxis`, `restrictToContainer`,
|
|
49
|
+
`RestrictToContainerResolver`, `DragModifier`, `DragModifierInput`,
|
|
50
|
+
`DragModifierSetupInput`, `DragModifierTransformInput`
|
|
51
|
+
- input and geometry types: `KeyboardCommand`, `KeyboardConfiguration`,
|
|
52
|
+
`PointerConfiguration`, `DragPoint`, `DragRect`
|
|
53
|
+
|
|
54
|
+
Internal classes and helpers such as the underlying runtime class, registries,
|
|
55
|
+
input controllers, measurement helpers, sortable preview internals, and modifier
|
|
56
|
+
pipeline internals are not public root APIs.
|
|
57
|
+
|
|
58
|
+
## Integration Subpath
|
|
59
|
+
|
|
60
|
+
`@mk-drag-and-drop/dom/integration` exports:
|
|
61
|
+
|
|
62
|
+
- `createDragRuntimeHandle`
|
|
63
|
+
- `DragRuntimeHandle`, `DragRuntimeHandleOptions`,
|
|
64
|
+
`DragRuntimeHandleConfigureInput`
|
|
65
|
+
- `DragState`, `DragOverlayPhase`, `DragOverlayRenderState`
|
|
66
|
+
- `DragRuntimeSubscription`
|
|
67
|
+
- `createDomDraggable`, `createDomDroppable`, `createDomDropContainer`,
|
|
68
|
+
`createDomSortable`
|
|
69
|
+
- the related DOM behavior/input/runtime types
|
|
70
|
+
- `domDragHandleAttribute`
|
|
71
|
+
|
|
72
|
+
Use this subpath when building a framework adapter or custom behavior layer. For
|
|
73
|
+
ordinary DOM usage, prefer the root controller and binding helpers.
|
|
74
|
+
|
|
75
|
+
## Controller
|
|
76
|
+
|
|
77
|
+
`createDragController(options?)` creates the public DOM controller used by the
|
|
78
|
+
root binding helpers. Controller options include:
|
|
79
|
+
|
|
80
|
+
- lifecycle callbacks: `onDragStart`, `onDragUpdate`, `onDragEnd`, `onDrop`
|
|
81
|
+
- `announcements`: lifecycle callbacks that return polite live-region text
|
|
82
|
+
- `dragOverlay(input)`: returns an overlay `HTMLElement` or `null`
|
|
83
|
+
- `overlayRoot`: optional root for the DOM overlay wrapper
|
|
84
|
+
- `keepOverlayOnDrop`: keeps the overlay in the `"released"` phase until
|
|
85
|
+
`finishOverlay` or the overlay input `finish` callback removes it
|
|
86
|
+
- `targetingAlgorithm` and `targetingConstraint`
|
|
87
|
+
- `modifiers`
|
|
88
|
+
- `pointerConfiguration`
|
|
89
|
+
- `keyboardConfiguration`
|
|
90
|
+
|
|
91
|
+
The returned controller has:
|
|
92
|
+
|
|
93
|
+
- `update(options)`: replaces runtime/controller configuration
|
|
94
|
+
- `remeasureDropTargets(input?)`: remeasures all targets, one target id, an
|
|
95
|
+
array of target ids, or `{ group }`
|
|
96
|
+
- `cleanup()`: ends active drag resources and removes the current overlay
|
|
97
|
+
- `dispose()`: final teardown for the controller, runtime registrations,
|
|
98
|
+
subscriptions, overlays, live region, and registered dispose callbacks
|
|
99
|
+
- `finishOverlay()`: removes a kept overlay
|
|
100
|
+
|
|
101
|
+
## Drag Overlays
|
|
102
|
+
|
|
103
|
+
`dragOverlay(input)` is a creation hook. The controller calls it when overlay
|
|
104
|
+
content is mounted for the `"dragging"` phase, and once more for the
|
|
105
|
+
`"released"` phase when `keepOverlayOnDrop` is enabled. Pointer movement does
|
|
106
|
+
not call `dragOverlay`.
|
|
107
|
+
|
|
108
|
+
Apps own the returned overlay element and any dynamic content inside it. The
|
|
109
|
+
package owns the overlay wrapper, movement, cleanup, and geometry measurement
|
|
110
|
+
used by targeting and modifiers. The wrapper is positioned with `position:
|
|
111
|
+
fixed`, source rect dimensions, `pointer-events: none`, and a transform derived
|
|
112
|
+
from the current pointer delta.
|
|
113
|
+
|
|
114
|
+
The controller measures the returned overlay element on mount/replacement. When
|
|
115
|
+
`ResizeObserver` is available, it also remeasures when that element changes
|
|
116
|
+
size. The runtime derives the current overlay rect from that cached measurement
|
|
117
|
+
plus pointer movement.
|
|
118
|
+
|
|
119
|
+
For dynamic overlay content, keep a reference to the element and update it from
|
|
120
|
+
lifecycle callbacks or subscriptions:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
let overlayElement: HTMLElement | null = null;
|
|
124
|
+
|
|
125
|
+
const controller = createDragController({
|
|
126
|
+
dragOverlay({ dragState }) {
|
|
127
|
+
overlayElement = document.createElement("div");
|
|
128
|
+
overlayElement.textContent = dragState.draggableId;
|
|
129
|
+
return overlayElement;
|
|
130
|
+
},
|
|
131
|
+
onDragUpdate({ pointerPosition }) {
|
|
132
|
+
if (overlayElement) {
|
|
133
|
+
overlayElement.textContent = `${pointerPosition.x}, ${pointerPosition.y}`;
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
onDragEnd() {
|
|
137
|
+
overlayElement = null;
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Lifecycle Events
|
|
143
|
+
|
|
144
|
+
Lifecycle callbacks receive an event and helper methods.
|
|
145
|
+
|
|
146
|
+
Event shapes:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
type DragStartEvent = {
|
|
150
|
+
draggableId: string;
|
|
151
|
+
source: DragSource;
|
|
152
|
+
pointerPosition: DragPoint;
|
|
153
|
+
sourceRect: DragRect;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
type DragUpdateEvent = {
|
|
157
|
+
draggableId: string;
|
|
158
|
+
source: DragSource;
|
|
159
|
+
pointerPosition: DragPoint;
|
|
160
|
+
activeDropTargetId: string | null;
|
|
161
|
+
previousDropTargetId: string | null;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
type DragSource = "pointer" | "keyboard";
|
|
165
|
+
|
|
166
|
+
type DragEndResult =
|
|
167
|
+
| "dropped"
|
|
168
|
+
| "no-target"
|
|
169
|
+
| "invalid-target"
|
|
170
|
+
| "canceled";
|
|
171
|
+
|
|
172
|
+
type DragEndEvent = {
|
|
173
|
+
draggableId: string;
|
|
174
|
+
source: DragSource;
|
|
175
|
+
result: DragEndResult;
|
|
176
|
+
dropTargetId: string | null;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
type DropEvent = {
|
|
180
|
+
draggableId: string;
|
|
181
|
+
source: DragSource;
|
|
182
|
+
dropTargetId: string;
|
|
183
|
+
sortablePlacement?: SortableDropPlacement;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
type SortableDropPlacement = {
|
|
187
|
+
sourceContainerId: string | null;
|
|
188
|
+
containerId: string | null;
|
|
189
|
+
previousDraggableId: string | null;
|
|
190
|
+
nextDraggableId: string | null;
|
|
191
|
+
targetDraggableId: string | null;
|
|
192
|
+
side: "before" | "after" | null;
|
|
193
|
+
};
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Helpers:
|
|
197
|
+
|
|
198
|
+
- `getDropTargetRect(dropTargetId)`
|
|
199
|
+
|
|
200
|
+
`onDrop` only runs for a valid successful drop. `onDragEnd` runs whenever a drag
|
|
201
|
+
ends. `result: "dropped"` means a valid target was accepted and `onDrop` also
|
|
202
|
+
runs. `result: "no-target"` means the user ended normally with no active target.
|
|
203
|
+
`result: "invalid-target"` means an active target candidate was stale by finish
|
|
204
|
+
time. `result: "canceled"` means the drag was canceled, such as Escape,
|
|
205
|
+
pointercancel, or runtime cancellation.
|
|
206
|
+
|
|
207
|
+
## Bindings
|
|
208
|
+
|
|
209
|
+
Root binding helpers attach behavior to DOM elements and return `void`.
|
|
210
|
+
Registrations and listeners are tied to the controller lifetime.
|
|
211
|
+
|
|
212
|
+
`createDraggable(input)` accepts:
|
|
213
|
+
|
|
214
|
+
- `controller`
|
|
215
|
+
- `element`
|
|
216
|
+
- `draggableId`
|
|
217
|
+
- `group`, defaulting to `"default"`
|
|
218
|
+
|
|
219
|
+
`createDroppable(input)` accepts:
|
|
220
|
+
|
|
221
|
+
- `controller`
|
|
222
|
+
- `element`
|
|
223
|
+
- `dropTargetId`
|
|
224
|
+
- `group`, defaulting to `"default"`
|
|
225
|
+
- `containerId`, defaulting to `null`
|
|
226
|
+
|
|
227
|
+
`createDropContainer(input)` accepts:
|
|
228
|
+
|
|
229
|
+
- `controller`
|
|
230
|
+
- `element`
|
|
231
|
+
- `containerId`
|
|
232
|
+
- `group`, defaulting to `"default"`
|
|
233
|
+
|
|
234
|
+
`createSortable(input)` accepts:
|
|
235
|
+
|
|
236
|
+
- `controller`
|
|
237
|
+
- `element`
|
|
238
|
+
- `draggableId`
|
|
239
|
+
- `group`, defaulting to `"default"`
|
|
240
|
+
- `containerId`, defaulting to `null`
|
|
241
|
+
|
|
242
|
+
`createDragHandle({ element })` marks an element with the drag-handle data
|
|
243
|
+
attribute. When a draggable or sortable contains a handle, pointer and keyboard
|
|
244
|
+
dragging are handled from that handle instead of unrelated children.
|
|
245
|
+
|
|
246
|
+
## Placement Data
|
|
247
|
+
|
|
248
|
+
Plain droppable drops are id-only: `draggableId`, `dropTargetId`, and `source`.
|
|
249
|
+
Sortable behavior may add sortable-specific placement under
|
|
250
|
+
`event.sortablePlacement`:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
type SortableDropPlacement = {
|
|
254
|
+
sourceContainerId: string | null;
|
|
255
|
+
containerId: string | null;
|
|
256
|
+
previousDraggableId: string | null;
|
|
257
|
+
nextDraggableId: string | null;
|
|
258
|
+
targetDraggableId: string | null;
|
|
259
|
+
side: "before" | "after" | null;
|
|
260
|
+
};
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
No sortable placement is included for plain droppable drops. No-op sortable drops
|
|
264
|
+
may omit `sortablePlacement`.
|
|
265
|
+
`targetDraggableId` and `side` preserve the active sortable item anchor when a
|
|
266
|
+
drop lands relative to an item. They are `null` for container-only placements.
|
|
267
|
+
|
|
268
|
+
Sortable behavior owns only a transient DOM preview during the drag. It may move
|
|
269
|
+
DOM nodes to show where an item would land, but it does not commit app data. On
|
|
270
|
+
drop, the app reads `event.sortablePlacement` and updates its own data/rendering.
|
|
271
|
+
|
|
272
|
+
## Targeting
|
|
273
|
+
|
|
274
|
+
A measured target passed to custom targeting code has:
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
type DropTarget = {
|
|
278
|
+
dropTargetId: string;
|
|
279
|
+
dropTargetRect: DragRect;
|
|
280
|
+
};
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Built-in targeting algorithms:
|
|
284
|
+
|
|
285
|
+
- `pointerToCenter`
|
|
286
|
+
- `centerToCenter`
|
|
287
|
+
- `pointerToRectDistance`
|
|
288
|
+
|
|
289
|
+
Built-in targeting helpers/constraints:
|
|
290
|
+
|
|
291
|
+
- `getDistanceToRect(point, rect)`
|
|
292
|
+
- `maxPointerDistanceToRect({ maxDistance?, maxXDistance?, maxYDistance? })`
|
|
293
|
+
- `maxOverlayCenterDistanceToRect({ maxDistance?, maxXDistance?, maxYDistance? })`
|
|
294
|
+
|
|
295
|
+
Custom `TargetingAlgorithm` functions receive `pointerPosition`, `overlayRect`,
|
|
296
|
+
and the filtered `dropTargets` list. Algorithms decide which geometry to use:
|
|
297
|
+
`pointerToCenter` and `pointerToRectDistance` are pointer based, while
|
|
298
|
+
`centerToCenter` computes the overlay center from `overlayRect` and returns no
|
|
299
|
+
target when no overlay rect is available. Custom `TargetingConstraint`
|
|
300
|
+
functions receive `pointerPosition`, `overlayRect`, and one `dropTarget`, and
|
|
301
|
+
return whether that candidate is eligible. Use the pointer or overlay-center
|
|
302
|
+
distance helper that matches the intended behavior.
|
|
303
|
+
|
|
304
|
+
Sortable placement is separate from targeting. The configured targeting
|
|
305
|
+
algorithm alone chooses `activeDropTarget`; sortable only decides before/after
|
|
306
|
+
preview placement relative to that active target.
|
|
307
|
+
|
|
308
|
+
## Modifiers
|
|
309
|
+
|
|
310
|
+
Modifiers transform pointer movement before drag state updates. Built-ins:
|
|
311
|
+
|
|
312
|
+
- `lockToXAxis()`
|
|
313
|
+
- `lockToYAxis()`
|
|
314
|
+
- `restrictToContainer(resolver)`
|
|
315
|
+
|
|
316
|
+
`restrictToContainer` receives a resolver function with
|
|
317
|
+
`DragModifierSetupInput` and returns an `HTMLElement | null`.
|
|
318
|
+
|
|
319
|
+
Custom modifiers can provide:
|
|
320
|
+
|
|
321
|
+
- `setup(input)`: optional setup state for the active drag
|
|
322
|
+
- `transform(input)`: returns the next `DragPoint`
|
|
323
|
+
|
|
324
|
+
Modifier input includes `draggableId`, `group`, source rect, initial pointer
|
|
325
|
+
position, raw pointer position, current pointer position, overlay rect, and
|
|
326
|
+
modifier state.
|
|
327
|
+
|
|
328
|
+
## Measurement
|
|
329
|
+
|
|
330
|
+
Targets are measured when registered and remeasured at drag start. The explicit
|
|
331
|
+
remeasurement entry point is `controller.remeasureDropTargets(input?)`; call it
|
|
332
|
+
when app-owned layout changes during a drag should affect targeting.
|
|
333
|
+
|
|
334
|
+
`RemeasureDropTargetsInput` accepts:
|
|
335
|
+
|
|
336
|
+
- no argument for all targets
|
|
337
|
+
- a target id string
|
|
338
|
+
- an array of target id strings
|
|
339
|
+
- `{ group: string }`
|
|
340
|
+
|
|
341
|
+
Sortable preview movement does not automatically remeasure a group. Targeting
|
|
342
|
+
continues from the runtime's cached target rects until the app explicitly
|
|
343
|
+
remeasures. Final sortable placement is derived from the current preview DOM
|
|
344
|
+
order, so app-owned layout changes such as expanding tree rows or changing
|
|
345
|
+
grouped sections during a drag should call the public remeasure API when those
|
|
346
|
+
changes need to affect targeting.
|
|
347
|
+
|
|
348
|
+
## Data Ownership
|
|
349
|
+
|
|
350
|
+
The DOM package never owns application data. Item ids, target ids, group ids,
|
|
351
|
+
and container ids are identifiers that your app maps to its own model.
|
|
352
|
+
|
|
353
|
+
Commit data in lifecycle callbacks, usually `onDrop`. Rerender or patch the DOM
|
|
354
|
+
using your app's normal rendering path after the commit.
|
|
355
|
+
|
|
356
|
+
## Cleanup Model
|
|
357
|
+
|
|
358
|
+
Root binding helpers do not return per-item disposers. Dispose the controller
|
|
359
|
+
when tearing down the view or application area that owns those bindings.
|
|
360
|
+
|
|
361
|
+
Use:
|
|
362
|
+
|
|
363
|
+
- `controller.cleanup()` to end active drag work and remove the active overlay
|
|
364
|
+
- `controller.dispose()` for final teardown
|
|
365
|
+
|
|
366
|
+
Disconnected drop targets are pruned on drag-critical paths, and dispose clears
|
|
367
|
+
runtime registrations. React users usually rely on `DragProvider` and hooks for
|
|
368
|
+
runtime and binding lifetime.
|
|
369
|
+
|
|
370
|
+
## Accessibility Scope
|
|
371
|
+
|
|
372
|
+
Keyboard dragging is available when `keyboardConfiguration.enabled` is not
|
|
373
|
+
disabled. Defaults use Space or Enter to start/drop, Escape to cancel, arrow
|
|
374
|
+
keys to move, and a default movement distance of 24.
|
|
375
|
+
|
|
376
|
+
Draggable or sortable elements may receive `tabIndex` and keyboard handlers
|
|
377
|
+
when keyboard dragging is enabled. Drag handles and focusable draggable elements
|
|
378
|
+
should have clear labels.
|
|
379
|
+
|
|
380
|
+
Announcements are opt-in through `announcements`. The controller creates a
|
|
381
|
+
polite live region only when announcement callbacks are provided. The package
|
|
382
|
+
does not claim complete screen-reader drag-and-drop support; apps should provide
|
|
383
|
+
domain-specific instructions and announcements for their interaction model.
|
|
384
|
+
The lifecycle `onDragUpdate` callback runs for drag updates; the announcement
|
|
385
|
+
`onDragUpdate` callback is intended for active-drop-target transitions and
|
|
386
|
+
dedupes repeated messages to avoid live-region spam. Use lifecycle
|
|
387
|
+
`onDragUpdate` for per-frame side effects, not announcements.
|
|
388
|
+
|
|
389
|
+
## Example
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
import {
|
|
393
|
+
createDragController,
|
|
394
|
+
createDraggable,
|
|
395
|
+
createDroppable,
|
|
396
|
+
} from "@mk-drag-and-drop/dom";
|
|
397
|
+
|
|
398
|
+
const state = {
|
|
399
|
+
itemLocation: "todo",
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const controller = createDragController({
|
|
403
|
+
onDrop({ draggableId, dropTargetId }) {
|
|
404
|
+
state.itemLocation = dropTargetId;
|
|
405
|
+
console.log(`${draggableId} moved to ${dropTargetId}`);
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const item = document.createElement("button");
|
|
410
|
+
item.type = "button";
|
|
411
|
+
item.textContent = "Drag me";
|
|
412
|
+
|
|
413
|
+
createDraggable({
|
|
414
|
+
controller,
|
|
415
|
+
element: item,
|
|
416
|
+
draggableId: "task-1",
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
for (const columnId of ["todo", "done"]) {
|
|
420
|
+
const column = document.createElement("section");
|
|
421
|
+
|
|
422
|
+
createDroppable({
|
|
423
|
+
controller,
|
|
424
|
+
element: column,
|
|
425
|
+
dropTargetId: columnId,
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
document.body.append(column);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
document.body.append(item);
|
|
432
|
+
window.addEventListener("pagehide", () => controller.dispose(), { once: true });
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Examples
|
|
436
|
+
|
|
437
|
+
Vanilla DOM examples live in `apps/web/src/vanilla`:
|
|
438
|
+
|
|
439
|
+
- `basicDrag.ts`
|
|
440
|
+
- `dropzoneList.ts`
|
|
441
|
+
- `sortableList.ts`
|
|
442
|
+
- `kanbanExample.ts`
|
|
443
|
+
- `groupedExample.ts`
|
|
444
|
+
- `treeExample.ts`
|
|
445
|
+
|
|
446
|
+
They use root imports from `@mk-drag-and-drop/dom`.
|
|
447
|
+
|
|
448
|
+
## Development
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
pnpm --filter @mk-drag-and-drop/dom build
|
|
452
|
+
pnpm --filter @mk-drag-and-drop/dom lint
|
|
453
|
+
pnpm --filter @mk-drag-and-drop/dom test
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Example app verification:
|
|
457
|
+
|
|
458
|
+
```bash
|
|
459
|
+
pnpm --filter web build
|
|
460
|
+
```
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DragRuntimeHandle, InternalBindingCleanupRuntime } from "../runtime/drag-runtime-handle.js";
|
|
2
|
+
import type { DragController } from "./create-drag-controller.js";
|
|
3
|
+
export type DragControllerRuntimeHandle = DragRuntimeHandle & InternalBindingCleanupRuntime;
|
|
4
|
+
export declare function setControllerRuntime(controller: DragController, runtime: DragRuntimeHandle): void;
|
|
5
|
+
export declare function getControllerRuntime(controller: DragController): DragControllerRuntimeHandle;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const controllerRuntimes = new WeakMap();
|
|
2
|
+
export function setControllerRuntime(controller, runtime) {
|
|
3
|
+
controllerRuntimes.set(controller, runtime);
|
|
4
|
+
}
|
|
5
|
+
export function getControllerRuntime(controller) {
|
|
6
|
+
const runtime = controllerRuntimes.get(controller);
|
|
7
|
+
if (!runtime) {
|
|
8
|
+
throw new Error("Unknown drag controller. Create controllers with createDragController before passing them to DOM bindings.");
|
|
9
|
+
}
|
|
10
|
+
return runtime;
|
|
11
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { KeyboardConfiguration, PointerConfiguration } from "../input/config.js";
|
|
2
|
+
import type { DragModifierInput } from "../modifiers/types.js";
|
|
3
|
+
import type { RemeasureDropTargetsInput } from "../runtime/drop-target-registry.js";
|
|
4
|
+
import type { DragEndEvent, DragLifecycleCallbacks, DragStartEvent, DragUpdateEvent, DropEvent } from "../runtime/lifecycle.js";
|
|
5
|
+
import type { DragOverlayPhase, DragState } from "../runtime/types.js";
|
|
6
|
+
import type { TargetingAlgorithm, TargetingConstraint } from "../targeting/types.js";
|
|
7
|
+
export type DragControllerAnnouncements = {
|
|
8
|
+
onDragStart?: (event: DragStartEvent) => string | null;
|
|
9
|
+
onDragUpdate?: (event: DragUpdateEvent) => string | null;
|
|
10
|
+
onDragEnd?: (event: DragEndEvent) => string | null;
|
|
11
|
+
onDrop?: (event: DropEvent) => string | null;
|
|
12
|
+
};
|
|
13
|
+
export type DragControllerOverlayInput = {
|
|
14
|
+
dragState: DragState;
|
|
15
|
+
phase: DragOverlayPhase;
|
|
16
|
+
finish: () => void;
|
|
17
|
+
};
|
|
18
|
+
export type DragControllerOptions = {
|
|
19
|
+
announcements?: DragControllerAnnouncements;
|
|
20
|
+
dragOverlay?: (input: DragControllerOverlayInput) => HTMLElement | null;
|
|
21
|
+
keyboardConfiguration?: KeyboardConfiguration;
|
|
22
|
+
keepOverlayOnDrop?: boolean;
|
|
23
|
+
modifiers?: readonly DragModifierInput[];
|
|
24
|
+
overlayRoot?: HTMLElement;
|
|
25
|
+
pointerConfiguration?: PointerConfiguration;
|
|
26
|
+
targetingAlgorithm?: TargetingAlgorithm;
|
|
27
|
+
targetingConstraint?: TargetingConstraint;
|
|
28
|
+
} & DragLifecycleCallbacks;
|
|
29
|
+
export type DragController = {
|
|
30
|
+
update: (options: DragControllerOptions) => void;
|
|
31
|
+
cleanup: () => void;
|
|
32
|
+
dispose: () => void;
|
|
33
|
+
finishOverlay: () => void;
|
|
34
|
+
remeasureDropTargets: (input?: RemeasureDropTargetsInput) => void;
|
|
35
|
+
};
|
|
36
|
+
export declare function createDragController(options?: DragControllerOptions): DragController;
|