dnd-block-tree 0.1.0 → 0.2.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 +167 -0
- package/dist/index.d.mts +198 -34
- package/dist/index.d.ts +198 -34
- package/dist/index.js +412 -118
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +413 -120
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -3
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, useRef, useReducer, createContext, useContext, useState
|
|
2
|
+
import { memo, useCallback, useEffect, Fragment, useMemo, useRef, useReducer, createContext, useContext, useState } 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,8 @@ function extractBlockId(zoneId) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
// src/core/collision.ts
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
collisionRect
|
|
19
|
-
}) => {
|
|
20
|
-
if (!collisionRect) return [];
|
|
16
|
+
function computeCollisionScores(droppableContainers, collisionRect) {
|
|
17
|
+
const pointerX = collisionRect.left + collisionRect.width / 2;
|
|
21
18
|
const pointerY = collisionRect.top + collisionRect.height / 2;
|
|
22
19
|
const candidates = droppableContainers.map((container) => {
|
|
23
20
|
const rect = container.rect.current;
|
|
@@ -27,11 +24,19 @@ var weightedVerticalCollision = ({
|
|
|
27
24
|
const edgeDistance = Math.min(distanceToTop, distanceToBottom);
|
|
28
25
|
const isBelowCenter = pointerY > rect.top + rect.height / 2;
|
|
29
26
|
const bias = isBelowCenter ? -5 : 0;
|
|
27
|
+
const isWithinX = pointerX >= rect.left && pointerX <= rect.right;
|
|
28
|
+
let horizontalScore = 0;
|
|
29
|
+
if (isWithinX) {
|
|
30
|
+
horizontalScore = -rect.left * 0.1;
|
|
31
|
+
} else {
|
|
32
|
+
const distanceToZone = pointerX < rect.left ? rect.left - pointerX : pointerX - rect.right;
|
|
33
|
+
horizontalScore = Math.min(distanceToZone, 50);
|
|
34
|
+
}
|
|
30
35
|
return {
|
|
31
36
|
id: container.id,
|
|
32
37
|
data: {
|
|
33
38
|
droppableContainer: container,
|
|
34
|
-
value: edgeDistance + bias
|
|
39
|
+
value: edgeDistance + bias + horizontalScore
|
|
35
40
|
}
|
|
36
41
|
};
|
|
37
42
|
}).filter((c) => c !== null);
|
|
@@ -40,8 +45,44 @@ var weightedVerticalCollision = ({
|
|
|
40
45
|
const bValue = b.data.value;
|
|
41
46
|
return aValue - bValue;
|
|
42
47
|
});
|
|
48
|
+
return candidates;
|
|
49
|
+
}
|
|
50
|
+
var weightedVerticalCollision = ({
|
|
51
|
+
droppableContainers,
|
|
52
|
+
collisionRect
|
|
53
|
+
}) => {
|
|
54
|
+
if (!collisionRect) return [];
|
|
55
|
+
const candidates = computeCollisionScores(droppableContainers, collisionRect);
|
|
43
56
|
return candidates.slice(0, 1);
|
|
44
57
|
};
|
|
58
|
+
function createStickyCollision(threshold = 15) {
|
|
59
|
+
let currentZoneId = null;
|
|
60
|
+
const detector = ({
|
|
61
|
+
droppableContainers,
|
|
62
|
+
collisionRect
|
|
63
|
+
}) => {
|
|
64
|
+
if (!collisionRect) return [];
|
|
65
|
+
const candidates = computeCollisionScores(droppableContainers, collisionRect);
|
|
66
|
+
if (candidates.length === 0) return [];
|
|
67
|
+
const bestCandidate = candidates[0];
|
|
68
|
+
const bestScore = bestCandidate.data.value;
|
|
69
|
+
if (currentZoneId !== null) {
|
|
70
|
+
const currentCandidate = candidates.find((c) => c.id === currentZoneId);
|
|
71
|
+
if (currentCandidate) {
|
|
72
|
+
const currentScore = currentCandidate.data.value;
|
|
73
|
+
if (currentScore - bestScore < threshold) {
|
|
74
|
+
return [currentCandidate];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
currentZoneId = bestCandidate.id;
|
|
79
|
+
return [bestCandidate];
|
|
80
|
+
};
|
|
81
|
+
detector.reset = () => {
|
|
82
|
+
currentZoneId = null;
|
|
83
|
+
};
|
|
84
|
+
return detector;
|
|
85
|
+
}
|
|
45
86
|
var closestCenterCollision = ({
|
|
46
87
|
droppableContainers,
|
|
47
88
|
collisionRect
|
|
@@ -74,38 +115,57 @@ var closestCenterCollision = ({
|
|
|
74
115
|
};
|
|
75
116
|
var DEFAULT_ACTIVATION_DISTANCE = 8;
|
|
76
117
|
function useConfiguredSensors(config = {}) {
|
|
77
|
-
const {
|
|
118
|
+
const {
|
|
119
|
+
activationDistance = DEFAULT_ACTIVATION_DISTANCE,
|
|
120
|
+
activationDelay,
|
|
121
|
+
tolerance
|
|
122
|
+
} = config;
|
|
123
|
+
let activationConstraint;
|
|
124
|
+
if (activationDelay !== void 0) {
|
|
125
|
+
activationConstraint = {
|
|
126
|
+
delay: activationDelay,
|
|
127
|
+
tolerance: tolerance ?? 5
|
|
128
|
+
};
|
|
129
|
+
} else {
|
|
130
|
+
activationConstraint = {
|
|
131
|
+
distance: activationDistance
|
|
132
|
+
};
|
|
133
|
+
}
|
|
78
134
|
return useSensors(
|
|
79
135
|
useSensor(PointerSensor, {
|
|
80
|
-
activationConstraint
|
|
81
|
-
distance: activationDistance
|
|
82
|
-
}
|
|
136
|
+
activationConstraint
|
|
83
137
|
}),
|
|
84
138
|
useSensor(TouchSensor, {
|
|
85
|
-
activationConstraint
|
|
86
|
-
distance: activationDistance
|
|
87
|
-
}
|
|
139
|
+
activationConstraint
|
|
88
140
|
}),
|
|
89
141
|
useSensor(KeyboardSensor)
|
|
90
142
|
);
|
|
91
143
|
}
|
|
92
|
-
function getSensorConfig(
|
|
144
|
+
function getSensorConfig(config = {}) {
|
|
145
|
+
const {
|
|
146
|
+
activationDistance = DEFAULT_ACTIVATION_DISTANCE,
|
|
147
|
+
activationDelay,
|
|
148
|
+
tolerance
|
|
149
|
+
} = config;
|
|
150
|
+
let activationConstraint;
|
|
151
|
+
if (activationDelay !== void 0) {
|
|
152
|
+
activationConstraint = {
|
|
153
|
+
delay: activationDelay,
|
|
154
|
+
tolerance: tolerance ?? 5
|
|
155
|
+
};
|
|
156
|
+
} else {
|
|
157
|
+
activationConstraint = {
|
|
158
|
+
distance: activationDistance
|
|
159
|
+
};
|
|
160
|
+
}
|
|
93
161
|
return {
|
|
94
|
-
pointer: {
|
|
95
|
-
|
|
96
|
-
distance: activationDistance
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
touch: {
|
|
100
|
-
activationConstraint: {
|
|
101
|
-
distance: activationDistance
|
|
102
|
-
}
|
|
103
|
-
}
|
|
162
|
+
pointer: { activationConstraint },
|
|
163
|
+
touch: { activationConstraint }
|
|
104
164
|
};
|
|
105
165
|
}
|
|
106
166
|
|
|
107
167
|
// src/utils/helper.ts
|
|
108
|
-
function extractUUID(id, pattern = "^(before|after|into)-") {
|
|
168
|
+
function extractUUID(id, pattern = "^(before|after|into|end)-") {
|
|
109
169
|
const regex = new RegExp(pattern);
|
|
110
170
|
return id.replace(regex, "");
|
|
111
171
|
}
|
|
@@ -145,8 +205,10 @@ function DropZoneComponent({
|
|
|
145
205
|
useEffect(() => {
|
|
146
206
|
if (isOver) handleInternalHover();
|
|
147
207
|
}, [isOver, handleInternalHover]);
|
|
148
|
-
|
|
149
|
-
|
|
208
|
+
const zoneBlockId = extractUUID(id);
|
|
209
|
+
const isIntoZone = id.startsWith("into-");
|
|
210
|
+
if (isIntoZone && active?.id && zoneBlockId === String(active.id)) return null;
|
|
211
|
+
if (isIntoZone && activeId && zoneBlockId === activeId) return null;
|
|
150
212
|
return /* @__PURE__ */ jsx(
|
|
151
213
|
"div",
|
|
152
214
|
{
|
|
@@ -161,10 +223,12 @@ function DropZoneComponent({
|
|
|
161
223
|
var DropZone = memo(DropZoneComponent);
|
|
162
224
|
function DraggableBlock({
|
|
163
225
|
block,
|
|
164
|
-
children
|
|
226
|
+
children,
|
|
227
|
+
disabled
|
|
165
228
|
}) {
|
|
166
229
|
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
|
167
|
-
id: block.id
|
|
230
|
+
id: block.id,
|
|
231
|
+
disabled
|
|
168
232
|
});
|
|
169
233
|
return /* @__PURE__ */ jsx("div", { ref: setNodeRef, ...attributes, ...listeners, children: children({ isDragging }) });
|
|
170
234
|
}
|
|
@@ -182,47 +246,49 @@ function TreeRenderer({
|
|
|
182
246
|
dropZoneClassName,
|
|
183
247
|
dropZoneActiveClassName,
|
|
184
248
|
indentClassName = "ml-6 border-l border-gray-200 pl-4",
|
|
185
|
-
rootClassName = "flex flex-col gap-1"
|
|
249
|
+
rootClassName = "flex flex-col gap-1",
|
|
250
|
+
canDrag,
|
|
251
|
+
previewPosition,
|
|
252
|
+
draggedBlock
|
|
186
253
|
}) {
|
|
187
254
|
const items = blocksByParent.get(parentId) ?? [];
|
|
188
255
|
const filteredBlocks = items.filter((block) => block.id !== activeId);
|
|
189
|
-
const
|
|
256
|
+
const showGhostHere = previewPosition?.parentId === parentId && draggedBlock;
|
|
190
257
|
const containerClass = depth === 0 ? rootClassName : indentClassName;
|
|
191
|
-
return /* @__PURE__ */
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
/* @__PURE__ */ jsx(
|
|
258
|
+
return /* @__PURE__ */ jsxs("div", { className: containerClass, children: [
|
|
259
|
+
/* @__PURE__ */ jsx(
|
|
260
|
+
DropZone,
|
|
261
|
+
{
|
|
262
|
+
id: parentId ? `into-${parentId}` : "root-start",
|
|
263
|
+
parentId,
|
|
264
|
+
onHover,
|
|
265
|
+
activeId,
|
|
266
|
+
className: dropZoneClassName,
|
|
267
|
+
activeClassName: dropZoneActiveClassName
|
|
268
|
+
}
|
|
269
|
+
),
|
|
270
|
+
filteredBlocks.map((block, index) => {
|
|
271
|
+
const isContainer = containerTypes.includes(block.type);
|
|
272
|
+
const isExpanded = expandedMap[block.id] !== false;
|
|
273
|
+
const Renderer = renderers[block.type];
|
|
274
|
+
const isDragDisabled = canDrag ? !canDrag(block) : false;
|
|
275
|
+
const ghostBeforeThis = showGhostHere && previewPosition.index === index;
|
|
276
|
+
const originalIndex = items.findIndex((b) => b.id === block.id);
|
|
277
|
+
const isLastInOriginal = originalIndex === items.length - 1;
|
|
278
|
+
if (!Renderer) {
|
|
279
|
+
console.warn(`No renderer found for block type: ${block.type}`);
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
const GhostRenderer = draggedBlock ? renderers[draggedBlock.type] : null;
|
|
283
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
284
|
+
ghostBeforeThis && GhostRenderer && /* @__PURE__ */ jsx("div", { className: "opacity-50 pointer-events-none", children: GhostRenderer({
|
|
285
|
+
block: draggedBlock,
|
|
286
|
+
isDragging: true,
|
|
287
|
+
depth
|
|
288
|
+
}) }),
|
|
289
|
+
/* @__PURE__ */ jsx(DraggableBlock, { block, disabled: isDragDisabled, children: ({ isDragging }) => {
|
|
290
|
+
if (isContainer) {
|
|
291
|
+
const childContent = isExpanded ? /* @__PURE__ */ jsx(Fragment$1, { children: /* @__PURE__ */ jsx(
|
|
226
292
|
TreeRenderer,
|
|
227
293
|
{
|
|
228
294
|
blocks,
|
|
@@ -238,38 +304,60 @@ function TreeRenderer({
|
|
|
238
304
|
dropZoneClassName,
|
|
239
305
|
dropZoneActiveClassName,
|
|
240
306
|
indentClassName,
|
|
241
|
-
rootClassName
|
|
307
|
+
rootClassName,
|
|
308
|
+
canDrag,
|
|
309
|
+
previewPosition,
|
|
310
|
+
draggedBlock
|
|
242
311
|
}
|
|
243
|
-
)
|
|
244
|
-
|
|
312
|
+
) }) : null;
|
|
313
|
+
return Renderer({
|
|
314
|
+
block,
|
|
315
|
+
children: childContent,
|
|
316
|
+
isDragging,
|
|
317
|
+
depth,
|
|
318
|
+
isExpanded,
|
|
319
|
+
onToggleExpand: () => onToggleExpand(block.id)
|
|
320
|
+
});
|
|
321
|
+
}
|
|
245
322
|
return Renderer({
|
|
246
323
|
block,
|
|
247
|
-
children: childContent,
|
|
248
324
|
isDragging,
|
|
249
|
-
depth
|
|
250
|
-
isExpanded,
|
|
251
|
-
onToggleExpand: () => onToggleExpand(block.id)
|
|
325
|
+
depth
|
|
252
326
|
});
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
327
|
+
} }),
|
|
328
|
+
!isLastInOriginal && /* @__PURE__ */ jsx(
|
|
329
|
+
DropZone,
|
|
330
|
+
{
|
|
331
|
+
id: `after-${block.id}`,
|
|
332
|
+
parentId: block.parentId,
|
|
333
|
+
onHover,
|
|
334
|
+
activeId,
|
|
335
|
+
className: dropZoneClassName,
|
|
336
|
+
activeClassName: dropZoneActiveClassName
|
|
337
|
+
}
|
|
338
|
+
)
|
|
339
|
+
] }, block.id);
|
|
340
|
+
}),
|
|
341
|
+
showGhostHere && previewPosition.index >= filteredBlocks.length && draggedBlock && (() => {
|
|
342
|
+
const GhostRenderer = renderers[draggedBlock.type];
|
|
343
|
+
return GhostRenderer ? /* @__PURE__ */ jsx("div", { className: "opacity-50 pointer-events-none", children: GhostRenderer({
|
|
344
|
+
block: draggedBlock,
|
|
345
|
+
isDragging: true,
|
|
346
|
+
depth
|
|
347
|
+
}) }) : null;
|
|
348
|
+
})(),
|
|
349
|
+
/* @__PURE__ */ jsx(
|
|
350
|
+
DropZone,
|
|
351
|
+
{
|
|
352
|
+
id: parentId ? `end-${parentId}` : "root-end",
|
|
353
|
+
parentId,
|
|
354
|
+
onHover,
|
|
355
|
+
activeId,
|
|
356
|
+
className: dropZoneClassName,
|
|
357
|
+
activeClassName: dropZoneActiveClassName
|
|
358
|
+
}
|
|
359
|
+
)
|
|
360
|
+
] });
|
|
273
361
|
}
|
|
274
362
|
function DragOverlay({
|
|
275
363
|
activeBlock,
|
|
@@ -329,12 +417,15 @@ function reparentBlockIndex(state, activeId, targetZone, containerTypes = []) {
|
|
|
329
417
|
const byParent = cloneParentMap(state.byParent);
|
|
330
418
|
const dragged = byId.get(String(activeId));
|
|
331
419
|
if (!dragged) return state;
|
|
420
|
+
const isRootStart = targetZone === "root-start";
|
|
421
|
+
const isRootEnd = targetZone === "root-end";
|
|
422
|
+
const isEnd = targetZone.startsWith("end-") || isRootEnd;
|
|
332
423
|
const zoneTargetId = extractUUID(targetZone);
|
|
333
424
|
const isAfter = targetZone.startsWith("after-");
|
|
334
|
-
const isInto = targetZone.startsWith("into-");
|
|
425
|
+
const isInto = targetZone.startsWith("into-") || isRootStart;
|
|
335
426
|
const target = byId.get(zoneTargetId);
|
|
336
427
|
const oldParentId = dragged.parentId ?? null;
|
|
337
|
-
const newParentId = isInto ? zoneTargetId : target?.parentId ?? null;
|
|
428
|
+
const newParentId = isRootStart || isRootEnd ? null : isInto || isEnd ? zoneTargetId : target?.parentId ?? null;
|
|
338
429
|
if (containerTypes.includes(dragged.type) && newParentId !== null) {
|
|
339
430
|
const newParent = byId.get(newParentId);
|
|
340
431
|
if (newParent && !containerTypes.includes(newParent.type)) {
|
|
@@ -346,8 +437,12 @@ function reparentBlockIndex(state, activeId, targetZone, containerTypes = []) {
|
|
|
346
437
|
const filtered = oldList.filter((id) => id !== dragged.id);
|
|
347
438
|
byParent.set(oldParentId, filtered);
|
|
348
439
|
const newList = [...byParent.get(newParentId) ?? []];
|
|
349
|
-
let insertIndex
|
|
350
|
-
if (
|
|
440
|
+
let insertIndex;
|
|
441
|
+
if (isInto) {
|
|
442
|
+
insertIndex = 0;
|
|
443
|
+
} else if (isEnd) {
|
|
444
|
+
insertIndex = newList.length;
|
|
445
|
+
} else {
|
|
351
446
|
const idx = newList.indexOf(zoneTargetId);
|
|
352
447
|
insertIndex = idx === -1 ? newList.length : isAfter ? idx + 1 : idx;
|
|
353
448
|
}
|
|
@@ -387,6 +482,30 @@ function deleteBlockAndDescendants(state, id) {
|
|
|
387
482
|
}
|
|
388
483
|
return { byId, byParent };
|
|
389
484
|
}
|
|
485
|
+
function getBlockPosition(blocks, blockId) {
|
|
486
|
+
const block = blocks.find((b) => b.id === blockId);
|
|
487
|
+
if (!block) return { parentId: null, index: 0 };
|
|
488
|
+
const siblings = blocks.filter((b) => b.parentId === block.parentId);
|
|
489
|
+
const index = siblings.findIndex((b) => b.id === blockId);
|
|
490
|
+
return { parentId: block.parentId, index };
|
|
491
|
+
}
|
|
492
|
+
function computeInitialExpanded(blocks, containerTypes, initialExpanded) {
|
|
493
|
+
if (initialExpanded === "none") {
|
|
494
|
+
return {};
|
|
495
|
+
}
|
|
496
|
+
const expandedMap = {};
|
|
497
|
+
const containers = blocks.filter((b) => containerTypes.includes(b.type));
|
|
498
|
+
if (initialExpanded === "all" || initialExpanded === void 0) {
|
|
499
|
+
for (const container of containers) {
|
|
500
|
+
expandedMap[container.id] = true;
|
|
501
|
+
}
|
|
502
|
+
} else if (Array.isArray(initialExpanded)) {
|
|
503
|
+
for (const id of initialExpanded) {
|
|
504
|
+
expandedMap[id] = true;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return expandedMap;
|
|
508
|
+
}
|
|
390
509
|
function BlockTree({
|
|
391
510
|
blocks,
|
|
392
511
|
renderers,
|
|
@@ -398,17 +517,45 @@ function BlockTree({
|
|
|
398
517
|
className = "flex flex-col gap-1",
|
|
399
518
|
dropZoneClassName,
|
|
400
519
|
dropZoneActiveClassName,
|
|
401
|
-
indentClassName
|
|
520
|
+
indentClassName,
|
|
521
|
+
showDropPreview = true,
|
|
522
|
+
// Callbacks
|
|
523
|
+
onDragStart,
|
|
524
|
+
onDragMove,
|
|
525
|
+
onDragEnd,
|
|
526
|
+
onDragCancel,
|
|
527
|
+
onBlockMove,
|
|
528
|
+
onExpandChange,
|
|
529
|
+
onHoverChange,
|
|
530
|
+
// Customization
|
|
531
|
+
canDrag,
|
|
532
|
+
canDrop,
|
|
533
|
+
collisionDetection,
|
|
534
|
+
sensors: sensorConfig,
|
|
535
|
+
initialExpanded
|
|
402
536
|
}) {
|
|
403
|
-
const sensors = useConfiguredSensors({
|
|
537
|
+
const sensors = useConfiguredSensors({
|
|
538
|
+
activationDistance: sensorConfig?.activationDistance ?? activationDistance,
|
|
539
|
+
activationDelay: sensorConfig?.activationDelay,
|
|
540
|
+
tolerance: sensorConfig?.tolerance
|
|
541
|
+
});
|
|
542
|
+
const initialExpandedMap = useMemo(
|
|
543
|
+
() => computeInitialExpanded(blocks, containerTypes, initialExpanded),
|
|
544
|
+
// Only compute on mount
|
|
545
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
546
|
+
[]
|
|
547
|
+
);
|
|
404
548
|
const stateRef = useRef({
|
|
405
549
|
activeId: null,
|
|
406
550
|
hoverZone: null,
|
|
407
|
-
expandedMap:
|
|
408
|
-
virtualState: null
|
|
551
|
+
expandedMap: initialExpandedMap,
|
|
552
|
+
virtualState: null,
|
|
553
|
+
isDragging: false
|
|
409
554
|
});
|
|
410
555
|
const initialBlocksRef = useRef([]);
|
|
411
556
|
const cachedReorderRef = useRef(null);
|
|
557
|
+
const fromPositionRef = useRef(null);
|
|
558
|
+
const stickyCollisionRef = useRef(createStickyCollision(20));
|
|
412
559
|
const [, forceRender] = useReducer((x) => x + 1, 0);
|
|
413
560
|
const debouncedSetVirtual = useRef(
|
|
414
561
|
debounce((newBlocks) => {
|
|
@@ -420,72 +567,218 @@ function BlockTree({
|
|
|
420
567
|
forceRender();
|
|
421
568
|
}, previewDebounce)
|
|
422
569
|
).current;
|
|
423
|
-
const
|
|
570
|
+
const debouncedDragMove = useRef(
|
|
571
|
+
debounce((event) => {
|
|
572
|
+
onDragMove?.(event);
|
|
573
|
+
}, 50)
|
|
574
|
+
).current;
|
|
575
|
+
const originalIndex = computeNormalizedIndex(blocks);
|
|
424
576
|
const blocksByParent = /* @__PURE__ */ new Map();
|
|
425
|
-
for (const [parentId, ids] of
|
|
577
|
+
for (const [parentId, ids] of originalIndex.byParent.entries()) {
|
|
426
578
|
blocksByParent.set(
|
|
427
579
|
parentId,
|
|
428
|
-
ids.map((id) =>
|
|
580
|
+
ids.map((id) => originalIndex.byId.get(id)).filter(Boolean)
|
|
429
581
|
);
|
|
430
582
|
}
|
|
431
|
-
const
|
|
583
|
+
const previewPosition = useMemo(() => {
|
|
584
|
+
if (!showDropPreview || !stateRef.current.virtualState || !stateRef.current.activeId) {
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
const virtualIndex = stateRef.current.virtualState;
|
|
588
|
+
const activeId = stateRef.current.activeId;
|
|
589
|
+
const block = virtualIndex.byId.get(activeId);
|
|
590
|
+
if (!block) return null;
|
|
591
|
+
const parentId = block.parentId ?? null;
|
|
592
|
+
const siblings = virtualIndex.byParent.get(parentId) ?? [];
|
|
593
|
+
const index = siblings.indexOf(activeId);
|
|
594
|
+
return { parentId, index };
|
|
595
|
+
}, [showDropPreview, stateRef.current.virtualState, stateRef.current.activeId]);
|
|
596
|
+
const activeBlock = stateRef.current.activeId ? originalIndex.byId.get(stateRef.current.activeId) ?? null : null;
|
|
597
|
+
const draggedBlock = activeBlock;
|
|
432
598
|
const handleDragStart = useCallback((event) => {
|
|
433
599
|
const id = String(event.active.id);
|
|
600
|
+
const block = blocks.find((b) => b.id === id);
|
|
601
|
+
if (!block) return;
|
|
602
|
+
if (canDrag && !canDrag(block)) {
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
const dragEvent = {
|
|
606
|
+
block,
|
|
607
|
+
blockId: id
|
|
608
|
+
};
|
|
609
|
+
const result = onDragStart?.(dragEvent);
|
|
610
|
+
if (result === false) {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
fromPositionRef.current = getBlockPosition(blocks, id);
|
|
614
|
+
stickyCollisionRef.current.reset();
|
|
434
615
|
stateRef.current.activeId = id;
|
|
616
|
+
stateRef.current.isDragging = true;
|
|
435
617
|
initialBlocksRef.current = [...blocks];
|
|
436
618
|
cachedReorderRef.current = null;
|
|
437
619
|
forceRender();
|
|
438
|
-
}, [blocks]);
|
|
620
|
+
}, [blocks, canDrag, onDragStart]);
|
|
621
|
+
const handleDragMove = useCallback((event) => {
|
|
622
|
+
if (!onDragMove) return;
|
|
623
|
+
const id = stateRef.current.activeId;
|
|
624
|
+
if (!id) return;
|
|
625
|
+
const block = blocks.find((b) => b.id === id);
|
|
626
|
+
if (!block) return;
|
|
627
|
+
const moveEvent = {
|
|
628
|
+
block,
|
|
629
|
+
blockId: id,
|
|
630
|
+
overZone: stateRef.current.hoverZone,
|
|
631
|
+
coordinates: {
|
|
632
|
+
x: event.delta.x,
|
|
633
|
+
y: event.delta.y
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
debouncedDragMove(moveEvent);
|
|
637
|
+
}, [blocks, onDragMove, debouncedDragMove]);
|
|
439
638
|
const handleDragOver = useCallback((event) => {
|
|
440
639
|
if (!event.over) return;
|
|
441
640
|
const targetZone = String(event.over.id);
|
|
442
641
|
const activeId = stateRef.current.activeId;
|
|
443
642
|
if (!activeId) return;
|
|
643
|
+
const activeBlock2 = blocks.find((b) => b.id === activeId);
|
|
644
|
+
const targetBlockId = extractBlockId(targetZone);
|
|
645
|
+
const targetBlock = blocks.find((b) => b.id === targetBlockId) ?? null;
|
|
646
|
+
if (canDrop && activeBlock2 && !canDrop(activeBlock2, targetZone, targetBlock)) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
if (stateRef.current.hoverZone !== targetZone) {
|
|
650
|
+
const zoneType = getDropZoneType(targetZone);
|
|
651
|
+
const hoverEvent = {
|
|
652
|
+
zoneId: targetZone,
|
|
653
|
+
zoneType,
|
|
654
|
+
targetBlock
|
|
655
|
+
};
|
|
656
|
+
onHoverChange?.(hoverEvent);
|
|
657
|
+
}
|
|
444
658
|
stateRef.current.hoverZone = targetZone;
|
|
445
659
|
const baseIndex = computeNormalizedIndex(initialBlocksRef.current);
|
|
446
660
|
const updatedIndex = reparentBlockIndex(baseIndex, activeId, targetZone, containerTypes);
|
|
447
661
|
const orderedBlocks = buildOrderedBlocks(updatedIndex, containerTypes);
|
|
448
662
|
cachedReorderRef.current = { targetId: targetZone, reorderedBlocks: orderedBlocks };
|
|
449
|
-
|
|
450
|
-
|
|
663
|
+
if (showDropPreview) {
|
|
664
|
+
debouncedSetVirtual(orderedBlocks);
|
|
665
|
+
}
|
|
666
|
+
}, [blocks, containerTypes, debouncedSetVirtual, canDrop, onHoverChange, showDropPreview]);
|
|
451
667
|
const handleDragEnd = useCallback((_event) => {
|
|
452
668
|
debouncedSetVirtual.cancel();
|
|
669
|
+
debouncedDragMove.cancel();
|
|
453
670
|
const cached = cachedReorderRef.current;
|
|
671
|
+
const activeId = stateRef.current.activeId;
|
|
672
|
+
const activeBlockData = activeId ? blocks.find((b) => b.id === activeId) : null;
|
|
673
|
+
if (activeBlockData) {
|
|
674
|
+
const endEvent = {
|
|
675
|
+
block: activeBlockData,
|
|
676
|
+
blockId: activeId,
|
|
677
|
+
targetZone: cached?.targetId ?? null,
|
|
678
|
+
cancelled: false
|
|
679
|
+
};
|
|
680
|
+
onDragEnd?.(endEvent);
|
|
681
|
+
}
|
|
682
|
+
if (cached && activeBlockData && fromPositionRef.current) {
|
|
683
|
+
const toPosition = getBlockPosition(cached.reorderedBlocks, activeBlockData.id);
|
|
684
|
+
const moveEvent = {
|
|
685
|
+
block: activeBlockData,
|
|
686
|
+
from: fromPositionRef.current,
|
|
687
|
+
to: toPosition,
|
|
688
|
+
blocks: cached.reorderedBlocks
|
|
689
|
+
};
|
|
690
|
+
onBlockMove?.(moveEvent);
|
|
691
|
+
}
|
|
454
692
|
stateRef.current.activeId = null;
|
|
455
693
|
stateRef.current.hoverZone = null;
|
|
456
694
|
stateRef.current.virtualState = null;
|
|
695
|
+
stateRef.current.isDragging = false;
|
|
457
696
|
cachedReorderRef.current = null;
|
|
458
697
|
initialBlocksRef.current = [];
|
|
698
|
+
fromPositionRef.current = null;
|
|
459
699
|
if (cached && onChange) {
|
|
460
700
|
onChange(cached.reorderedBlocks);
|
|
461
701
|
}
|
|
462
702
|
forceRender();
|
|
463
|
-
}, [debouncedSetVirtual, onChange]);
|
|
703
|
+
}, [blocks, debouncedSetVirtual, debouncedDragMove, onChange, onDragEnd, onBlockMove]);
|
|
704
|
+
const handleDragCancel = useCallback((_event) => {
|
|
705
|
+
debouncedSetVirtual.cancel();
|
|
706
|
+
debouncedDragMove.cancel();
|
|
707
|
+
const activeId = stateRef.current.activeId;
|
|
708
|
+
const activeBlockData = activeId ? blocks.find((b) => b.id === activeId) : null;
|
|
709
|
+
if (activeBlockData) {
|
|
710
|
+
const cancelEvent = {
|
|
711
|
+
block: activeBlockData,
|
|
712
|
+
blockId: activeId,
|
|
713
|
+
targetZone: null,
|
|
714
|
+
cancelled: true
|
|
715
|
+
};
|
|
716
|
+
onDragCancel?.(cancelEvent);
|
|
717
|
+
onDragEnd?.(cancelEvent);
|
|
718
|
+
}
|
|
719
|
+
stateRef.current.activeId = null;
|
|
720
|
+
stateRef.current.hoverZone = null;
|
|
721
|
+
stateRef.current.virtualState = null;
|
|
722
|
+
stateRef.current.isDragging = false;
|
|
723
|
+
cachedReorderRef.current = null;
|
|
724
|
+
initialBlocksRef.current = [];
|
|
725
|
+
fromPositionRef.current = null;
|
|
726
|
+
forceRender();
|
|
727
|
+
}, [blocks, debouncedSetVirtual, debouncedDragMove, onDragCancel, onDragEnd]);
|
|
464
728
|
const handleHover = useCallback((zoneId, _parentId) => {
|
|
465
729
|
const activeId = stateRef.current.activeId;
|
|
466
730
|
if (!activeId) return;
|
|
731
|
+
const activeBlockData = blocks.find((b) => b.id === activeId);
|
|
732
|
+
const targetBlockId = extractBlockId(zoneId);
|
|
733
|
+
const targetBlock = blocks.find((b) => b.id === targetBlockId) ?? null;
|
|
734
|
+
if (canDrop && activeBlockData && !canDrop(activeBlockData, zoneId, targetBlock)) {
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
if (stateRef.current.hoverZone !== zoneId) {
|
|
738
|
+
const zoneType = getDropZoneType(zoneId);
|
|
739
|
+
const hoverEvent = {
|
|
740
|
+
zoneId,
|
|
741
|
+
zoneType,
|
|
742
|
+
targetBlock
|
|
743
|
+
};
|
|
744
|
+
onHoverChange?.(hoverEvent);
|
|
745
|
+
}
|
|
467
746
|
stateRef.current.hoverZone = zoneId;
|
|
468
747
|
const baseIndex = computeNormalizedIndex(initialBlocksRef.current);
|
|
469
748
|
const updatedIndex = reparentBlockIndex(baseIndex, activeId, zoneId, containerTypes);
|
|
470
749
|
const orderedBlocks = buildOrderedBlocks(updatedIndex, containerTypes);
|
|
471
750
|
cachedReorderRef.current = { targetId: zoneId, reorderedBlocks: orderedBlocks };
|
|
472
|
-
|
|
473
|
-
|
|
751
|
+
if (showDropPreview) {
|
|
752
|
+
debouncedSetVirtual(orderedBlocks);
|
|
753
|
+
}
|
|
754
|
+
}, [blocks, containerTypes, debouncedSetVirtual, canDrop, onHoverChange, showDropPreview]);
|
|
474
755
|
const handleToggleExpand = useCallback((id) => {
|
|
756
|
+
const newExpanded = stateRef.current.expandedMap[id] === false;
|
|
475
757
|
stateRef.current.expandedMap = {
|
|
476
758
|
...stateRef.current.expandedMap,
|
|
477
|
-
[id]:
|
|
759
|
+
[id]: newExpanded
|
|
478
760
|
};
|
|
761
|
+
const block = blocks.find((b) => b.id === id);
|
|
762
|
+
if (block && onExpandChange) {
|
|
763
|
+
const expandEvent = {
|
|
764
|
+
block,
|
|
765
|
+
blockId: id,
|
|
766
|
+
expanded: newExpanded
|
|
767
|
+
};
|
|
768
|
+
onExpandChange(expandEvent);
|
|
769
|
+
}
|
|
479
770
|
forceRender();
|
|
480
|
-
}, []);
|
|
771
|
+
}, [blocks, onExpandChange]);
|
|
481
772
|
return /* @__PURE__ */ jsxs(
|
|
482
773
|
DndContext,
|
|
483
774
|
{
|
|
484
775
|
sensors,
|
|
485
|
-
collisionDetection:
|
|
776
|
+
collisionDetection: collisionDetection ?? stickyCollisionRef.current,
|
|
486
777
|
onDragStart: handleDragStart,
|
|
778
|
+
onDragMove: handleDragMove,
|
|
487
779
|
onDragOver: handleDragOver,
|
|
488
780
|
onDragEnd: handleDragEnd,
|
|
781
|
+
onDragCancel: handleDragCancel,
|
|
489
782
|
children: [
|
|
490
783
|
/* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx(
|
|
491
784
|
TreeRenderer,
|
|
@@ -502,7 +795,10 @@ function BlockTree({
|
|
|
502
795
|
dropZoneClassName,
|
|
503
796
|
dropZoneActiveClassName,
|
|
504
797
|
indentClassName,
|
|
505
|
-
rootClassName: className
|
|
798
|
+
rootClassName: className,
|
|
799
|
+
canDrag,
|
|
800
|
+
previewPosition,
|
|
801
|
+
draggedBlock
|
|
506
802
|
}
|
|
507
803
|
) }),
|
|
508
804
|
/* @__PURE__ */ jsx(DragOverlay, { activeBlock, children: dragOverlay })
|
|
@@ -575,7 +871,6 @@ function createBlockState() {
|
|
|
575
871
|
reducerWithContainerTypes,
|
|
576
872
|
computeNormalizedIndex(initialBlocks)
|
|
577
873
|
);
|
|
578
|
-
const [lastCreatedItem, setLastCreatedItem] = useState(null);
|
|
579
874
|
const blocks = useMemo(() => {
|
|
580
875
|
const result = [];
|
|
581
876
|
const walk = (parentId) => {
|
|
@@ -624,7 +919,6 @@ function createBlockState() {
|
|
|
624
919
|
order: 0
|
|
625
920
|
};
|
|
626
921
|
dispatch({ type: "ADD_ITEM", payload: newItem });
|
|
627
|
-
setLastCreatedItem(newItem);
|
|
628
922
|
return newItem;
|
|
629
923
|
},
|
|
630
924
|
[]
|
|
@@ -647,7 +941,6 @@ function createBlockState() {
|
|
|
647
941
|
type: "INSERT_ITEM",
|
|
648
942
|
payload: { item: newItem, parentId, index: insertIndex }
|
|
649
943
|
});
|
|
650
|
-
setLastCreatedItem(newItem);
|
|
651
944
|
return newItem;
|
|
652
945
|
},
|
|
653
946
|
[state]
|
|
@@ -843,6 +1136,6 @@ function createTreeState(options = {}) {
|
|
|
843
1136
|
};
|
|
844
1137
|
}
|
|
845
1138
|
|
|
846
|
-
export { BlockTree, DragOverlay, DropZone, TreeRenderer, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, computeNormalizedIndex, createBlockState, createTreeState, debounce, deleteBlockAndDescendants, extractBlockId, extractUUID, generateId, getDescendantIds, getDropZoneType, getSensorConfig, reparentBlockIndex, useConfiguredSensors, weightedVerticalCollision };
|
|
1139
|
+
export { BlockTree, DragOverlay, DropZone, TreeRenderer, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, computeNormalizedIndex, createBlockState, createStickyCollision, createTreeState, debounce, deleteBlockAndDescendants, extractBlockId, extractUUID, generateId, getDescendantIds, getDropZoneType, getSensorConfig, reparentBlockIndex, useConfiguredSensors, weightedVerticalCollision };
|
|
847
1140
|
//# sourceMappingURL=index.mjs.map
|
|
848
1141
|
//# sourceMappingURL=index.mjs.map
|