dnd-block-tree 0.4.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 +482 -14
- package/dist/index.d.mts +111 -4
- package/dist/index.d.ts +111 -4
- package/dist/index.js +311 -48
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +307 -50
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useDroppable, useSensors, useSensor, PointerSensor, TouchSensor, KeyboardSensor, DragOverlay as DragOverlay$1, DndContext, useDraggable } from '@dnd-kit/core';
|
|
2
|
-
import { memo, useCallback, useEffect, Fragment, useMemo, useRef, useReducer, createContext,
|
|
2
|
+
import { memo, useCallback, useEffect, Fragment, useMemo, useRef, useReducer, useState, createContext, useLayoutEffect, useContext } from 'react';
|
|
3
3
|
import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
// src/core/types.ts
|
|
@@ -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
|
}
|
|
@@ -133,7 +138,7 @@ function useConfiguredSensors(config = {}) {
|
|
|
133
138
|
distance: activationDistance
|
|
134
139
|
};
|
|
135
140
|
touchConstraint = {
|
|
136
|
-
delay: 200,
|
|
141
|
+
delay: config.longPressDelay ?? 200,
|
|
137
142
|
tolerance: 5
|
|
138
143
|
};
|
|
139
144
|
}
|
|
@@ -195,6 +200,11 @@ function debounce(fn, delay) {
|
|
|
195
200
|
function generateId() {
|
|
196
201
|
return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
197
202
|
}
|
|
203
|
+
function triggerHaptic(durationMs = 10) {
|
|
204
|
+
if (typeof navigator !== "undefined" && typeof navigator.vibrate === "function") {
|
|
205
|
+
navigator.vibrate(durationMs);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
198
208
|
function DropZoneComponent({
|
|
199
209
|
id,
|
|
200
210
|
parentId,
|
|
@@ -227,6 +237,9 @@ function DropZoneComponent({
|
|
|
227
237
|
);
|
|
228
238
|
}
|
|
229
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
|
+
}
|
|
230
243
|
function DraggableBlock({
|
|
231
244
|
block,
|
|
232
245
|
children,
|
|
@@ -276,10 +289,15 @@ function TreeRenderer({
|
|
|
276
289
|
draggedBlock,
|
|
277
290
|
focusedId,
|
|
278
291
|
selectedIds,
|
|
279
|
-
onBlockClick
|
|
292
|
+
onBlockClick,
|
|
293
|
+
animation,
|
|
294
|
+
virtualVisibleIds
|
|
280
295
|
}) {
|
|
281
296
|
const items = blocksByParent.get(parentId) ?? [];
|
|
282
|
-
|
|
297
|
+
let filteredBlocks = items.filter((block) => block.id !== activeId);
|
|
298
|
+
if (virtualVisibleIds && depth === 0) {
|
|
299
|
+
filteredBlocks = filteredBlocks.filter((block) => virtualVisibleIds.has(block.id));
|
|
300
|
+
}
|
|
283
301
|
const showGhostHere = previewPosition?.parentId === parentId && draggedBlock;
|
|
284
302
|
const containerClass = depth === 0 ? rootClassName : indentClassName;
|
|
285
303
|
return /* @__PURE__ */ jsxs("div", { className: containerClass, style: { minWidth: 0 }, children: [
|
|
@@ -308,7 +326,7 @@ function TreeRenderer({
|
|
|
308
326
|
}
|
|
309
327
|
const GhostRenderer = draggedBlock ? renderers[draggedBlock.type] : null;
|
|
310
328
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
311
|
-
ghostBeforeThis && GhostRenderer && /* @__PURE__ */ jsx(
|
|
329
|
+
ghostBeforeThis && GhostRenderer && /* @__PURE__ */ jsx(GhostPreview, { children: GhostRenderer({
|
|
312
330
|
block: draggedBlock,
|
|
313
331
|
isDragging: true,
|
|
314
332
|
depth
|
|
@@ -323,7 +341,11 @@ function TreeRenderer({
|
|
|
323
341
|
onBlockClick,
|
|
324
342
|
children: ({ isDragging }) => {
|
|
325
343
|
if (isContainer) {
|
|
326
|
-
const
|
|
344
|
+
const expandStyle = animation?.expandDuration ? {
|
|
345
|
+
transition: `opacity ${animation.expandDuration}ms ${animation.easing ?? "ease"}`,
|
|
346
|
+
opacity: isExpanded ? 1 : 0
|
|
347
|
+
} : void 0;
|
|
348
|
+
const childContent = isExpanded ? /* @__PURE__ */ jsx("div", { style: expandStyle, children: /* @__PURE__ */ jsx(
|
|
327
349
|
TreeRenderer,
|
|
328
350
|
{
|
|
329
351
|
blocks,
|
|
@@ -345,7 +367,9 @@ function TreeRenderer({
|
|
|
345
367
|
draggedBlock,
|
|
346
368
|
focusedId,
|
|
347
369
|
selectedIds,
|
|
348
|
-
onBlockClick
|
|
370
|
+
onBlockClick,
|
|
371
|
+
animation,
|
|
372
|
+
virtualVisibleIds
|
|
349
373
|
}
|
|
350
374
|
) }) : null;
|
|
351
375
|
return Renderer({
|
|
@@ -380,7 +404,7 @@ function TreeRenderer({
|
|
|
380
404
|
}),
|
|
381
405
|
showGhostHere && previewPosition.index >= filteredBlocks.length && draggedBlock && (() => {
|
|
382
406
|
const GhostRenderer = renderers[draggedBlock.type];
|
|
383
|
-
return GhostRenderer ? /* @__PURE__ */ jsx(
|
|
407
|
+
return GhostRenderer ? /* @__PURE__ */ jsx(GhostPreview, { children: GhostRenderer({
|
|
384
408
|
block: draggedBlock,
|
|
385
409
|
isDragging: true,
|
|
386
410
|
depth
|
|
@@ -632,6 +656,23 @@ function reparentBlockIndex(state, activeId, targetZone, containerTypes = [], or
|
|
|
632
656
|
}
|
|
633
657
|
if (dragged.id === zoneTargetId) return state;
|
|
634
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
|
+
}
|
|
635
676
|
const filtered = oldList.filter((id) => id !== dragged.id);
|
|
636
677
|
byParent.set(oldParentId, filtered);
|
|
637
678
|
const newList = [...byParent.get(newParentId) ?? []];
|
|
@@ -644,10 +685,6 @@ function reparentBlockIndex(state, activeId, targetZone, containerTypes = [], or
|
|
|
644
685
|
const idx = newList.indexOf(zoneTargetId);
|
|
645
686
|
insertIndex = idx === -1 ? newList.length : isAfter ? idx + 1 : idx;
|
|
646
687
|
}
|
|
647
|
-
const currentIndex = newList.indexOf(dragged.id);
|
|
648
|
-
if (dragged.parentId === newParentId && currentIndex === insertIndex) {
|
|
649
|
-
return state;
|
|
650
|
-
}
|
|
651
688
|
newList.splice(insertIndex, 0, dragged.id);
|
|
652
689
|
byParent.set(newParentId, newList);
|
|
653
690
|
let newOrder = dragged.order;
|
|
@@ -784,18 +821,21 @@ function BlockTree({
|
|
|
784
821
|
canDrop,
|
|
785
822
|
collisionDetection,
|
|
786
823
|
sensors: sensorConfig,
|
|
824
|
+
animation,
|
|
787
825
|
initialExpanded,
|
|
788
826
|
orderingStrategy = "integer",
|
|
789
827
|
maxDepth,
|
|
790
828
|
keyboardNavigation = false,
|
|
791
829
|
multiSelect = false,
|
|
792
830
|
selectedIds: externalSelectedIds,
|
|
793
|
-
onSelectionChange
|
|
831
|
+
onSelectionChange,
|
|
832
|
+
virtualize
|
|
794
833
|
}) {
|
|
795
834
|
const sensors = useConfiguredSensors({
|
|
796
835
|
activationDistance: sensorConfig?.activationDistance ?? activationDistance,
|
|
797
836
|
activationDelay: sensorConfig?.activationDelay,
|
|
798
|
-
tolerance: sensorConfig?.tolerance
|
|
837
|
+
tolerance: sensorConfig?.tolerance,
|
|
838
|
+
longPressDelay: sensorConfig?.longPressDelay
|
|
799
839
|
});
|
|
800
840
|
const initialExpandedMap = useMemo(
|
|
801
841
|
() => computeInitialExpanded(blocks, containerTypes, initialExpanded),
|
|
@@ -814,7 +854,20 @@ function BlockTree({
|
|
|
814
854
|
const cachedReorderRef = useRef(null);
|
|
815
855
|
const fromPositionRef = useRef(null);
|
|
816
856
|
const draggedIdsRef = useRef([]);
|
|
817
|
-
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
|
+
}, []);
|
|
818
871
|
const [, forceRender] = useReducer((x) => x + 1, 0);
|
|
819
872
|
const debouncedSetVirtual = useRef(
|
|
820
873
|
debounce((newBlocks) => {
|
|
@@ -823,6 +876,7 @@ function BlockTree({
|
|
|
823
876
|
} else {
|
|
824
877
|
stateRef.current.virtualState = null;
|
|
825
878
|
}
|
|
879
|
+
needsResnapshot.current = true;
|
|
826
880
|
forceRender();
|
|
827
881
|
}, previewDebounce)
|
|
828
882
|
).current;
|
|
@@ -961,6 +1015,13 @@ function BlockTree({
|
|
|
961
1015
|
}
|
|
962
1016
|
lastClickedIdRef.current = blockId;
|
|
963
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
|
+
});
|
|
964
1025
|
useEffect(() => {
|
|
965
1026
|
if (!keyboardNavigation || !focusedIdRef.current || !rootRef.current) return;
|
|
966
1027
|
const el = rootRef.current.querySelector(`[data-block-id="${focusedIdRef.current}"]`);
|
|
@@ -1006,12 +1067,16 @@ function BlockTree({
|
|
|
1006
1067
|
setSelectedIds(/* @__PURE__ */ new Set([id]));
|
|
1007
1068
|
}
|
|
1008
1069
|
}
|
|
1070
|
+
if (sensorConfig?.hapticFeedback) {
|
|
1071
|
+
triggerHaptic();
|
|
1072
|
+
}
|
|
1009
1073
|
stateRef.current.activeId = id;
|
|
1010
1074
|
stateRef.current.isDragging = true;
|
|
1011
1075
|
initialBlocksRef.current = [...blocks];
|
|
1012
1076
|
cachedReorderRef.current = null;
|
|
1077
|
+
needsResnapshot.current = true;
|
|
1013
1078
|
forceRender();
|
|
1014
|
-
}, [blocks, canDrag, onDragStart, multiSelect, selectedIds, setSelectedIds, visibleBlockIds]);
|
|
1079
|
+
}, [blocks, canDrag, onDragStart, multiSelect, selectedIds, setSelectedIds, visibleBlockIds, sensorConfig?.hapticFeedback]);
|
|
1015
1080
|
const handleDragMove = useCallback((event) => {
|
|
1016
1081
|
if (!onDragMove) return;
|
|
1017
1082
|
const id = stateRef.current.activeId;
|
|
@@ -1085,6 +1150,7 @@ function BlockTree({
|
|
|
1085
1150
|
cachedReorderRef.current = null;
|
|
1086
1151
|
initialBlocksRef.current = [];
|
|
1087
1152
|
fromPositionRef.current = null;
|
|
1153
|
+
snapshotRectsRef.current = null;
|
|
1088
1154
|
forceRender();
|
|
1089
1155
|
return;
|
|
1090
1156
|
}
|
|
@@ -1124,6 +1190,7 @@ function BlockTree({
|
|
|
1124
1190
|
initialBlocksRef.current = [];
|
|
1125
1191
|
fromPositionRef.current = null;
|
|
1126
1192
|
draggedIdsRef.current = [];
|
|
1193
|
+
snapshotRectsRef.current = null;
|
|
1127
1194
|
if (cached && onChange) {
|
|
1128
1195
|
onChange(cached.reorderedBlocks);
|
|
1129
1196
|
}
|
|
@@ -1152,6 +1219,7 @@ function BlockTree({
|
|
|
1152
1219
|
initialBlocksRef.current = [];
|
|
1153
1220
|
fromPositionRef.current = null;
|
|
1154
1221
|
draggedIdsRef.current = [];
|
|
1222
|
+
snapshotRectsRef.current = null;
|
|
1155
1223
|
forceRender();
|
|
1156
1224
|
}, [blocks, debouncedSetVirtual, debouncedDragMove, onDragCancel, onDragEnd]);
|
|
1157
1225
|
const handleHover = useCallback((zoneId, _parentId) => {
|
|
@@ -1205,6 +1273,61 @@ function BlockTree({
|
|
|
1205
1273
|
forceRender();
|
|
1206
1274
|
}, [blocks, onExpandChange]);
|
|
1207
1275
|
toggleExpandRef.current = handleToggleExpand;
|
|
1276
|
+
const virtualContainerRef = useRef(null);
|
|
1277
|
+
const [virtualScroll, setVirtualScroll] = useState({ scrollTop: 0, clientHeight: 0 });
|
|
1278
|
+
useEffect(() => {
|
|
1279
|
+
if (!virtualize) return;
|
|
1280
|
+
const el = virtualContainerRef.current;
|
|
1281
|
+
if (!el) return;
|
|
1282
|
+
setVirtualScroll({ scrollTop: el.scrollTop, clientHeight: el.clientHeight });
|
|
1283
|
+
const onScroll = () => {
|
|
1284
|
+
setVirtualScroll({ scrollTop: el.scrollTop, clientHeight: el.clientHeight });
|
|
1285
|
+
};
|
|
1286
|
+
el.addEventListener("scroll", onScroll, { passive: true });
|
|
1287
|
+
return () => el.removeEventListener("scroll", onScroll);
|
|
1288
|
+
}, [virtualize]);
|
|
1289
|
+
const virtualResult = useMemo(() => {
|
|
1290
|
+
if (!virtualize) return null;
|
|
1291
|
+
const { itemHeight, overscan = 5 } = virtualize;
|
|
1292
|
+
const { scrollTop, clientHeight } = virtualScroll;
|
|
1293
|
+
const totalHeight = visibleBlockIds.length * itemHeight;
|
|
1294
|
+
const startRaw = Math.floor(scrollTop / itemHeight);
|
|
1295
|
+
const visibleCount = Math.ceil(clientHeight / itemHeight);
|
|
1296
|
+
const start = Math.max(0, startRaw - overscan);
|
|
1297
|
+
const end = Math.min(visibleBlockIds.length - 1, startRaw + visibleCount + overscan);
|
|
1298
|
+
const offsetY = start * itemHeight;
|
|
1299
|
+
const visibleSet = /* @__PURE__ */ new Set();
|
|
1300
|
+
for (let i = start; i <= end; i++) {
|
|
1301
|
+
visibleSet.add(visibleBlockIds[i]);
|
|
1302
|
+
}
|
|
1303
|
+
return { totalHeight, offsetY, visibleSet };
|
|
1304
|
+
}, [virtualize, virtualScroll, visibleBlockIds]);
|
|
1305
|
+
const treeContent = /* @__PURE__ */ jsx(
|
|
1306
|
+
TreeRenderer,
|
|
1307
|
+
{
|
|
1308
|
+
blocks,
|
|
1309
|
+
blocksByParent,
|
|
1310
|
+
parentId: null,
|
|
1311
|
+
activeId: stateRef.current.activeId,
|
|
1312
|
+
expandedMap: stateRef.current.expandedMap,
|
|
1313
|
+
renderers,
|
|
1314
|
+
containerTypes,
|
|
1315
|
+
onHover: handleHover,
|
|
1316
|
+
onToggleExpand: handleToggleExpand,
|
|
1317
|
+
dropZoneClassName,
|
|
1318
|
+
dropZoneActiveClassName,
|
|
1319
|
+
indentClassName,
|
|
1320
|
+
rootClassName: className,
|
|
1321
|
+
canDrag,
|
|
1322
|
+
previewPosition,
|
|
1323
|
+
draggedBlock,
|
|
1324
|
+
focusedId: keyboardNavigation ? focusedIdRef.current : void 0,
|
|
1325
|
+
selectedIds: multiSelect ? selectedIds : void 0,
|
|
1326
|
+
onBlockClick: multiSelect ? handleBlockClick : void 0,
|
|
1327
|
+
animation,
|
|
1328
|
+
virtualVisibleIds: virtualResult?.visibleSet ?? null
|
|
1329
|
+
}
|
|
1330
|
+
);
|
|
1208
1331
|
return /* @__PURE__ */ jsxs(
|
|
1209
1332
|
DndContext,
|
|
1210
1333
|
{
|
|
@@ -1216,7 +1339,20 @@ function BlockTree({
|
|
|
1216
1339
|
onDragEnd: handleDragEnd,
|
|
1217
1340
|
onDragCancel: handleDragCancel,
|
|
1218
1341
|
children: [
|
|
1219
|
-
/* @__PURE__ */ jsx(
|
|
1342
|
+
virtualize ? /* @__PURE__ */ jsx(
|
|
1343
|
+
"div",
|
|
1344
|
+
{
|
|
1345
|
+
ref: (el) => {
|
|
1346
|
+
virtualContainerRef.current = el;
|
|
1347
|
+
rootRef.current = el;
|
|
1348
|
+
},
|
|
1349
|
+
className,
|
|
1350
|
+
style: { minWidth: 0, overflow: "auto", position: "relative" },
|
|
1351
|
+
onKeyDown: keyboardNavigation ? handleKeyDown : void 0,
|
|
1352
|
+
role: keyboardNavigation ? "tree" : void 0,
|
|
1353
|
+
children: /* @__PURE__ */ jsx("div", { style: { height: virtualResult.totalHeight, position: "relative" }, children: /* @__PURE__ */ jsx("div", { style: { position: "absolute", top: virtualResult.offsetY, left: 0, right: 0 }, children: treeContent }) })
|
|
1354
|
+
}
|
|
1355
|
+
) : /* @__PURE__ */ jsx(
|
|
1220
1356
|
"div",
|
|
1221
1357
|
{
|
|
1222
1358
|
ref: rootRef,
|
|
@@ -1224,30 +1360,7 @@ function BlockTree({
|
|
|
1224
1360
|
style: { minWidth: 0 },
|
|
1225
1361
|
onKeyDown: keyboardNavigation ? handleKeyDown : void 0,
|
|
1226
1362
|
role: keyboardNavigation ? "tree" : void 0,
|
|
1227
|
-
children:
|
|
1228
|
-
TreeRenderer,
|
|
1229
|
-
{
|
|
1230
|
-
blocks,
|
|
1231
|
-
blocksByParent,
|
|
1232
|
-
parentId: null,
|
|
1233
|
-
activeId: stateRef.current.activeId,
|
|
1234
|
-
expandedMap: stateRef.current.expandedMap,
|
|
1235
|
-
renderers,
|
|
1236
|
-
containerTypes,
|
|
1237
|
-
onHover: handleHover,
|
|
1238
|
-
onToggleExpand: handleToggleExpand,
|
|
1239
|
-
dropZoneClassName,
|
|
1240
|
-
dropZoneActiveClassName,
|
|
1241
|
-
indentClassName,
|
|
1242
|
-
rootClassName: className,
|
|
1243
|
-
canDrag,
|
|
1244
|
-
previewPosition,
|
|
1245
|
-
draggedBlock,
|
|
1246
|
-
focusedId: keyboardNavigation ? focusedIdRef.current : void 0,
|
|
1247
|
-
selectedIds: multiSelect ? selectedIds : void 0,
|
|
1248
|
-
onBlockClick: multiSelect ? handleBlockClick : void 0
|
|
1249
|
-
}
|
|
1250
|
-
)
|
|
1363
|
+
children: treeContent
|
|
1251
1364
|
}
|
|
1252
1365
|
),
|
|
1253
1366
|
/* @__PURE__ */ jsx(DragOverlay, { activeBlock, selectedCount: multiSelect ? selectedIds.size : 0, children: dragOverlay })
|
|
@@ -1255,6 +1368,16 @@ function BlockTree({
|
|
|
1255
1368
|
}
|
|
1256
1369
|
);
|
|
1257
1370
|
}
|
|
1371
|
+
function BlockTreeSSR({ fallback = null, ...props }) {
|
|
1372
|
+
const [mounted, setMounted] = useState(false);
|
|
1373
|
+
useEffect(() => {
|
|
1374
|
+
setMounted(true);
|
|
1375
|
+
}, []);
|
|
1376
|
+
if (!mounted) {
|
|
1377
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: fallback });
|
|
1378
|
+
}
|
|
1379
|
+
return /* @__PURE__ */ jsx(BlockTree, { ...props });
|
|
1380
|
+
}
|
|
1258
1381
|
function blockReducer(state, action, containerTypes = [], orderingStrategy = "integer", maxDepth) {
|
|
1259
1382
|
switch (action.type) {
|
|
1260
1383
|
case "ADD_ITEM": {
|
|
@@ -1659,7 +1782,141 @@ function useBlockHistory(initialBlocks, options = {}) {
|
|
|
1659
1782
|
canRedo: state.future.length > 0
|
|
1660
1783
|
};
|
|
1661
1784
|
}
|
|
1785
|
+
function useLayoutAnimation(containerRef, options = {}) {
|
|
1786
|
+
const {
|
|
1787
|
+
duration = 200,
|
|
1788
|
+
easing = "ease",
|
|
1789
|
+
selector = "[data-block-id]"
|
|
1790
|
+
} = options;
|
|
1791
|
+
const prevPositions = useRef(/* @__PURE__ */ new Map());
|
|
1792
|
+
useLayoutEffect(() => {
|
|
1793
|
+
const container = containerRef.current;
|
|
1794
|
+
if (!container) return;
|
|
1795
|
+
const children = container.querySelectorAll(selector);
|
|
1796
|
+
const currentPositions = /* @__PURE__ */ new Map();
|
|
1797
|
+
children.forEach((el) => {
|
|
1798
|
+
const id = el.dataset.blockId;
|
|
1799
|
+
if (id) {
|
|
1800
|
+
currentPositions.set(id, el.getBoundingClientRect());
|
|
1801
|
+
}
|
|
1802
|
+
});
|
|
1803
|
+
children.forEach((el) => {
|
|
1804
|
+
const htmlEl = el;
|
|
1805
|
+
const id = htmlEl.dataset.blockId;
|
|
1806
|
+
if (!id) return;
|
|
1807
|
+
const prev = prevPositions.current.get(id);
|
|
1808
|
+
const curr = currentPositions.get(id);
|
|
1809
|
+
if (!prev || !curr) return;
|
|
1810
|
+
const deltaY = prev.top - curr.top;
|
|
1811
|
+
const deltaX = prev.left - curr.left;
|
|
1812
|
+
if (deltaY === 0 && deltaX === 0) return;
|
|
1813
|
+
htmlEl.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
|
|
1814
|
+
htmlEl.style.transition = "none";
|
|
1815
|
+
requestAnimationFrame(() => {
|
|
1816
|
+
htmlEl.style.transition = `transform ${duration}ms ${easing}`;
|
|
1817
|
+
htmlEl.style.transform = "";
|
|
1818
|
+
const onEnd = () => {
|
|
1819
|
+
htmlEl.style.transition = "";
|
|
1820
|
+
htmlEl.removeEventListener("transitionend", onEnd);
|
|
1821
|
+
};
|
|
1822
|
+
htmlEl.addEventListener("transitionend", onEnd);
|
|
1823
|
+
});
|
|
1824
|
+
});
|
|
1825
|
+
prevPositions.current = currentPositions;
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
function useVirtualTree({
|
|
1829
|
+
containerRef,
|
|
1830
|
+
itemCount,
|
|
1831
|
+
itemHeight,
|
|
1832
|
+
overscan = 5
|
|
1833
|
+
}) {
|
|
1834
|
+
const [scrollTop, setScrollTop] = useState(0);
|
|
1835
|
+
const [containerHeight, setContainerHeight] = useState(0);
|
|
1836
|
+
const rafId = useRef(0);
|
|
1837
|
+
const handleScroll = useCallback(() => {
|
|
1838
|
+
cancelAnimationFrame(rafId.current);
|
|
1839
|
+
rafId.current = requestAnimationFrame(() => {
|
|
1840
|
+
const el = containerRef.current;
|
|
1841
|
+
if (el) {
|
|
1842
|
+
setScrollTop(el.scrollTop);
|
|
1843
|
+
setContainerHeight(el.clientHeight);
|
|
1844
|
+
}
|
|
1845
|
+
});
|
|
1846
|
+
}, [containerRef]);
|
|
1847
|
+
useEffect(() => {
|
|
1848
|
+
const el = containerRef.current;
|
|
1849
|
+
if (!el) return;
|
|
1850
|
+
setScrollTop(el.scrollTop);
|
|
1851
|
+
setContainerHeight(el.clientHeight);
|
|
1852
|
+
el.addEventListener("scroll", handleScroll, { passive: true });
|
|
1853
|
+
return () => {
|
|
1854
|
+
el.removeEventListener("scroll", handleScroll);
|
|
1855
|
+
cancelAnimationFrame(rafId.current);
|
|
1856
|
+
};
|
|
1857
|
+
}, [containerRef, handleScroll]);
|
|
1858
|
+
const totalHeight = itemCount * itemHeight;
|
|
1859
|
+
const startRaw = Math.floor(scrollTop / itemHeight);
|
|
1860
|
+
const visibleCount = Math.ceil(containerHeight / itemHeight);
|
|
1861
|
+
const start = Math.max(0, startRaw - overscan);
|
|
1862
|
+
const end = Math.min(itemCount - 1, startRaw + visibleCount + overscan);
|
|
1863
|
+
const offsetY = start * itemHeight;
|
|
1864
|
+
return {
|
|
1865
|
+
visibleRange: { start, end },
|
|
1866
|
+
totalHeight,
|
|
1867
|
+
offsetY
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
// src/utils/serialization.ts
|
|
1872
|
+
function flatToNested(blocks) {
|
|
1873
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
1874
|
+
for (const block of blocks) {
|
|
1875
|
+
const key = block.parentId ?? null;
|
|
1876
|
+
const list = byParent.get(key);
|
|
1877
|
+
if (list) {
|
|
1878
|
+
list.push(block);
|
|
1879
|
+
} else {
|
|
1880
|
+
byParent.set(key, [block]);
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
for (const list of byParent.values()) {
|
|
1884
|
+
list.sort((a, b) => {
|
|
1885
|
+
if (typeof a.order === "string" && typeof b.order === "string") {
|
|
1886
|
+
return a.order < b.order ? -1 : a.order > b.order ? 1 : 0;
|
|
1887
|
+
}
|
|
1888
|
+
return Number(a.order) - Number(b.order);
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
function buildChildren(parentId) {
|
|
1892
|
+
const siblings = byParent.get(parentId) ?? [];
|
|
1893
|
+
return siblings.map((block) => {
|
|
1894
|
+
const { parentId: _p, order: _o, ...rest } = block;
|
|
1895
|
+
return {
|
|
1896
|
+
...rest,
|
|
1897
|
+
children: buildChildren(block.id)
|
|
1898
|
+
};
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
return buildChildren(null);
|
|
1902
|
+
}
|
|
1903
|
+
function nestedToFlat(nested) {
|
|
1904
|
+
const result = [];
|
|
1905
|
+
function walk(nodes, parentId) {
|
|
1906
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
1907
|
+
const { children, ...rest } = nodes[i];
|
|
1908
|
+
result.push({
|
|
1909
|
+
...rest,
|
|
1910
|
+
parentId,
|
|
1911
|
+
order: i
|
|
1912
|
+
});
|
|
1913
|
+
walk(children, rest.id);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
walk(nested, null);
|
|
1917
|
+
return result;
|
|
1918
|
+
}
|
|
1662
1919
|
|
|
1663
|
-
export { BlockTree, DragOverlay, DropZone, TreeRenderer, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, compareFractionalKeys, computeNormalizedIndex, createBlockState, createStickyCollision, createTreeState, debounce, deleteBlockAndDescendants, extractBlockId, extractUUID, generateId, generateInitialKeys, generateKeyBetween, generateNKeysBetween, getBlockDepth, getDescendantIds, getDropZoneType, getSensorConfig, getSubtreeDepth, initFractionalOrder, reparentBlockIndex, reparentMultipleBlocks, useBlockHistory, useConfiguredSensors, weightedVerticalCollision };
|
|
1920
|
+
export { BlockTree, BlockTreeSSR, DragOverlay, DropZone, TreeRenderer, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, compareFractionalKeys, computeNormalizedIndex, createBlockState, createStickyCollision, createTreeState, debounce, deleteBlockAndDescendants, extractBlockId, extractUUID, flatToNested, generateId, generateInitialKeys, generateKeyBetween, generateNKeysBetween, getBlockDepth, getDescendantIds, getDropZoneType, getSensorConfig, getSubtreeDepth, initFractionalOrder, nestedToFlat, reparentBlockIndex, reparentMultipleBlocks, triggerHaptic, useBlockHistory, useConfiguredSensors, useLayoutAnimation, useVirtualTree, weightedVerticalCollision };
|
|
1664
1921
|
//# sourceMappingURL=index.mjs.map
|
|
1665
1922
|
//# sourceMappingURL=index.mjs.map
|