@mk-drag-and-drop/react 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 +594 -0
- package/dist/drag-context.d.ts +6 -0
- package/dist/drag-context.js +2 -0
- package/dist/drag-overlay.d.ts +18 -0
- package/dist/drag-overlay.js +80 -0
- package/dist/drag-provider.d.ts +21 -0
- package/dist/drag-provider.js +293 -0
- package/dist/hooks/use-drag-handle.d.ts +5 -0
- package/dist/hooks/use-drag-handle.js +7 -0
- package/dist/hooks/use-draggable.d.ts +9 -0
- package/dist/hooks/use-draggable.js +36 -0
- package/dist/hooks/use-drop-container.d.ts +9 -0
- package/dist/hooks/use-drop-container.js +56 -0
- package/dist/hooks/use-droppable.d.ts +10 -0
- package/dist/hooks/use-droppable.js +23 -0
- package/dist/hooks/use-remeasure-drop-targets.d.ts +2 -0
- package/dist/hooks/use-remeasure-drop-targets.js +12 -0
- package/dist/hooks/use-sortable.d.ts +13 -0
- package/dist/hooks/use-sortable.js +49 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +10 -0
- package/dist/modifiers/restrict-to-container.d.ts +4 -0
- package/dist/modifiers/restrict-to-container.js +10 -0
- package/dist/utils/compose-refs.d.ts +2 -0
- package/dist/utils/compose-refs.js +15 -0
- package/package.json +70 -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,594 @@
|
|
|
1
|
+
# @mk-drag-and-drop/react
|
|
2
|
+
|
|
3
|
+
## What It Is
|
|
4
|
+
|
|
5
|
+
`@mk-drag-and-drop/react` is the React adapter for the DOM-first
|
|
6
|
+
`@mk-drag-and-drop/dom` drag-and-drop runtime. It provides React hooks and a
|
|
7
|
+
provider for wiring rendered DOM nodes into the shared runtime.
|
|
8
|
+
|
|
9
|
+
The package is headless. It is not a component library, and it does not own your
|
|
10
|
+
markup, styling, data model, rendering strategy, or persistence. It provides
|
|
11
|
+
behavior, registration, targeting, lifecycle information, overlays, modifiers,
|
|
12
|
+
and placement data. Your app decides what the UI looks like and how a completed
|
|
13
|
+
drop changes application data.
|
|
14
|
+
|
|
15
|
+
The React package supports draggable items, droppable targets, sortable
|
|
16
|
+
lists and boards, drag handles, drop containers, drag overlays, modifiers,
|
|
17
|
+
targeting configuration, keyboard and pointer configuration, lifecycle
|
|
18
|
+
callbacks, and optional announcements.
|
|
19
|
+
|
|
20
|
+
## Relationship To The DOM Package
|
|
21
|
+
|
|
22
|
+
The React package wraps `@mk-drag-and-drop/dom`.
|
|
23
|
+
|
|
24
|
+
`DragProvider` creates and configures the DOM runtime through the package
|
|
25
|
+
integration handle.
|
|
26
|
+
Hooks such as `useDraggable`, `useDroppable`, `useSortable`, and
|
|
27
|
+
`useDropContainer` register DOM nodes with that runtime through React refs.
|
|
28
|
+
React hooks are adapters over DOM behavior, not a separate implementation.
|
|
29
|
+
|
|
30
|
+
Consumers should import React APIs from the package root,
|
|
31
|
+
`@mk-drag-and-drop/react`. The React package does not expose public subpath
|
|
32
|
+
exports. It uses `@mk-drag-and-drop/dom/integration` internally for lower-level
|
|
33
|
+
DOM behavior wiring.
|
|
34
|
+
|
|
35
|
+
Deep imports into package `src` or `dist` files are not part of the public
|
|
36
|
+
package exports.
|
|
37
|
+
|
|
38
|
+
The vanilla and React examples use the same underlying behavior. React users can
|
|
39
|
+
use the React package for provider and ref integration. Users who are not
|
|
40
|
+
rendering with React, or who are building another framework adapter, can use
|
|
41
|
+
`@mk-drag-and-drop/dom` directly.
|
|
42
|
+
|
|
43
|
+
## Public Root API
|
|
44
|
+
|
|
45
|
+
The React root export includes:
|
|
46
|
+
|
|
47
|
+
- `DragProvider`, `DragProviderProps`, `DragAnnouncements`
|
|
48
|
+
- `DragOverlayInput`
|
|
49
|
+
- `useDraggable`, `UseDraggableOptions`, `UseDraggableResult`
|
|
50
|
+
- `useDroppable`, `UseDroppableOptions`, `UseDroppableResult`
|
|
51
|
+
- `useDropContainer`, `UseDropContainerOptions`, `UseDropContainerResult`
|
|
52
|
+
- `useSortable`, `UseSortableOptions`, `UseSortableResult`
|
|
53
|
+
- `useDragHandle`, `UseDragHandleResult`
|
|
54
|
+
- `useRemeasureDropTargets`
|
|
55
|
+
- `composeRefs`
|
|
56
|
+
- React-friendly `restrictToContainer`, `ReactRestrictToContainerInput`
|
|
57
|
+
- DOM targeting helpers, modifier helpers, lifecycle/source/result types,
|
|
58
|
+
sortable placement types, keyboard/pointer configuration types, and geometry
|
|
59
|
+
types re-exported from the DOM package
|
|
60
|
+
- `DragState` and `DragOverlayPhase` from the DOM integration layer
|
|
61
|
+
|
|
62
|
+
## Core Mental Model
|
|
63
|
+
|
|
64
|
+
1. Wrap the interactive area in `DragProvider`.
|
|
65
|
+
2. Register drag sources with `useDraggable` or `useSortable`.
|
|
66
|
+
3. Register drop targets with `useDroppable` or `useDropContainer`.
|
|
67
|
+
4. Optionally attach `useDragHandle` to a handle element.
|
|
68
|
+
5. Use lifecycle callbacks, especially `onDrop`, to update app data.
|
|
69
|
+
6. Render the result however your app wants.
|
|
70
|
+
|
|
71
|
+
The package reports drag/drop operations. Your app owns the data and the final
|
|
72
|
+
UI commit. React state is one common way to commit the result, but it is not
|
|
73
|
+
required by the package.
|
|
74
|
+
|
|
75
|
+
## Installation
|
|
76
|
+
|
|
77
|
+
This workspace currently marks `@mk-drag-and-drop/react` as private. Install the
|
|
78
|
+
React and DOM packages from your workspace or package registry when they are
|
|
79
|
+
available:
|
|
80
|
+
|
|
81
|
+
- `@mk-drag-and-drop/react`
|
|
82
|
+
- `@mk-drag-and-drop/dom`
|
|
83
|
+
|
|
84
|
+
`react` is a peer dependency of the React package.
|
|
85
|
+
|
|
86
|
+
## Quick Start
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { useState, type ReactNode } from "react";
|
|
90
|
+
import {
|
|
91
|
+
DragProvider,
|
|
92
|
+
useDragHandle,
|
|
93
|
+
useDraggable,
|
|
94
|
+
useDroppable,
|
|
95
|
+
} from "@mk-drag-and-drop/react";
|
|
96
|
+
|
|
97
|
+
type Location = "left" | "right";
|
|
98
|
+
|
|
99
|
+
export function Example() {
|
|
100
|
+
const [location, setLocation] = useState<Location>("left");
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<DragProvider
|
|
104
|
+
onDrop={({ draggableId, dropTargetId }) => {
|
|
105
|
+
if (
|
|
106
|
+
draggableId === "card" &&
|
|
107
|
+
(dropTargetId === "left" || dropTargetId === "right")
|
|
108
|
+
) {
|
|
109
|
+
setLocation(dropTargetId);
|
|
110
|
+
}
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
<div>
|
|
114
|
+
<DropZone dropTargetId="left">
|
|
115
|
+
{location === "left" ? <Card /> : null}
|
|
116
|
+
</DropZone>
|
|
117
|
+
<DropZone dropTargetId="right">
|
|
118
|
+
{location === "right" ? <Card /> : null}
|
|
119
|
+
</DropZone>
|
|
120
|
+
</div>
|
|
121
|
+
</DragProvider>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function Card() {
|
|
126
|
+
const draggable = useDraggable({ draggableId: "card", group: "demo" });
|
|
127
|
+
const dragHandle = useDragHandle<HTMLButtonElement>();
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<div {...draggable}>
|
|
131
|
+
<button {...dragHandle} type="button" aria-label="Drag card">
|
|
132
|
+
Drag
|
|
133
|
+
</button>
|
|
134
|
+
<span>Card</span>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function DropZone({
|
|
140
|
+
dropTargetId,
|
|
141
|
+
children,
|
|
142
|
+
}: {
|
|
143
|
+
dropTargetId: Location;
|
|
144
|
+
children: ReactNode;
|
|
145
|
+
}) {
|
|
146
|
+
const droppable = useDroppable({ dropTargetId, group: "demo" });
|
|
147
|
+
|
|
148
|
+
return <div {...droppable}>{children}</div>;
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Real apps own the markup, layout, styling, state updates, and persistence.
|
|
153
|
+
|
|
154
|
+
## DragProvider
|
|
155
|
+
|
|
156
|
+
`DragProvider` owns the runtime lifetime for the React subtree. It creates the
|
|
157
|
+
DOM runtime, configures it from props, exposes it through React context, renders
|
|
158
|
+
an optional drag overlay, and disposes the runtime when the provider unmounts.
|
|
159
|
+
|
|
160
|
+
Key props:
|
|
161
|
+
|
|
162
|
+
- `onDragStart(event, helpers)`: called when a drag starts.
|
|
163
|
+
- `onDragUpdate(event, helpers)`: called for drag updates.
|
|
164
|
+
- `onDragEnd(event, helpers)`: called when dragging ends, with or without a
|
|
165
|
+
valid drop.
|
|
166
|
+
- `onDrop(event, helpers)`: called when an item is dropped on a valid target.
|
|
167
|
+
- `dragOverlay(input)`: creates the visual overlay for the active drag.
|
|
168
|
+
- `targetingAlgorithm`: chooses one target from the measured targets.
|
|
169
|
+
- `targetingConstraint`: filters measured targets before targeting runs.
|
|
170
|
+
- `modifiers`: transforms pointer movement, such as axis locking or bounds.
|
|
171
|
+
- `pointerConfiguration`: configures pointer activation delay and distance.
|
|
172
|
+
- `keyboardConfiguration`: enables and configures keyboard drag commands.
|
|
173
|
+
- `announcements`: optional callbacks that return live-region messages.
|
|
174
|
+
- `keepOverlayOnDrop`: keeps the overlay in the released phase until the overlay
|
|
175
|
+
calls `finish`.
|
|
176
|
+
|
|
177
|
+
Callbacks receive operation information and helper methods from the runtime.
|
|
178
|
+
Use those callbacks to commit app data. Do not mutate package internals.
|
|
179
|
+
|
|
180
|
+
## Lifecycle Events
|
|
181
|
+
|
|
182
|
+
`DragProvider` lifecycle callbacks and `announcements` callbacks receive these
|
|
183
|
+
event shapes:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
type DragSource = "pointer" | "keyboard";
|
|
187
|
+
|
|
188
|
+
type DragEndResult =
|
|
189
|
+
| "dropped"
|
|
190
|
+
| "no-target"
|
|
191
|
+
| "invalid-target"
|
|
192
|
+
| "canceled";
|
|
193
|
+
|
|
194
|
+
type DragStartEvent = {
|
|
195
|
+
draggableId: string;
|
|
196
|
+
source: DragSource;
|
|
197
|
+
pointerPosition: DragPoint;
|
|
198
|
+
sourceRect: DragRect;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
type DragUpdateEvent = {
|
|
202
|
+
draggableId: string;
|
|
203
|
+
source: DragSource;
|
|
204
|
+
pointerPosition: DragPoint;
|
|
205
|
+
activeDropTargetId: string | null;
|
|
206
|
+
previousDropTargetId: string | null;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
type DragEndEvent = {
|
|
210
|
+
draggableId: string;
|
|
211
|
+
source: DragSource;
|
|
212
|
+
result: DragEndResult;
|
|
213
|
+
dropTargetId: string | null;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
type DropEvent = {
|
|
217
|
+
draggableId: string;
|
|
218
|
+
source: DragSource;
|
|
219
|
+
dropTargetId: string;
|
|
220
|
+
sortablePlacement?: SortableDropPlacement;
|
|
221
|
+
};
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`onDrop` only runs for a valid successful drop. `onDragEnd.result` distinguishes
|
|
225
|
+
successful drops, normal releases with no target, stale/invalid targets, and
|
|
226
|
+
cancellations such as Escape or pointercancel. Plain drops are id-only.
|
|
227
|
+
Sortable drops may include `event.sortablePlacement`; apps still own the data
|
|
228
|
+
commit.
|
|
229
|
+
|
|
230
|
+
## Hooks
|
|
231
|
+
|
|
232
|
+
Hook result types are generic over the host element type and default to
|
|
233
|
+
`HTMLElement`. Specify a narrower element type when you want element-specific
|
|
234
|
+
ref and attribute typing; the hooks are not limited to `div` elements.
|
|
235
|
+
|
|
236
|
+
### useDraggable
|
|
237
|
+
|
|
238
|
+
`useDraggable` registers an item as a drag source.
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
const draggable = useDraggable({
|
|
242
|
+
draggableId: "card-1",
|
|
243
|
+
group: "cards",
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
return <div {...draggable}>Card</div>;
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Options:
|
|
250
|
+
|
|
251
|
+
- `draggableId`: stable string identifier for the dragged item.
|
|
252
|
+
- `group`: optional drag group. Defaults to `"default"`.
|
|
253
|
+
|
|
254
|
+
Return shape:
|
|
255
|
+
|
|
256
|
+
- `ref`: attaches the rendered element to the runtime.
|
|
257
|
+
- `onPointerDown`: starts pointer activation when allowed.
|
|
258
|
+
- `tabIndex` and `onKeyDown`: included when keyboard dragging is enabled.
|
|
259
|
+
|
|
260
|
+
Use `useDragHandle` when dragging should start only from a specific handle.
|
|
261
|
+
|
|
262
|
+
### useDroppable
|
|
263
|
+
|
|
264
|
+
`useDroppable` registers a DOM node as a valid drop target.
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
const droppable = useDroppable({
|
|
268
|
+
dropTargetId: "archive",
|
|
269
|
+
group: "cards",
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return <div {...droppable} />;
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Options:
|
|
276
|
+
|
|
277
|
+
- `dropTargetId`: stable string identifier for the target.
|
|
278
|
+
- `group`: optional drag group. Only items in the same group target it.
|
|
279
|
+
- `containerId`: optional container identity for placement-aware behavior.
|
|
280
|
+
|
|
281
|
+
Use droppable targets for custom drop zones, tree rows, grouped rows, insertion
|
|
282
|
+
lines, and other app-defined target geometry.
|
|
283
|
+
|
|
284
|
+
### useSortable
|
|
285
|
+
|
|
286
|
+
`useSortable` combines draggable behavior and drop-target behavior for sortable
|
|
287
|
+
items.
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
const sortable = useSortable<HTMLArticleElement>({
|
|
291
|
+
draggableId: item.id,
|
|
292
|
+
group: "cards",
|
|
293
|
+
containerId: column.id,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
return <article {...sortable}>{item.title}</article>;
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Options:
|
|
300
|
+
|
|
301
|
+
- `draggableId`: stable string identifier for the sortable item.
|
|
302
|
+
- `group`: optional drag group. Defaults to `"default"`.
|
|
303
|
+
- `containerId`: optional container identity for lists, boards, and columns.
|
|
304
|
+
|
|
305
|
+
Return shape:
|
|
306
|
+
|
|
307
|
+
- `ref`: attaches and registers the sortable element.
|
|
308
|
+
- `onPointerDown`: starts pointer activation when allowed.
|
|
309
|
+
- `tabIndex` and `onKeyDown`: included when keyboard dragging is enabled.
|
|
310
|
+
|
|
311
|
+
During drag, the runtime may temporarily move sortable DOM nodes to preview
|
|
312
|
+
placement. That preview is transient. It does not commit application order.
|
|
313
|
+
On drop, read `event.sortablePlacement` and update your own data. Plain
|
|
314
|
+
droppable drops do not include sortable placement.
|
|
315
|
+
|
|
316
|
+
### useDropContainer
|
|
317
|
+
|
|
318
|
+
`useDropContainer` registers a container for placement-aware sortable/drop
|
|
319
|
+
behavior.
|
|
320
|
+
|
|
321
|
+
```tsx
|
|
322
|
+
const container = useDropContainer({
|
|
323
|
+
containerId: "backlog",
|
|
324
|
+
group: "cards",
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
return <div {...container}>{cards}</div>;
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Use drop containers for boards, multiple lists, columns, and empty containers
|
|
331
|
+
that should accept items. A container does not imply orientation by itself.
|
|
332
|
+
Sortable placement is determined from the registered items' layout and the
|
|
333
|
+
targeting behavior.
|
|
334
|
+
|
|
335
|
+
### useDragHandle
|
|
336
|
+
|
|
337
|
+
`useDragHandle` marks an element as the drag handle for the nearest draggable or
|
|
338
|
+
sortable element.
|
|
339
|
+
|
|
340
|
+
```tsx
|
|
341
|
+
const handle = useDragHandle<HTMLButtonElement>();
|
|
342
|
+
|
|
343
|
+
return (
|
|
344
|
+
<button {...handle} type="button" aria-label="Drag item">
|
|
345
|
+
Drag
|
|
346
|
+
</button>
|
|
347
|
+
);
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
When a draggable contains a handle, pointer dragging starts from the handle.
|
|
351
|
+
This helps keep other interactive children usable.
|
|
352
|
+
|
|
353
|
+
### useRemeasureDropTargets
|
|
354
|
+
|
|
355
|
+
`useRemeasureDropTargets` returns a function for explicitly remeasuring targets
|
|
356
|
+
after layout changes during a drag.
|
|
357
|
+
|
|
358
|
+
```tsx
|
|
359
|
+
const remeasureDropTargets = useRemeasureDropTargets();
|
|
360
|
+
|
|
361
|
+
requestAnimationFrame(() => {
|
|
362
|
+
remeasureDropTargets({ group: "tree" });
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
It accepts no argument, a target id, an array of target ids, or `{ group }`.
|
|
367
|
+
Use it intentionally for expand/collapse, grouped trees, or drag-state layout
|
|
368
|
+
changes. It is not needed for normal cleanup and should not run on every render.
|
|
369
|
+
Sortable preview movement does not automatically remeasure a group; call this
|
|
370
|
+
function when an app-owned layout change needs to affect targeting.
|
|
371
|
+
|
|
372
|
+
## Sortable Behavior
|
|
373
|
+
|
|
374
|
+
Sortable preview is transient. The runtime may move DOM temporarily to show
|
|
375
|
+
where an item would land, then restore or clear that preview as the drag ends.
|
|
376
|
+
Preview movement does not trigger full target remeasurement. The app still
|
|
377
|
+
commits data on drop, using placement derived from the current preview DOM
|
|
378
|
+
order. React rendering should then reflect the final data.
|
|
379
|
+
|
|
380
|
+
Examples may rerender a full list for simplicity, but granular state management,
|
|
381
|
+
external stores, server commits, and imperative rendering strategies are
|
|
382
|
+
compatible. The package does not require React state.
|
|
383
|
+
|
|
384
|
+
```tsx
|
|
385
|
+
<DragProvider
|
|
386
|
+
onDrop={({ draggableId, sortablePlacement }) => {
|
|
387
|
+
const placement = sortablePlacement;
|
|
388
|
+
|
|
389
|
+
if (!placement) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
setItems((items) => reorderData(items, draggableId, placement));
|
|
394
|
+
}}
|
|
395
|
+
>
|
|
396
|
+
{/* sortable items */}
|
|
397
|
+
</DragProvider>
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
`reorderData` is app code. It should translate `draggableId`,
|
|
401
|
+
`placement.containerId`, `placement.targetDraggableId`, `placement.side`,
|
|
402
|
+
`placement.previousDraggableId`, and `placement.nextDraggableId` into your data
|
|
403
|
+
shape.
|
|
404
|
+
|
|
405
|
+
## Drag Overlays
|
|
406
|
+
|
|
407
|
+
`dragOverlay` creates a visual representation during drag.
|
|
408
|
+
|
|
409
|
+
```tsx
|
|
410
|
+
<DragProvider
|
|
411
|
+
dragOverlay={({ dragState }) => (
|
|
412
|
+
<div className="dragOverlay">{dragState.draggableId}</div>
|
|
413
|
+
)}
|
|
414
|
+
>
|
|
415
|
+
{children}
|
|
416
|
+
</DragProvider>
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
The overlay input includes:
|
|
420
|
+
|
|
421
|
+
- `dragState`: item id, group, source rect, start pointer, and current pointer.
|
|
422
|
+
- `phase`: `"dragging"` or `"released"`.
|
|
423
|
+
- `finish`: call this when a kept release overlay should be removed.
|
|
424
|
+
|
|
425
|
+
Overlay rendering is app-owned. `DragProvider` mounts overlay content for the
|
|
426
|
+
dragging phase and, when `keepOverlayOnDrop` is enabled, replaces it once for
|
|
427
|
+
the released phase. Pointer movement updates the package-owned overlay host
|
|
428
|
+
imperatively and does not call `dragOverlay` by default.
|
|
429
|
+
|
|
430
|
+
The package owns overlay hosting, movement, cleanup, and measurement for
|
|
431
|
+
targeting and modifiers. It measures overlay content on mount/replacement and
|
|
432
|
+
uses `ResizeObserver` when available to remeasure content size changes. Dynamic
|
|
433
|
+
overlay content should use overlay-local state, component effects, lifecycle
|
|
434
|
+
callbacks feeding a subscription/external store, or another app-owned update
|
|
435
|
+
path rather than relying on `dragOverlay` being called for every pointer move.
|
|
436
|
+
|
|
437
|
+
## Targeting And Modifiers
|
|
438
|
+
|
|
439
|
+
Targeting algorithms choose among measured drop targets. Targeting constraints
|
|
440
|
+
filter targets before an algorithm runs. Modifiers transform movement during a
|
|
441
|
+
drag.
|
|
442
|
+
|
|
443
|
+
Custom targeting algorithms receive `pointerPosition`, `overlayRect`, and
|
|
444
|
+
measured targets. Constraints receive `pointerPosition`, `overlayRect`, and one
|
|
445
|
+
candidate target. Algorithms and constraints choose their own geometry:
|
|
446
|
+
pointer helpers use the pointer position, while `centerToCenter` computes the
|
|
447
|
+
overlay center from `overlayRect`.
|
|
448
|
+
|
|
449
|
+
The React package re-exports targeting helpers such as `pointerToCenter`,
|
|
450
|
+
`centerToCenter`, `pointerToRectDistance`, `getDistanceToRect`, and
|
|
451
|
+
`maxPointerDistanceToRect` / `maxOverlayCenterDistanceToRect`, plus modifier
|
|
452
|
+
helpers. Sortable placement remains separate from targeting: the configured
|
|
453
|
+
targeting algorithm chooses the active target, and sortable only chooses
|
|
454
|
+
before/after placement relative to that target.
|
|
455
|
+
|
|
456
|
+
- `lockToXAxis()`
|
|
457
|
+
- `lockToYAxis()`
|
|
458
|
+
- `restrictToContainer(refOrResolver)`
|
|
459
|
+
|
|
460
|
+
`restrictToContainer` accepts either a React ref object or a resolver function.
|
|
461
|
+
Use the DOM package directly when you are building non-React integrations or
|
|
462
|
+
imperative DOM behavior.
|
|
463
|
+
|
|
464
|
+
### Memoize Modifiers And Composed Refs
|
|
465
|
+
|
|
466
|
+
Modifier factories such as `restrictToContainer(...)` return modifier objects.
|
|
467
|
+
When passing modifiers to `DragProvider`, create the array with `useMemo` so the
|
|
468
|
+
runtime is not reconfigured with a new array every render.
|
|
469
|
+
|
|
470
|
+
`composeRefs(...)` returns a ref callback. When the composed ref is used on a
|
|
471
|
+
registered draggable, droppable, sortable, or drop container element, memoize it
|
|
472
|
+
with `useMemo` so React does not clear and reassign the ref on every render.
|
|
473
|
+
|
|
474
|
+
```tsx
|
|
475
|
+
const modifiers = useMemo(
|
|
476
|
+
() => [restrictToContainer(containerRef)],
|
|
477
|
+
[],
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
const combinedRef = useMemo(
|
|
481
|
+
() => composeRefs(localRef, droppable.ref),
|
|
482
|
+
[droppable.ref],
|
|
483
|
+
);
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Custom targeting algorithms receive `pointerPosition`, `overlayRect`, and a
|
|
487
|
+
list of measured `dropTargets`. Each target has `dropTargetId` and
|
|
488
|
+
`dropTargetRect`. Custom constraints receive one candidate `dropTarget` and
|
|
489
|
+
return whether it should be eligible.
|
|
490
|
+
|
|
491
|
+
## Accessibility
|
|
492
|
+
|
|
493
|
+
The runtime has keyboard dragging support through `keyboardConfiguration`.
|
|
494
|
+
When keyboard dragging is enabled, draggable and sortable hooks add keyboard
|
|
495
|
+
props to registered elements.
|
|
496
|
+
|
|
497
|
+
Accessibility still depends on the app's markup and product behavior:
|
|
498
|
+
|
|
499
|
+
- Prefer button or focusable drag handles with clear labels.
|
|
500
|
+
- Do not hijack editable controls or unrelated interactive children.
|
|
501
|
+
- Provide domain-specific instructions for what dragging does.
|
|
502
|
+
- Use `announcements` when you want the provider to render polite live-region
|
|
503
|
+
updates from lifecycle callbacks. `announcements.onDragUpdate` runs for
|
|
504
|
+
active-drop-target transitions and dedupes repeated messages to avoid
|
|
505
|
+
live-region spam and provider rerenders; use lifecycle `onDragUpdate` for
|
|
506
|
+
per-frame side effects.
|
|
507
|
+
- Do not assume this package alone provides complete screen-reader drag-and-drop
|
|
508
|
+
support for your domain.
|
|
509
|
+
|
|
510
|
+
## Cleanup And Effects
|
|
511
|
+
|
|
512
|
+
Hooks attach input props and register DOM nodes through refs and lifecycle
|
|
513
|
+
effects. Users should not manually clean runtime entries for normal React
|
|
514
|
+
mount/unmount.
|
|
515
|
+
|
|
516
|
+
`DragProvider` owns runtime lifetime and disposes the runtime when it unmounts.
|
|
517
|
+
`useDropContainer` also cleans its DOM binding on effect cleanup.
|
|
518
|
+
`useDraggable`, `useDroppable`, and `useSortable` rely on React prop/ref updates
|
|
519
|
+
for normal mount, unmount, and element replacement behavior.
|
|
520
|
+
|
|
521
|
+
`useRemeasureDropTargets` is for layout changes, not cleanup. The runtime also
|
|
522
|
+
prunes disconnected targets on drag-critical paths such as remeasurement.
|
|
523
|
+
|
|
524
|
+
## Examples
|
|
525
|
+
|
|
526
|
+
React examples live in `apps/react-web/src/react`:
|
|
527
|
+
|
|
528
|
+
- Basic drag: moves one item between two droppable containers with an overlay.
|
|
529
|
+
- Dropzone list: uses generated droppable insertion lines for list reordering.
|
|
530
|
+
- Sortable list: uses `useSortable` and commits order from sortable placement.
|
|
531
|
+
- Kanban: combines sortable columns, sortable cards, drop containers, overlays,
|
|
532
|
+
and a container modifier.
|
|
533
|
+
- Grouped drag and drop: mixes parent sorting, child dragging, custom targets,
|
|
534
|
+
expansion state, and explicit remeasurement.
|
|
535
|
+
- Tree: uses app-defined tree projection, generated targets, custom targeting,
|
|
536
|
+
and app-owned hierarchy updates.
|
|
537
|
+
|
|
538
|
+
## API Reference
|
|
539
|
+
|
|
540
|
+
- `DragProvider`: creates/configures the DOM runtime for a React subtree.
|
|
541
|
+
Important props include lifecycle callbacks, overlay rendering, targeting,
|
|
542
|
+
constraints, modifiers, pointer/keyboard configuration, announcements, and
|
|
543
|
+
`keepOverlayOnDrop`.
|
|
544
|
+
- `useDraggable(options)`: registers a drag source. Important options:
|
|
545
|
+
`draggableId`, `group`. Returns ref and input props.
|
|
546
|
+
- `useDroppable(options)`: registers a drop target. Important options:
|
|
547
|
+
`dropTargetId`, `group`, `containerId`. Returns a ref prop.
|
|
548
|
+
- `useSortable(options)`: registers a sortable item as both source and target.
|
|
549
|
+
Important options: `draggableId`, `group`, `containerId`. Returns ref and input
|
|
550
|
+
props.
|
|
551
|
+
- `useDropContainer(options)`: registers a placement-aware container. Important
|
|
552
|
+
options: `containerId`, `group`. Returns a ref prop.
|
|
553
|
+
- `useDragHandle()`: returns the handle attribute props for a handle element.
|
|
554
|
+
- `useRemeasureDropTargets()`: returns a function for remeasuring all targets,
|
|
555
|
+
one target, multiple targets, or a group.
|
|
556
|
+
- `composeRefs`: helper for combining the refs returned by hooks with app refs.
|
|
557
|
+
- `lockToXAxis`, `lockToYAxis`: DOM movement modifiers re-exported by React.
|
|
558
|
+
- `restrictToContainer`: React-friendly container-bound modifier.
|
|
559
|
+
|
|
560
|
+
Important event/helper types are re-exported from the React package, including
|
|
561
|
+
`DragStartEvent`, `DragUpdateEvent`, `DragEndEvent`, `DropEvent`,
|
|
562
|
+
`DragSource`, `DragEndResult`, `DragLifecycleHelpers`, `DragState`,
|
|
563
|
+
`DragOverlayPhase`, `SortableDropPlacement`, `RemeasureDropTargetsInput`,
|
|
564
|
+
`PointerConfiguration`, `KeyboardConfiguration`, `KeyboardCommand`, `DropTarget`,
|
|
565
|
+
targeting types, geometry types, and modifier types.
|
|
566
|
+
|
|
567
|
+
## When To Use DOM Directly
|
|
568
|
+
|
|
569
|
+
Use `@mk-drag-and-drop/dom` directly when:
|
|
570
|
+
|
|
571
|
+
- You are not using React.
|
|
572
|
+
- You are integrating with another rendering framework.
|
|
573
|
+
- You are managing DOM imperatively.
|
|
574
|
+
- You are building a lower-level adapter.
|
|
575
|
+
|
|
576
|
+
Use `@mk-drag-and-drop/react` when:
|
|
577
|
+
|
|
578
|
+
- Your UI is rendered with React.
|
|
579
|
+
- You want ref-based hooks for registration.
|
|
580
|
+
- You want provider-based runtime lifetime and lifecycle integration.
|
|
581
|
+
|
|
582
|
+
## Development
|
|
583
|
+
|
|
584
|
+
Package scripts in this workspace:
|
|
585
|
+
|
|
586
|
+
```bash
|
|
587
|
+
pnpm --filter @mk-drag-and-drop/react... build
|
|
588
|
+
pnpm --filter @mk-drag-and-drop/react lint
|
|
589
|
+
pnpm --filter @mk-drag-and-drop/react test
|
|
590
|
+
pnpm --filter react-web build
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
The React example app also has `dev` and `preview` scripts, but they start local
|
|
594
|
+
Vite processes and are not required for package verification.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { DragRect } from "@mk-drag-and-drop/dom";
|
|
3
|
+
import type { DragOverlayPhase, DragState } from "@mk-drag-and-drop/dom/integration";
|
|
4
|
+
export type DragOverlayInput = {
|
|
5
|
+
dragState: DragState;
|
|
6
|
+
phase: DragOverlayPhase;
|
|
7
|
+
finish: () => void;
|
|
8
|
+
};
|
|
9
|
+
export type DragOverlayHostHandle = {
|
|
10
|
+
move: (dragState: DragState) => void;
|
|
11
|
+
};
|
|
12
|
+
export declare const DragOverlayHost: import("react").NamedExoticComponent<{
|
|
13
|
+
dragState: DragState;
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
contentId: number;
|
|
16
|
+
onHostReady?: (host: DragOverlayHostHandle | null) => void;
|
|
17
|
+
onOverlayRectChange?: (overlayRect: DragRect | null) => void;
|
|
18
|
+
}>;
|