@mk-drag-and-drop/react 0.2.0 → 0.4.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/README.md +81 -7
- package/dist/drag-context.d.ts +1 -0
- package/dist/drag-overlay.d.ts +3 -0
- package/dist/drag-overlay.js +20 -10
- package/dist/drag-provider.js +7 -1
- package/dist/hooks/use-recompute-active-drag.d.ts +1 -0
- package/dist/hooks/use-recompute-active-drag.js +12 -0
- package/dist/hooks/use-remeasure-overlay.d.ts +1 -0
- package/dist/hooks/use-remeasure-overlay.js +12 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -52,6 +52,8 @@ The React root export includes:
|
|
|
52
52
|
- `useSortable`, `UseSortableOptions`, `UseSortableResult`
|
|
53
53
|
- `useDragHandle`, `UseDragHandleResult`
|
|
54
54
|
- `useRemeasureDropTargets`
|
|
55
|
+
- `useRecomputeActiveDrag`
|
|
56
|
+
- `useRemeasureOverlay`
|
|
55
57
|
- `composeRefs`
|
|
56
58
|
- React-friendly `restrictToContainer`, `ReactRestrictToContainerInput`
|
|
57
59
|
- DOM targeting helpers, modifier helpers, lifecycle/source/result types,
|
|
@@ -222,6 +224,7 @@ type DragUpdateEvent = {
|
|
|
222
224
|
draggableId: string;
|
|
223
225
|
source: DragSource;
|
|
224
226
|
pointerPosition: DragPoint;
|
|
227
|
+
overlayRect: DragRect | null;
|
|
225
228
|
activeDropTargetId: string | null;
|
|
226
229
|
previousDropTargetId: string | null;
|
|
227
230
|
};
|
|
@@ -231,6 +234,7 @@ type DragEndEvent = {
|
|
|
231
234
|
source: DragSource;
|
|
232
235
|
result: DragEndResult;
|
|
233
236
|
dropTargetId: string | null;
|
|
237
|
+
overlayRect: DragRect | null;
|
|
234
238
|
};
|
|
235
239
|
|
|
236
240
|
type DropEvent = {
|
|
@@ -241,6 +245,15 @@ type DropEvent = {
|
|
|
241
245
|
};
|
|
242
246
|
```
|
|
243
247
|
|
|
248
|
+
`DragUpdateEvent.overlayRect` is the current viewport-space rectangle for the
|
|
249
|
+
drag overlay. `DragEndEvent.overlayRect` is the final viewport-space rectangle
|
|
250
|
+
at the moment the drag ends. React receives these DOM lifecycle event types
|
|
251
|
+
unchanged. The field is `null` when no drag overlay is configured. When an
|
|
252
|
+
overlay exists, it is based on cached overlay measurement plus modified pointer
|
|
253
|
+
movement, with the translated source rect as the fallback before overlay content
|
|
254
|
+
has been measured. React does not add overlay DOM measurement on every pointer
|
|
255
|
+
move.
|
|
256
|
+
|
|
244
257
|
`onDrop` only runs for a valid successful drop. `onDragEnd.result` distinguishes
|
|
245
258
|
successful drops, normal releases with no target, stale/invalid targets, and
|
|
246
259
|
cancellations such as Escape or pointercancel. Plain drops are id-only.
|
|
@@ -390,6 +403,48 @@ every render. Sortable preview movement does not automatically remeasure a
|
|
|
390
403
|
group; call this function when an app-owned layout change needs to affect
|
|
391
404
|
targeting.
|
|
392
405
|
|
|
406
|
+
### useRecomputeActiveDrag
|
|
407
|
+
|
|
408
|
+
`useRecomputeActiveDrag` returns a function for rerunning the active drag update
|
|
409
|
+
path from the last pointer position. It must be used inside `DragProvider` and
|
|
410
|
+
is a thin wrapper over the DOM runtime operation. It follows the same update
|
|
411
|
+
path as DOM pointer movement. Calling the returned function with no active drag
|
|
412
|
+
is safe and does nothing.
|
|
413
|
+
|
|
414
|
+
```tsx
|
|
415
|
+
const recomputeActiveDrag = useRecomputeActiveDrag();
|
|
416
|
+
|
|
417
|
+
recomputeActiveDrag();
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Use it after external changes that require active targeting to run again without
|
|
421
|
+
a new pointer event. It does not remeasure drop targets. When target
|
|
422
|
+
measurements changed, call `useRemeasureDropTargets()` first.
|
|
423
|
+
|
|
424
|
+
```tsx
|
|
425
|
+
const remeasureDropTargets = useRemeasureDropTargets();
|
|
426
|
+
const recomputeActiveDrag = useRecomputeActiveDrag();
|
|
427
|
+
|
|
428
|
+
remeasureDropTargets({ group: "items" });
|
|
429
|
+
recomputeActiveDrag();
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### useRemeasureOverlay
|
|
433
|
+
|
|
434
|
+
`useRemeasureOverlay` returns a function for manually refreshing the current
|
|
435
|
+
overlay measurement. It must be used inside `DragProvider`. Calling the returned
|
|
436
|
+
function with no mounted overlay is safe and does nothing.
|
|
437
|
+
|
|
438
|
+
```tsx
|
|
439
|
+
const remeasureOverlay = useRemeasureOverlay();
|
|
440
|
+
|
|
441
|
+
remeasureOverlay();
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Normal overlay size changes are observed automatically with `ResizeObserver`
|
|
445
|
+
when available. Use this hook when the app knows visual geometry changed outside
|
|
446
|
+
that automatic path, such as after a CSS transform or transition.
|
|
447
|
+
|
|
393
448
|
## Sortable Behavior
|
|
394
449
|
|
|
395
450
|
Sortable preview is transient. The runtime may move DOM temporarily to show
|
|
@@ -429,8 +484,10 @@ shape.
|
|
|
429
484
|
|
|
430
485
|
```tsx
|
|
431
486
|
<DragProvider
|
|
432
|
-
dragOverlay={({ dragState }) => (
|
|
433
|
-
<div className="dragOverlay"
|
|
487
|
+
dragOverlay={({ dragState, remeasureOverlay }) => (
|
|
488
|
+
<div className="dragOverlay" onTransitionEnd={remeasureOverlay}>
|
|
489
|
+
{dragState.draggableId}
|
|
490
|
+
</div>
|
|
434
491
|
)}
|
|
435
492
|
>
|
|
436
493
|
{children}
|
|
@@ -441,6 +498,7 @@ The overlay input includes:
|
|
|
441
498
|
|
|
442
499
|
- `dragState`: item id, group, source rect, start pointer, and current pointer.
|
|
443
500
|
- `phase`: `"dragging"` or `"released"`.
|
|
501
|
+
- `remeasureOverlay`: manually refreshes the cached overlay measurement.
|
|
444
502
|
- `removeOverlay`: present only for `"released"` overlays in manual release
|
|
445
503
|
mode; call it when app-owned release animation should remove the overlay.
|
|
446
504
|
|
|
@@ -456,10 +514,21 @@ provider, controller, or runtime teardown API.
|
|
|
456
514
|
|
|
457
515
|
The package owns overlay hosting, movement, release, and measurement for
|
|
458
516
|
targeting and modifiers. It measures overlay content on mount/replacement and
|
|
459
|
-
uses `ResizeObserver` when available to remeasure content size changes.
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
517
|
+
uses `ResizeObserver` when available to remeasure content size changes. The
|
|
518
|
+
React package mirrors DOM behavior: overlay movement remains package-managed and
|
|
519
|
+
imperative, so pointer movement should not require React state updates on every
|
|
520
|
+
move.
|
|
521
|
+
|
|
522
|
+
Use the overlay input `remeasureOverlay` helper when overlay-local UI knows its
|
|
523
|
+
visual bounds changed. It uses the same provider/runtime path as
|
|
524
|
+
`useRemeasureOverlay` and avoids reaching into internal runtime or provider
|
|
525
|
+
state. CSS transforms can change visual bounds without necessarily triggering
|
|
526
|
+
`ResizeObserver`.
|
|
527
|
+
|
|
528
|
+
Dynamic overlay content should use overlay-local state, component effects,
|
|
529
|
+
lifecycle callbacks feeding a subscription/external store, or another app-owned
|
|
530
|
+
update path rather than relying on `dragOverlay` being called for every pointer
|
|
531
|
+
move.
|
|
463
532
|
|
|
464
533
|
## Targeting And Modifiers
|
|
465
534
|
|
|
@@ -548,7 +617,8 @@ mount/unmount.
|
|
|
548
617
|
`useDraggable`, `useDroppable`, and `useSortable` rely on React prop/ref updates
|
|
549
618
|
for normal mount, unmount, and element replacement behavior.
|
|
550
619
|
|
|
551
|
-
`useRemeasureDropTargets`
|
|
620
|
+
`useRemeasureDropTargets`, `useRecomputeActiveDrag`, and `useRemeasureOverlay`
|
|
621
|
+
are for active drag geometry or targeting changes, not resource release. The
|
|
552
622
|
runtime also prunes disconnected targets on drag-critical paths such as
|
|
553
623
|
remeasurement.
|
|
554
624
|
|
|
@@ -584,6 +654,10 @@ React examples live in `apps/react-web/src/react`:
|
|
|
584
654
|
- `useDragHandle()`: returns the handle attribute props for a handle element.
|
|
585
655
|
- `useRemeasureDropTargets()`: returns a function for remeasuring all targets,
|
|
586
656
|
one target, multiple targets, or a group.
|
|
657
|
+
- `useRecomputeActiveDrag()`: returns a function for rerunning active targeting
|
|
658
|
+
and drag update from the last pointer position without remeasuring targets.
|
|
659
|
+
- `useRemeasureOverlay()`: returns a function for manually refreshing the
|
|
660
|
+
currently mounted overlay measurement.
|
|
587
661
|
- `composeRefs`: helper for combining the refs returned by hooks with app refs.
|
|
588
662
|
- `lockToXAxis`, `lockToYAxis`: DOM movement modifiers re-exported by React.
|
|
589
663
|
- `restrictToContainer`: React-friendly container-bound modifier.
|
package/dist/drag-context.d.ts
CHANGED
|
@@ -2,5 +2,6 @@ import type { DragRuntimeScope } from "@mk-drag-and-drop/dom/integration";
|
|
|
2
2
|
export type DragContextValue = {
|
|
3
3
|
runtime: DragRuntimeScope;
|
|
4
4
|
keyboardDragEnabled: boolean;
|
|
5
|
+
remeasureOverlay: () => void;
|
|
5
6
|
};
|
|
6
7
|
export declare const DragContext: import("react").Context<DragContextValue | null>;
|
package/dist/drag-overlay.d.ts
CHANGED
|
@@ -5,13 +5,16 @@ export type DragOverlayInput = {
|
|
|
5
5
|
dragState: DragState;
|
|
6
6
|
} & ({
|
|
7
7
|
phase: Extract<DragOverlayPhase, "dragging">;
|
|
8
|
+
remeasureOverlay: () => void;
|
|
8
9
|
removeOverlay?: never;
|
|
9
10
|
} | {
|
|
10
11
|
phase: Extract<DragOverlayPhase, "released">;
|
|
12
|
+
remeasureOverlay: () => void;
|
|
11
13
|
removeOverlay: () => void;
|
|
12
14
|
});
|
|
13
15
|
export type DragOverlayHostHandle = {
|
|
14
16
|
move: (dragState: DragState) => void;
|
|
17
|
+
remeasure: () => void;
|
|
15
18
|
};
|
|
16
19
|
export declare const DragOverlayHost: import("react").NamedExoticComponent<{
|
|
17
20
|
dragState: DragState;
|
package/dist/drag-overlay.js
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { memo, useLayoutEffect, useRef, } from "react";
|
|
2
|
+
import { memo, useCallback, useLayoutEffect, useRef, } from "react";
|
|
3
3
|
export const DragOverlayHost = memo(function DragOverlayHost({ dragState, children, contentId, onHostReady, onOverlayRectChange, }) {
|
|
4
4
|
const overlayWrapperRef = useRef(null);
|
|
5
|
+
const measureOverlayElement = useCallback(() => {
|
|
6
|
+
const wrapper = overlayWrapperRef.current;
|
|
7
|
+
if (!wrapper) {
|
|
8
|
+
onOverlayRectChange?.(null);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const measuredElement = getMeasuredOverlayElement(wrapper);
|
|
12
|
+
if (!measuredElement.isConnected) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
onOverlayRectChange?.(domRectToDragRect(measuredElement.getBoundingClientRect()));
|
|
16
|
+
}, [onOverlayRectChange]);
|
|
5
17
|
useLayoutEffect(() => {
|
|
6
18
|
const wrapper = overlayWrapperRef.current;
|
|
7
19
|
if (!wrapper) {
|
|
@@ -12,26 +24,21 @@ export const DragOverlayHost = memo(function DragOverlayHost({ dragState, childr
|
|
|
12
24
|
move: (nextDragState) => {
|
|
13
25
|
moveOverlayWrapper(wrapper, nextDragState);
|
|
14
26
|
},
|
|
27
|
+
remeasure: measureOverlayElement,
|
|
15
28
|
};
|
|
16
29
|
onHostReady?.(host);
|
|
17
30
|
host.move(dragState);
|
|
18
31
|
return () => {
|
|
19
32
|
onHostReady?.(null);
|
|
20
33
|
};
|
|
21
|
-
}, [dragState, onHostReady]);
|
|
34
|
+
}, [dragState, measureOverlayElement, onHostReady]);
|
|
22
35
|
useLayoutEffect(() => {
|
|
23
36
|
const wrapper = overlayWrapperRef.current;
|
|
24
37
|
if (!wrapper) {
|
|
25
38
|
onOverlayRectChange?.(null);
|
|
26
39
|
return;
|
|
27
40
|
}
|
|
28
|
-
const measuredElement = wrapper
|
|
29
|
-
const measureOverlayElement = () => {
|
|
30
|
-
if (!measuredElement.isConnected) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
onOverlayRectChange?.(domRectToDragRect(measuredElement.getBoundingClientRect()));
|
|
34
|
-
};
|
|
41
|
+
const measuredElement = getMeasuredOverlayElement(wrapper);
|
|
35
42
|
measureOverlayElement();
|
|
36
43
|
if (typeof ResizeObserver === "undefined") {
|
|
37
44
|
return () => {
|
|
@@ -46,7 +53,7 @@ export const DragOverlayHost = memo(function DragOverlayHost({ dragState, childr
|
|
|
46
53
|
resizeObserver.disconnect();
|
|
47
54
|
onOverlayRectChange?.(null);
|
|
48
55
|
};
|
|
49
|
-
}, [contentId, onOverlayRectChange]);
|
|
56
|
+
}, [contentId, measureOverlayElement, onOverlayRectChange]);
|
|
50
57
|
return (_jsx("div", { ref: overlayWrapperRef, style: {
|
|
51
58
|
position: "fixed",
|
|
52
59
|
left: dragState.sourceRect.left,
|
|
@@ -58,6 +65,9 @@ export const DragOverlayHost = memo(function DragOverlayHost({ dragState, childr
|
|
|
58
65
|
transform: getOverlayTransform(dragState),
|
|
59
66
|
}, children: children }));
|
|
60
67
|
});
|
|
68
|
+
function getMeasuredOverlayElement(wrapper) {
|
|
69
|
+
return wrapper.firstElementChild ?? wrapper;
|
|
70
|
+
}
|
|
61
71
|
function moveOverlayWrapper(wrapper, dragState) {
|
|
62
72
|
wrapper.style.transform = getOverlayTransform(dragState);
|
|
63
73
|
}
|
package/dist/drag-provider.js
CHANGED
|
@@ -77,10 +77,14 @@ export function DragProvider({ children, announcements, dragOverlay, keyboardCon
|
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
79
|
const runtime = runtimeRef.current;
|
|
80
|
+
const remeasureOverlay = useCallback(() => {
|
|
81
|
+
overlayHostRef.current?.remeasure();
|
|
82
|
+
}, []);
|
|
80
83
|
const contextValue = useMemo(() => ({
|
|
81
84
|
runtime,
|
|
82
85
|
keyboardDragEnabled,
|
|
83
|
-
|
|
86
|
+
remeasureOverlay,
|
|
87
|
+
}), [keyboardDragEnabled, remeasureOverlay, runtime]);
|
|
84
88
|
useEffect(() => {
|
|
85
89
|
return () => {
|
|
86
90
|
runtime.releaseActiveDragResources();
|
|
@@ -193,12 +197,14 @@ export function DragProvider({ children, announcements, dragOverlay, keyboardCon
|
|
|
193
197
|
return {
|
|
194
198
|
dragState: overlayState.dragState,
|
|
195
199
|
phase: "released",
|
|
200
|
+
remeasureOverlay,
|
|
196
201
|
removeOverlay: clearOverlayHost,
|
|
197
202
|
};
|
|
198
203
|
}
|
|
199
204
|
return {
|
|
200
205
|
dragState: overlayState.dragState,
|
|
201
206
|
phase: "dragging",
|
|
207
|
+
remeasureOverlay,
|
|
202
208
|
};
|
|
203
209
|
}
|
|
204
210
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useRecomputeActiveDrag(): () => void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useCallback, useContext } from "react";
|
|
2
|
+
import { DragContext } from "../drag-context.js";
|
|
3
|
+
export function useRecomputeActiveDrag() {
|
|
4
|
+
const context = useContext(DragContext);
|
|
5
|
+
if (!context) {
|
|
6
|
+
throw new Error("useRecomputeActiveDrag must be used inside DragProvider");
|
|
7
|
+
}
|
|
8
|
+
const { runtime } = context;
|
|
9
|
+
return useCallback(() => {
|
|
10
|
+
runtime.recomputeActiveDrag();
|
|
11
|
+
}, [runtime]);
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useRemeasureOverlay(): () => void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useCallback, useContext } from "react";
|
|
2
|
+
import { DragContext } from "../drag-context.js";
|
|
3
|
+
export function useRemeasureOverlay() {
|
|
4
|
+
const context = useContext(DragContext);
|
|
5
|
+
if (!context) {
|
|
6
|
+
throw new Error("useRemeasureOverlay must be used inside DragProvider");
|
|
7
|
+
}
|
|
8
|
+
const { remeasureOverlay } = context;
|
|
9
|
+
return useCallback(() => {
|
|
10
|
+
remeasureOverlay();
|
|
11
|
+
}, [remeasureOverlay]);
|
|
12
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,5 +8,7 @@ export { useDraggable, type UseDraggableOptions, type UseDraggableResult, } from
|
|
|
8
8
|
export { useDropContainer, type UseDropContainerOptions, type UseDropContainerResult, } from "./hooks/use-drop-container.js";
|
|
9
9
|
export { useDroppable, type UseDroppableOptions, type UseDroppableResult, } from "./hooks/use-droppable.js";
|
|
10
10
|
export { useRemeasureDropTargets } from "./hooks/use-remeasure-drop-targets.js";
|
|
11
|
+
export { useRecomputeActiveDrag } from "./hooks/use-recompute-active-drag.js";
|
|
12
|
+
export { useRemeasureOverlay } from "./hooks/use-remeasure-overlay.js";
|
|
11
13
|
export { useSortable, type UseSortableOptions, type UseSortableResult, } from "./hooks/use-sortable.js";
|
|
12
14
|
export { composeRefs } from "./utils/compose-refs.js";
|
package/dist/index.js
CHANGED
|
@@ -6,5 +6,7 @@ export { useDraggable, } from "./hooks/use-draggable.js";
|
|
|
6
6
|
export { useDropContainer, } from "./hooks/use-drop-container.js";
|
|
7
7
|
export { useDroppable, } from "./hooks/use-droppable.js";
|
|
8
8
|
export { useRemeasureDropTargets } from "./hooks/use-remeasure-drop-targets.js";
|
|
9
|
+
export { useRecomputeActiveDrag } from "./hooks/use-recompute-active-drag.js";
|
|
10
|
+
export { useRemeasureOverlay } from "./hooks/use-remeasure-overlay.js";
|
|
9
11
|
export { useSortable, } from "./hooks/use-sortable.js";
|
|
10
12
|
export { composeRefs } from "./utils/compose-refs.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mk-drag-and-drop/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "React hooks and provider for the mk drag-and-drop DOM runtime.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://mkramer.dev",
|
|
@@ -37,8 +37,9 @@
|
|
|
37
37
|
"files": [
|
|
38
38
|
"dist"
|
|
39
39
|
],
|
|
40
|
+
"sideEffects": false,
|
|
40
41
|
"dependencies": {
|
|
41
|
-
"@mk-drag-and-drop/dom": "0.
|
|
42
|
+
"@mk-drag-and-drop/dom": "^0.4.0"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
45
|
"react": "^19.0.0"
|