@mk-drag-and-drop/react 0.2.0 → 0.3.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 +53 -9
- 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-remeasure-overlay.d.ts +1 -0
- package/dist/hooks/use-remeasure-overlay.js +12 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -52,6 +52,7 @@ The React root export includes:
|
|
|
52
52
|
- `useSortable`, `UseSortableOptions`, `UseSortableResult`
|
|
53
53
|
- `useDragHandle`, `UseDragHandleResult`
|
|
54
54
|
- `useRemeasureDropTargets`
|
|
55
|
+
- `useRemeasureOverlay`
|
|
55
56
|
- `composeRefs`
|
|
56
57
|
- React-friendly `restrictToContainer`, `ReactRestrictToContainerInput`
|
|
57
58
|
- DOM targeting helpers, modifier helpers, lifecycle/source/result types,
|
|
@@ -222,6 +223,7 @@ type DragUpdateEvent = {
|
|
|
222
223
|
draggableId: string;
|
|
223
224
|
source: DragSource;
|
|
224
225
|
pointerPosition: DragPoint;
|
|
226
|
+
overlayRect: DragRect | null;
|
|
225
227
|
activeDropTargetId: string | null;
|
|
226
228
|
previousDropTargetId: string | null;
|
|
227
229
|
};
|
|
@@ -231,6 +233,7 @@ type DragEndEvent = {
|
|
|
231
233
|
source: DragSource;
|
|
232
234
|
result: DragEndResult;
|
|
233
235
|
dropTargetId: string | null;
|
|
236
|
+
overlayRect: DragRect | null;
|
|
234
237
|
};
|
|
235
238
|
|
|
236
239
|
type DropEvent = {
|
|
@@ -241,6 +244,15 @@ type DropEvent = {
|
|
|
241
244
|
};
|
|
242
245
|
```
|
|
243
246
|
|
|
247
|
+
`DragUpdateEvent.overlayRect` is the current viewport-space rectangle for the
|
|
248
|
+
drag overlay. `DragEndEvent.overlayRect` is the final viewport-space rectangle
|
|
249
|
+
at the moment the drag ends. React receives these DOM lifecycle event types
|
|
250
|
+
unchanged. The field is `null` when no drag overlay is configured. When an
|
|
251
|
+
overlay exists, it is based on cached overlay measurement plus modified pointer
|
|
252
|
+
movement, with the translated source rect as the fallback before overlay content
|
|
253
|
+
has been measured. React does not add overlay DOM measurement on every pointer
|
|
254
|
+
move.
|
|
255
|
+
|
|
244
256
|
`onDrop` only runs for a valid successful drop. `onDragEnd.result` distinguishes
|
|
245
257
|
successful drops, normal releases with no target, stale/invalid targets, and
|
|
246
258
|
cancellations such as Escape or pointercancel. Plain drops are id-only.
|
|
@@ -390,6 +402,22 @@ every render. Sortable preview movement does not automatically remeasure a
|
|
|
390
402
|
group; call this function when an app-owned layout change needs to affect
|
|
391
403
|
targeting.
|
|
392
404
|
|
|
405
|
+
### useRemeasureOverlay
|
|
406
|
+
|
|
407
|
+
`useRemeasureOverlay` returns a function for manually refreshing the current
|
|
408
|
+
overlay measurement. It must be used inside `DragProvider`. Calling the returned
|
|
409
|
+
function with no mounted overlay is safe and does nothing.
|
|
410
|
+
|
|
411
|
+
```tsx
|
|
412
|
+
const remeasureOverlay = useRemeasureOverlay();
|
|
413
|
+
|
|
414
|
+
remeasureOverlay();
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Normal overlay size changes are observed automatically with `ResizeObserver`
|
|
418
|
+
when available. Use this hook when the app knows visual geometry changed outside
|
|
419
|
+
that automatic path, such as after a CSS transform or transition.
|
|
420
|
+
|
|
393
421
|
## Sortable Behavior
|
|
394
422
|
|
|
395
423
|
Sortable preview is transient. The runtime may move DOM temporarily to show
|
|
@@ -429,8 +457,10 @@ shape.
|
|
|
429
457
|
|
|
430
458
|
```tsx
|
|
431
459
|
<DragProvider
|
|
432
|
-
dragOverlay={({ dragState }) => (
|
|
433
|
-
<div className="dragOverlay"
|
|
460
|
+
dragOverlay={({ dragState, remeasureOverlay }) => (
|
|
461
|
+
<div className="dragOverlay" onTransitionEnd={remeasureOverlay}>
|
|
462
|
+
{dragState.draggableId}
|
|
463
|
+
</div>
|
|
434
464
|
)}
|
|
435
465
|
>
|
|
436
466
|
{children}
|
|
@@ -441,6 +471,7 @@ The overlay input includes:
|
|
|
441
471
|
|
|
442
472
|
- `dragState`: item id, group, source rect, start pointer, and current pointer.
|
|
443
473
|
- `phase`: `"dragging"` or `"released"`.
|
|
474
|
+
- `remeasureOverlay`: manually refreshes the cached overlay measurement.
|
|
444
475
|
- `removeOverlay`: present only for `"released"` overlays in manual release
|
|
445
476
|
mode; call it when app-owned release animation should remove the overlay.
|
|
446
477
|
|
|
@@ -456,10 +487,21 @@ provider, controller, or runtime teardown API.
|
|
|
456
487
|
|
|
457
488
|
The package owns overlay hosting, movement, release, and measurement for
|
|
458
489
|
targeting and modifiers. It measures overlay content on mount/replacement and
|
|
459
|
-
uses `ResizeObserver` when available to remeasure content size changes.
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
490
|
+
uses `ResizeObserver` when available to remeasure content size changes. The
|
|
491
|
+
React package mirrors DOM behavior: overlay movement remains package-managed and
|
|
492
|
+
imperative, so pointer movement should not require React state updates on every
|
|
493
|
+
move.
|
|
494
|
+
|
|
495
|
+
Use the overlay input `remeasureOverlay` helper when overlay-local UI knows its
|
|
496
|
+
visual bounds changed. It uses the same provider/runtime path as
|
|
497
|
+
`useRemeasureOverlay` and avoids reaching into internal runtime or provider
|
|
498
|
+
state. CSS transforms can change visual bounds without necessarily triggering
|
|
499
|
+
`ResizeObserver`.
|
|
500
|
+
|
|
501
|
+
Dynamic overlay content should use overlay-local state, component effects,
|
|
502
|
+
lifecycle callbacks feeding a subscription/external store, or another app-owned
|
|
503
|
+
update path rather than relying on `dragOverlay` being called for every pointer
|
|
504
|
+
move.
|
|
463
505
|
|
|
464
506
|
## Targeting And Modifiers
|
|
465
507
|
|
|
@@ -548,9 +590,9 @@ mount/unmount.
|
|
|
548
590
|
`useDraggable`, `useDroppable`, and `useSortable` rely on React prop/ref updates
|
|
549
591
|
for normal mount, unmount, and element replacement behavior.
|
|
550
592
|
|
|
551
|
-
`useRemeasureDropTargets`
|
|
552
|
-
runtime also prunes disconnected targets on
|
|
553
|
-
remeasurement.
|
|
593
|
+
`useRemeasureDropTargets` and `useRemeasureOverlay` are for geometry changes,
|
|
594
|
+
not resource release. The runtime also prunes disconnected targets on
|
|
595
|
+
drag-critical paths such as remeasurement.
|
|
554
596
|
|
|
555
597
|
## Examples
|
|
556
598
|
|
|
@@ -584,6 +626,8 @@ React examples live in `apps/react-web/src/react`:
|
|
|
584
626
|
- `useDragHandle()`: returns the handle attribute props for a handle element.
|
|
585
627
|
- `useRemeasureDropTargets()`: returns a function for remeasuring all targets,
|
|
586
628
|
one target, multiple targets, or a group.
|
|
629
|
+
- `useRemeasureOverlay()`: returns a function for manually refreshing the
|
|
630
|
+
currently mounted overlay measurement.
|
|
587
631
|
- `composeRefs`: helper for combining the refs returned by hooks with app refs.
|
|
588
632
|
- `lockToXAxis`, `lockToYAxis`: DOM movement modifiers re-exported by React.
|
|
589
633
|
- `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 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,6 @@ 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 { useRemeasureOverlay } from "./hooks/use-remeasure-overlay.js";
|
|
11
12
|
export { useSortable, type UseSortableOptions, type UseSortableResult, } from "./hooks/use-sortable.js";
|
|
12
13
|
export { composeRefs } from "./utils/compose-refs.js";
|
package/dist/index.js
CHANGED
|
@@ -6,5 +6,6 @@ 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 { useRemeasureOverlay } from "./hooks/use-remeasure-overlay.js";
|
|
9
10
|
export { useSortable, } from "./hooks/use-sortable.js";
|
|
10
11
|
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.3.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.3.0"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
45
|
"react": "^19.0.0"
|