dnd-block-tree 0.5.0 → 1.0.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 +30 -3
- package/dist/index.d.mts +8 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.js +61 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +61 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -13,11 +13,11 @@ function extractBlockId(zoneId) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
// src/core/collision.ts
|
|
16
|
-
function computeCollisionScores(droppableContainers, collisionRect) {
|
|
16
|
+
function computeCollisionScores(droppableContainers, collisionRect, snapshotRects) {
|
|
17
17
|
const pointerX = collisionRect.left + collisionRect.width / 2;
|
|
18
18
|
const pointerY = collisionRect.top + collisionRect.height / 2;
|
|
19
19
|
const candidates = droppableContainers.map((container) => {
|
|
20
|
-
const rect = container.rect.current;
|
|
20
|
+
const rect = snapshotRects?.get(container.id) ?? container.rect.current;
|
|
21
21
|
if (!rect) return null;
|
|
22
22
|
const distanceToTop = Math.abs(pointerY - rect.top);
|
|
23
23
|
const distanceToBottom = Math.abs(pointerY - rect.bottom);
|
|
@@ -27,16 +27,17 @@ function computeCollisionScores(droppableContainers, collisionRect) {
|
|
|
27
27
|
const isWithinX = pointerX >= rect.left && pointerX <= rect.right;
|
|
28
28
|
let horizontalScore = 0;
|
|
29
29
|
if (isWithinX) {
|
|
30
|
-
horizontalScore = -rect.left * 0.
|
|
30
|
+
horizontalScore = Math.abs(pointerX - rect.left) * 0.3;
|
|
31
31
|
} else {
|
|
32
32
|
const distanceToZone = pointerX < rect.left ? rect.left - pointerX : pointerX - rect.right;
|
|
33
|
-
horizontalScore =
|
|
33
|
+
horizontalScore = distanceToZone * 2;
|
|
34
34
|
}
|
|
35
35
|
return {
|
|
36
36
|
id: container.id,
|
|
37
37
|
data: {
|
|
38
38
|
droppableContainer: container,
|
|
39
|
-
value: edgeDistance + bias + horizontalScore
|
|
39
|
+
value: edgeDistance + bias + horizontalScore,
|
|
40
|
+
left: rect.left
|
|
40
41
|
}
|
|
41
42
|
};
|
|
42
43
|
}).filter((c) => c !== null);
|
|
@@ -55,14 +56,14 @@ var weightedVerticalCollision = ({
|
|
|
55
56
|
const candidates = computeCollisionScores(droppableContainers, collisionRect);
|
|
56
57
|
return candidates.slice(0, 1);
|
|
57
58
|
};
|
|
58
|
-
function createStickyCollision(threshold = 15) {
|
|
59
|
+
function createStickyCollision(threshold = 15, snapshotRef) {
|
|
59
60
|
let currentZoneId = null;
|
|
60
61
|
const detector = ({
|
|
61
62
|
droppableContainers,
|
|
62
63
|
collisionRect
|
|
63
64
|
}) => {
|
|
64
65
|
if (!collisionRect) return [];
|
|
65
|
-
const candidates = computeCollisionScores(droppableContainers, collisionRect);
|
|
66
|
+
const candidates = computeCollisionScores(droppableContainers, collisionRect, snapshotRef?.current);
|
|
66
67
|
if (candidates.length === 0) return [];
|
|
67
68
|
const bestCandidate = candidates[0];
|
|
68
69
|
const bestScore = bestCandidate.data.value;
|
|
@@ -70,7 +71,11 @@ function createStickyCollision(threshold = 15) {
|
|
|
70
71
|
const currentCandidate = candidates.find((c) => c.id === currentZoneId);
|
|
71
72
|
if (currentCandidate) {
|
|
72
73
|
const currentScore = currentCandidate.data.value;
|
|
73
|
-
|
|
74
|
+
const currentLeft = currentCandidate.data.left;
|
|
75
|
+
const bestLeft = bestCandidate.data.left;
|
|
76
|
+
const crossDepth = Math.abs(currentLeft - bestLeft) > 20;
|
|
77
|
+
const effectiveThreshold = crossDepth ? threshold * 0.25 : threshold;
|
|
78
|
+
if (currentScore - bestScore < effectiveThreshold) {
|
|
74
79
|
return [currentCandidate];
|
|
75
80
|
}
|
|
76
81
|
}
|
|
@@ -232,6 +237,9 @@ function DropZoneComponent({
|
|
|
232
237
|
);
|
|
233
238
|
}
|
|
234
239
|
var DropZone = memo(DropZoneComponent);
|
|
240
|
+
function GhostPreview({ children }) {
|
|
241
|
+
return /* @__PURE__ */ jsx("div", { "data-dnd-ghost": true, className: "opacity-50", style: { pointerEvents: "none" }, children });
|
|
242
|
+
}
|
|
235
243
|
function DraggableBlock({
|
|
236
244
|
block,
|
|
237
245
|
children,
|
|
@@ -318,7 +326,7 @@ function TreeRenderer({
|
|
|
318
326
|
}
|
|
319
327
|
const GhostRenderer = draggedBlock ? renderers[draggedBlock.type] : null;
|
|
320
328
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
321
|
-
ghostBeforeThis && GhostRenderer && /* @__PURE__ */ jsx(
|
|
329
|
+
ghostBeforeThis && GhostRenderer && /* @__PURE__ */ jsx(GhostPreview, { children: GhostRenderer({
|
|
322
330
|
block: draggedBlock,
|
|
323
331
|
isDragging: true,
|
|
324
332
|
depth
|
|
@@ -396,7 +404,7 @@ function TreeRenderer({
|
|
|
396
404
|
}),
|
|
397
405
|
showGhostHere && previewPosition.index >= filteredBlocks.length && draggedBlock && (() => {
|
|
398
406
|
const GhostRenderer = renderers[draggedBlock.type];
|
|
399
|
-
return GhostRenderer ? /* @__PURE__ */ jsx(
|
|
407
|
+
return GhostRenderer ? /* @__PURE__ */ jsx(GhostPreview, { children: GhostRenderer({
|
|
400
408
|
block: draggedBlock,
|
|
401
409
|
isDragging: true,
|
|
402
410
|
depth
|
|
@@ -648,6 +656,23 @@ function reparentBlockIndex(state, activeId, targetZone, containerTypes = [], or
|
|
|
648
656
|
}
|
|
649
657
|
if (dragged.id === zoneTargetId) return state;
|
|
650
658
|
const oldList = byParent.get(oldParentId) ?? [];
|
|
659
|
+
const currentIndexInOldParent = oldList.indexOf(dragged.id);
|
|
660
|
+
const preNewList = byParent.get(newParentId) ?? [];
|
|
661
|
+
let targetIndex;
|
|
662
|
+
if (isInto) {
|
|
663
|
+
targetIndex = 0;
|
|
664
|
+
} else if (isEnd) {
|
|
665
|
+
targetIndex = preNewList.length;
|
|
666
|
+
} else {
|
|
667
|
+
const idx = preNewList.indexOf(zoneTargetId);
|
|
668
|
+
targetIndex = idx === -1 ? preNewList.length : isAfter ? idx + 1 : idx;
|
|
669
|
+
}
|
|
670
|
+
if (oldParentId === newParentId && currentIndexInOldParent !== -1) {
|
|
671
|
+
const adjustedTarget = targetIndex > currentIndexInOldParent ? targetIndex - 1 : targetIndex;
|
|
672
|
+
if (adjustedTarget === currentIndexInOldParent) {
|
|
673
|
+
return state;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
651
676
|
const filtered = oldList.filter((id) => id !== dragged.id);
|
|
652
677
|
byParent.set(oldParentId, filtered);
|
|
653
678
|
const newList = [...byParent.get(newParentId) ?? []];
|
|
@@ -660,10 +685,6 @@ function reparentBlockIndex(state, activeId, targetZone, containerTypes = [], or
|
|
|
660
685
|
const idx = newList.indexOf(zoneTargetId);
|
|
661
686
|
insertIndex = idx === -1 ? newList.length : isAfter ? idx + 1 : idx;
|
|
662
687
|
}
|
|
663
|
-
const currentIndex = newList.indexOf(dragged.id);
|
|
664
|
-
if (dragged.parentId === newParentId && currentIndex === insertIndex) {
|
|
665
|
-
return state;
|
|
666
|
-
}
|
|
667
688
|
newList.splice(insertIndex, 0, dragged.id);
|
|
668
689
|
byParent.set(newParentId, newList);
|
|
669
690
|
let newOrder = dragged.order;
|
|
@@ -833,7 +854,20 @@ function BlockTree({
|
|
|
833
854
|
const cachedReorderRef = useRef(null);
|
|
834
855
|
const fromPositionRef = useRef(null);
|
|
835
856
|
const draggedIdsRef = useRef([]);
|
|
836
|
-
const
|
|
857
|
+
const snapshotRectsRef = useRef(null);
|
|
858
|
+
const needsResnapshot = useRef(false);
|
|
859
|
+
const stickyCollisionRef = useRef(createStickyCollision(20, snapshotRectsRef));
|
|
860
|
+
const snapshotZoneRects = useCallback(() => {
|
|
861
|
+
const root = rootRef.current;
|
|
862
|
+
if (!root) return;
|
|
863
|
+
const zones = root.querySelectorAll("[data-zone-id]");
|
|
864
|
+
const map = /* @__PURE__ */ new Map();
|
|
865
|
+
zones.forEach((el) => {
|
|
866
|
+
const id = el.getAttribute("data-zone-id");
|
|
867
|
+
if (id) map.set(id, el.getBoundingClientRect());
|
|
868
|
+
});
|
|
869
|
+
snapshotRectsRef.current = map;
|
|
870
|
+
}, []);
|
|
837
871
|
const [, forceRender] = useReducer((x) => x + 1, 0);
|
|
838
872
|
const debouncedSetVirtual = useRef(
|
|
839
873
|
debounce((newBlocks) => {
|
|
@@ -842,6 +876,7 @@ function BlockTree({
|
|
|
842
876
|
} else {
|
|
843
877
|
stateRef.current.virtualState = null;
|
|
844
878
|
}
|
|
879
|
+
needsResnapshot.current = true;
|
|
845
880
|
forceRender();
|
|
846
881
|
}, previewDebounce)
|
|
847
882
|
).current;
|
|
@@ -980,6 +1015,13 @@ function BlockTree({
|
|
|
980
1015
|
}
|
|
981
1016
|
lastClickedIdRef.current = blockId;
|
|
982
1017
|
}, [multiSelect, selectedIds, setSelectedIds, visibleBlockIds]);
|
|
1018
|
+
useEffect(() => {
|
|
1019
|
+
if (!needsResnapshot.current || !stateRef.current.isDragging) return;
|
|
1020
|
+
needsResnapshot.current = false;
|
|
1021
|
+
requestAnimationFrame(() => {
|
|
1022
|
+
snapshotZoneRects();
|
|
1023
|
+
});
|
|
1024
|
+
});
|
|
983
1025
|
useEffect(() => {
|
|
984
1026
|
if (!keyboardNavigation || !focusedIdRef.current || !rootRef.current) return;
|
|
985
1027
|
const el = rootRef.current.querySelector(`[data-block-id="${focusedIdRef.current}"]`);
|
|
@@ -1032,6 +1074,7 @@ function BlockTree({
|
|
|
1032
1074
|
stateRef.current.isDragging = true;
|
|
1033
1075
|
initialBlocksRef.current = [...blocks];
|
|
1034
1076
|
cachedReorderRef.current = null;
|
|
1077
|
+
needsResnapshot.current = true;
|
|
1035
1078
|
forceRender();
|
|
1036
1079
|
}, [blocks, canDrag, onDragStart, multiSelect, selectedIds, setSelectedIds, visibleBlockIds, sensorConfig?.hapticFeedback]);
|
|
1037
1080
|
const handleDragMove = useCallback((event) => {
|
|
@@ -1107,6 +1150,7 @@ function BlockTree({
|
|
|
1107
1150
|
cachedReorderRef.current = null;
|
|
1108
1151
|
initialBlocksRef.current = [];
|
|
1109
1152
|
fromPositionRef.current = null;
|
|
1153
|
+
snapshotRectsRef.current = null;
|
|
1110
1154
|
forceRender();
|
|
1111
1155
|
return;
|
|
1112
1156
|
}
|
|
@@ -1146,6 +1190,7 @@ function BlockTree({
|
|
|
1146
1190
|
initialBlocksRef.current = [];
|
|
1147
1191
|
fromPositionRef.current = null;
|
|
1148
1192
|
draggedIdsRef.current = [];
|
|
1193
|
+
snapshotRectsRef.current = null;
|
|
1149
1194
|
if (cached && onChange) {
|
|
1150
1195
|
onChange(cached.reorderedBlocks);
|
|
1151
1196
|
}
|
|
@@ -1174,6 +1219,7 @@ function BlockTree({
|
|
|
1174
1219
|
initialBlocksRef.current = [];
|
|
1175
1220
|
fromPositionRef.current = null;
|
|
1176
1221
|
draggedIdsRef.current = [];
|
|
1222
|
+
snapshotRectsRef.current = null;
|
|
1177
1223
|
forceRender();
|
|
1178
1224
|
}, [blocks, debouncedSetVirtual, debouncedDragMove, onDragCancel, onDragEnd]);
|
|
1179
1225
|
const handleHover = useCallback((zoneId, _parentId) => {
|