dnd-block-tree 1.2.0 → 2.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 +44 -696
- package/dist/index.d.mts +1 -872
- package/dist/index.d.ts +1 -872
- package/dist/index.js +7 -2758
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -2721
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -9
package/dist/index.js
CHANGED
|
@@ -1,2765 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var react = require('react');
|
|
5
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
3
|
+
var react = require('@dnd-block-tree/react');
|
|
6
4
|
|
|
7
|
-
// src/core/types.ts
|
|
8
|
-
function getDropZoneType(zoneId) {
|
|
9
|
-
if (zoneId.startsWith("before-")) return "before";
|
|
10
|
-
if (zoneId.startsWith("into-")) return "into";
|
|
11
|
-
return "after";
|
|
12
|
-
}
|
|
13
|
-
function extractBlockId(zoneId) {
|
|
14
|
-
return zoneId.replace(/^(before|after|into)-/, "");
|
|
15
|
-
}
|
|
16
5
|
|
|
17
|
-
// src/core/collision.ts
|
|
18
|
-
function collisionValue(d) {
|
|
19
|
-
return d.data.value;
|
|
20
|
-
}
|
|
21
|
-
function collisionLeft(d) {
|
|
22
|
-
return d.data.left;
|
|
23
|
-
}
|
|
24
|
-
function computeCollisionScores(droppableContainers, collisionRect, snapshotRects) {
|
|
25
|
-
const pointerX = collisionRect.left + collisionRect.width / 2;
|
|
26
|
-
const pointerY = collisionRect.top + collisionRect.height / 2;
|
|
27
|
-
const candidates = droppableContainers.map((container) => {
|
|
28
|
-
const rect = snapshotRects?.get(container.id) ?? container.rect.current;
|
|
29
|
-
if (!rect) return null;
|
|
30
|
-
const distanceToTop = Math.abs(pointerY - rect.top);
|
|
31
|
-
const distanceToBottom = Math.abs(pointerY - rect.bottom);
|
|
32
|
-
const edgeDistance = Math.min(distanceToTop, distanceToBottom);
|
|
33
|
-
const isBelowCenter = pointerY > rect.top + rect.height / 2;
|
|
34
|
-
const bias = isBelowCenter ? -5 : 0;
|
|
35
|
-
const isWithinX = pointerX >= rect.left && pointerX <= rect.right;
|
|
36
|
-
let horizontalScore = 0;
|
|
37
|
-
if (isWithinX) {
|
|
38
|
-
horizontalScore = Math.abs(pointerX - rect.left) * 0.3;
|
|
39
|
-
} else {
|
|
40
|
-
const distanceToZone = pointerX < rect.left ? rect.left - pointerX : pointerX - rect.right;
|
|
41
|
-
horizontalScore = distanceToZone * 2;
|
|
42
|
-
}
|
|
43
|
-
return {
|
|
44
|
-
id: container.id,
|
|
45
|
-
data: {
|
|
46
|
-
droppableContainer: container,
|
|
47
|
-
value: edgeDistance + bias + horizontalScore,
|
|
48
|
-
left: rect.left
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
}).filter((c) => c !== null);
|
|
52
|
-
candidates.sort((a, b) => collisionValue(a) - collisionValue(b));
|
|
53
|
-
return candidates;
|
|
54
|
-
}
|
|
55
|
-
var weightedVerticalCollision = ({
|
|
56
|
-
droppableContainers,
|
|
57
|
-
collisionRect
|
|
58
|
-
}) => {
|
|
59
|
-
if (!collisionRect) return [];
|
|
60
|
-
const candidates = computeCollisionScores(droppableContainers, collisionRect);
|
|
61
|
-
return candidates.slice(0, 1);
|
|
62
|
-
};
|
|
63
|
-
function createStickyCollision(threshold = 15, snapshotRef) {
|
|
64
|
-
let currentZoneId = null;
|
|
65
|
-
const detector = ({
|
|
66
|
-
droppableContainers,
|
|
67
|
-
collisionRect
|
|
68
|
-
}) => {
|
|
69
|
-
if (!collisionRect) return [];
|
|
70
|
-
const candidates = computeCollisionScores(droppableContainers, collisionRect, snapshotRef?.current);
|
|
71
|
-
if (candidates.length === 0) return [];
|
|
72
|
-
const bestCandidate = candidates[0];
|
|
73
|
-
const bestScore = collisionValue(bestCandidate);
|
|
74
|
-
if (currentZoneId !== null) {
|
|
75
|
-
const currentCandidate = candidates.find((c) => c.id === currentZoneId);
|
|
76
|
-
if (currentCandidate) {
|
|
77
|
-
const currentScore = collisionValue(currentCandidate);
|
|
78
|
-
const currentLeft = collisionLeft(currentCandidate);
|
|
79
|
-
const bestLeft = collisionLeft(bestCandidate);
|
|
80
|
-
const crossDepth = Math.abs(currentLeft - bestLeft) > 20;
|
|
81
|
-
const effectiveThreshold = crossDepth ? threshold * 0.25 : threshold;
|
|
82
|
-
if (currentScore - bestScore < effectiveThreshold) {
|
|
83
|
-
return [currentCandidate];
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
currentZoneId = bestCandidate.id;
|
|
88
|
-
return [bestCandidate];
|
|
89
|
-
};
|
|
90
|
-
detector.reset = () => {
|
|
91
|
-
currentZoneId = null;
|
|
92
|
-
};
|
|
93
|
-
return detector;
|
|
94
|
-
}
|
|
95
|
-
var closestCenterCollision = ({
|
|
96
|
-
droppableContainers,
|
|
97
|
-
collisionRect
|
|
98
|
-
}) => {
|
|
99
|
-
if (!collisionRect) return [];
|
|
100
|
-
const centerY = collisionRect.top + collisionRect.height / 2;
|
|
101
|
-
const centerX = collisionRect.left + collisionRect.width / 2;
|
|
102
|
-
const candidates = droppableContainers.map((container) => {
|
|
103
|
-
const rect = container.rect.current;
|
|
104
|
-
if (!rect) return null;
|
|
105
|
-
const containerCenterX = rect.left + rect.width / 2;
|
|
106
|
-
const containerCenterY = rect.top + rect.height / 2;
|
|
107
|
-
const distance = Math.sqrt(
|
|
108
|
-
Math.pow(centerX - containerCenterX, 2) + Math.pow(centerY - containerCenterY, 2)
|
|
109
|
-
);
|
|
110
|
-
return {
|
|
111
|
-
id: container.id,
|
|
112
|
-
data: {
|
|
113
|
-
droppableContainer: container,
|
|
114
|
-
value: distance
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
}).filter((c) => c !== null);
|
|
118
|
-
candidates.sort((a, b) => collisionValue(a) - collisionValue(b));
|
|
119
|
-
return candidates.slice(0, 1);
|
|
120
|
-
};
|
|
121
|
-
var DEFAULT_ACTIVATION_DISTANCE = 8;
|
|
122
|
-
function useConfiguredSensors(config = {}) {
|
|
123
|
-
const {
|
|
124
|
-
activationDistance = DEFAULT_ACTIVATION_DISTANCE,
|
|
125
|
-
activationDelay,
|
|
126
|
-
tolerance
|
|
127
|
-
} = config;
|
|
128
|
-
let pointerConstraint;
|
|
129
|
-
let touchConstraint;
|
|
130
|
-
if (activationDelay !== void 0) {
|
|
131
|
-
pointerConstraint = {
|
|
132
|
-
delay: activationDelay,
|
|
133
|
-
tolerance: tolerance ?? 5
|
|
134
|
-
};
|
|
135
|
-
touchConstraint = pointerConstraint;
|
|
136
|
-
} else {
|
|
137
|
-
pointerConstraint = {
|
|
138
|
-
distance: activationDistance
|
|
139
|
-
};
|
|
140
|
-
touchConstraint = {
|
|
141
|
-
delay: config.longPressDelay ?? 200,
|
|
142
|
-
tolerance: 5
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
return core.useSensors(
|
|
146
|
-
core.useSensor(core.PointerSensor, {
|
|
147
|
-
activationConstraint: pointerConstraint
|
|
148
|
-
}),
|
|
149
|
-
core.useSensor(core.TouchSensor, {
|
|
150
|
-
activationConstraint: touchConstraint
|
|
151
|
-
}),
|
|
152
|
-
core.useSensor(core.KeyboardSensor)
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
function getSensorConfig(config = {}) {
|
|
156
|
-
const {
|
|
157
|
-
activationDistance = DEFAULT_ACTIVATION_DISTANCE,
|
|
158
|
-
activationDelay,
|
|
159
|
-
tolerance
|
|
160
|
-
} = config;
|
|
161
|
-
let activationConstraint;
|
|
162
|
-
if (activationDelay !== void 0) {
|
|
163
|
-
activationConstraint = {
|
|
164
|
-
delay: activationDelay,
|
|
165
|
-
tolerance: tolerance ?? 5
|
|
166
|
-
};
|
|
167
|
-
} else {
|
|
168
|
-
activationConstraint = {
|
|
169
|
-
distance: activationDistance
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
return {
|
|
173
|
-
pointer: { activationConstraint },
|
|
174
|
-
touch: { activationConstraint }
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
6
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
let timeoutId = null;
|
|
185
|
-
const debounced = ((...args) => {
|
|
186
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
187
|
-
timeoutId = setTimeout(() => {
|
|
188
|
-
fn(...args);
|
|
189
|
-
timeoutId = null;
|
|
190
|
-
}, delay);
|
|
191
|
-
});
|
|
192
|
-
debounced.cancel = () => {
|
|
193
|
-
if (timeoutId) {
|
|
194
|
-
clearTimeout(timeoutId);
|
|
195
|
-
timeoutId = null;
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
return debounced;
|
|
199
|
-
}
|
|
200
|
-
function generateId() {
|
|
201
|
-
return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
202
|
-
}
|
|
203
|
-
function triggerHaptic(durationMs = 10) {
|
|
204
|
-
if (typeof navigator !== "undefined" && typeof navigator.vibrate === "function") {
|
|
205
|
-
navigator.vibrate(durationMs);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
function DropZoneComponent({
|
|
209
|
-
id,
|
|
210
|
-
parentId,
|
|
211
|
-
onHover,
|
|
212
|
-
activeId,
|
|
213
|
-
className = "h-1 rounded transition-colors",
|
|
214
|
-
activeClassName = "bg-blue-500",
|
|
215
|
-
height = 4
|
|
216
|
-
}) {
|
|
217
|
-
const { setNodeRef, isOver, active } = core.useDroppable({ id });
|
|
218
|
-
const handleInternalHover = react.useCallback(() => {
|
|
219
|
-
onHover(id, parentId);
|
|
220
|
-
}, [onHover, id, parentId]);
|
|
221
|
-
react.useEffect(() => {
|
|
222
|
-
if (isOver) handleInternalHover();
|
|
223
|
-
}, [isOver, handleInternalHover]);
|
|
224
|
-
const zoneBlockId = extractUUID(id);
|
|
225
|
-
const isIntoZone = id.startsWith("into-");
|
|
226
|
-
if (isIntoZone && active?.id && zoneBlockId === String(active.id)) return null;
|
|
227
|
-
if (isIntoZone && activeId && zoneBlockId === activeId) return null;
|
|
228
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
229
|
-
"div",
|
|
230
|
-
{
|
|
231
|
-
ref: setNodeRef,
|
|
232
|
-
"data-zone-id": id,
|
|
233
|
-
"data-parent-id": parentId ?? "",
|
|
234
|
-
style: { height: isOver ? height * 2 : height },
|
|
235
|
-
className: `${className} ${isOver ? activeClassName : "bg-transparent"}`
|
|
236
|
-
}
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
var DropZone = react.memo(DropZoneComponent);
|
|
240
|
-
function GhostPreview({ children }) {
|
|
241
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-dnd-ghost": true, className: "opacity-50", style: { pointerEvents: "none" }, children });
|
|
242
|
-
}
|
|
243
|
-
function DraggableBlock({
|
|
244
|
-
block,
|
|
245
|
-
children,
|
|
246
|
-
disabled,
|
|
247
|
-
focusedId,
|
|
248
|
-
isSelected,
|
|
249
|
-
onBlockClick,
|
|
250
|
-
isContainer,
|
|
251
|
-
isExpanded,
|
|
252
|
-
depth,
|
|
253
|
-
posInSet,
|
|
254
|
-
setSize
|
|
255
|
-
}) {
|
|
256
|
-
const { attributes, listeners, setNodeRef, isDragging } = core.useDraggable({
|
|
257
|
-
id: block.id,
|
|
258
|
-
disabled
|
|
259
|
-
});
|
|
260
|
-
const isFocused = focusedId === block.id;
|
|
261
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
262
|
-
"div",
|
|
263
|
-
{
|
|
264
|
-
ref: setNodeRef,
|
|
265
|
-
...attributes,
|
|
266
|
-
...listeners,
|
|
267
|
-
"data-block-id": block.id,
|
|
268
|
-
tabIndex: isFocused ? 0 : -1,
|
|
269
|
-
onClick: onBlockClick ? (e) => onBlockClick(block.id, e) : void 0,
|
|
270
|
-
"data-selected": isSelected || void 0,
|
|
271
|
-
style: { touchAction: "none", minWidth: 0, outline: "none" },
|
|
272
|
-
role: "treeitem",
|
|
273
|
-
"aria-level": depth + 1,
|
|
274
|
-
"aria-posinset": posInSet,
|
|
275
|
-
"aria-setsize": setSize,
|
|
276
|
-
"aria-expanded": isContainer ? isExpanded : void 0,
|
|
277
|
-
"aria-selected": isSelected ?? void 0,
|
|
278
|
-
children: children({ isDragging })
|
|
279
|
-
}
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
function TreeRendererInner({
|
|
283
|
-
blocks,
|
|
284
|
-
blocksByParent,
|
|
285
|
-
parentId,
|
|
286
|
-
activeId,
|
|
287
|
-
expandedMap,
|
|
288
|
-
renderers,
|
|
289
|
-
containerTypes,
|
|
290
|
-
onHover,
|
|
291
|
-
onToggleExpand,
|
|
292
|
-
depth = 0,
|
|
293
|
-
dropZoneClassName,
|
|
294
|
-
dropZoneActiveClassName,
|
|
295
|
-
indentClassName = "ml-6 border-l border-gray-200 pl-4",
|
|
296
|
-
rootClassName = "flex flex-col gap-1",
|
|
297
|
-
canDrag,
|
|
298
|
-
previewPosition,
|
|
299
|
-
draggedBlock,
|
|
300
|
-
focusedId,
|
|
301
|
-
selectedIds,
|
|
302
|
-
onBlockClick,
|
|
303
|
-
animation,
|
|
304
|
-
virtualVisibleIds
|
|
305
|
-
}) {
|
|
306
|
-
const items = blocksByParent.get(parentId) ?? [];
|
|
307
|
-
let filteredBlocks = items.filter((block) => block.id !== activeId);
|
|
308
|
-
if (virtualVisibleIds && depth === 0) {
|
|
309
|
-
filteredBlocks = filteredBlocks.filter((block) => virtualVisibleIds.has(block.id));
|
|
310
|
-
}
|
|
311
|
-
const showGhostHere = previewPosition?.parentId === parentId && draggedBlock;
|
|
312
|
-
const containerClass = depth === 0 ? rootClassName : indentClassName;
|
|
313
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: containerClass, style: { minWidth: 0 }, children: [
|
|
314
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
315
|
-
DropZone,
|
|
316
|
-
{
|
|
317
|
-
id: parentId ? `into-${parentId}` : "root-start",
|
|
318
|
-
parentId,
|
|
319
|
-
onHover,
|
|
320
|
-
activeId,
|
|
321
|
-
className: dropZoneClassName,
|
|
322
|
-
activeClassName: dropZoneActiveClassName
|
|
323
|
-
}
|
|
324
|
-
),
|
|
325
|
-
filteredBlocks.map((block, index) => {
|
|
326
|
-
const isContainer = containerTypes.includes(block.type);
|
|
327
|
-
const isExpanded = expandedMap[block.id] !== false;
|
|
328
|
-
const Renderer = renderers[block.type];
|
|
329
|
-
const isDragDisabled = canDrag ? !canDrag(block) : false;
|
|
330
|
-
const ghostBeforeThis = showGhostHere && previewPosition.index === index;
|
|
331
|
-
const originalIndex = items.findIndex((b) => b.id === block.id);
|
|
332
|
-
const isLastInOriginal = originalIndex === items.length - 1;
|
|
333
|
-
if (!Renderer) {
|
|
334
|
-
console.warn(`No renderer found for block type: ${block.type}`);
|
|
335
|
-
return null;
|
|
336
|
-
}
|
|
337
|
-
const GhostRenderer = draggedBlock ? renderers[draggedBlock.type] : null;
|
|
338
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
|
|
339
|
-
ghostBeforeThis && GhostRenderer && /* @__PURE__ */ jsxRuntime.jsx(GhostPreview, { children: GhostRenderer({
|
|
340
|
-
block: draggedBlock,
|
|
341
|
-
isDragging: true,
|
|
342
|
-
depth
|
|
343
|
-
}) }),
|
|
344
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
345
|
-
DraggableBlock,
|
|
346
|
-
{
|
|
347
|
-
block,
|
|
348
|
-
disabled: isDragDisabled,
|
|
349
|
-
focusedId,
|
|
350
|
-
isSelected: selectedIds?.has(block.id),
|
|
351
|
-
onBlockClick,
|
|
352
|
-
isContainer,
|
|
353
|
-
isExpanded,
|
|
354
|
-
depth,
|
|
355
|
-
posInSet: originalIndex + 1,
|
|
356
|
-
setSize: items.length,
|
|
357
|
-
children: ({ isDragging }) => {
|
|
358
|
-
if (isContainer) {
|
|
359
|
-
const expandStyle = animation?.expandDuration ? {
|
|
360
|
-
transition: `opacity ${animation.expandDuration}ms ${animation.easing ?? "ease"}`,
|
|
361
|
-
opacity: isExpanded ? 1 : 0
|
|
362
|
-
} : void 0;
|
|
363
|
-
const childContent = isExpanded ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: expandStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
364
|
-
TreeRenderer,
|
|
365
|
-
{
|
|
366
|
-
blocks,
|
|
367
|
-
blocksByParent,
|
|
368
|
-
parentId: block.id,
|
|
369
|
-
activeId,
|
|
370
|
-
expandedMap,
|
|
371
|
-
renderers,
|
|
372
|
-
containerTypes,
|
|
373
|
-
onHover,
|
|
374
|
-
onToggleExpand,
|
|
375
|
-
depth: depth + 1,
|
|
376
|
-
dropZoneClassName,
|
|
377
|
-
dropZoneActiveClassName,
|
|
378
|
-
indentClassName,
|
|
379
|
-
rootClassName,
|
|
380
|
-
canDrag,
|
|
381
|
-
previewPosition,
|
|
382
|
-
draggedBlock,
|
|
383
|
-
focusedId,
|
|
384
|
-
selectedIds,
|
|
385
|
-
onBlockClick,
|
|
386
|
-
animation,
|
|
387
|
-
virtualVisibleIds
|
|
388
|
-
}
|
|
389
|
-
) }) : null;
|
|
390
|
-
return Renderer({
|
|
391
|
-
block,
|
|
392
|
-
children: childContent,
|
|
393
|
-
isDragging,
|
|
394
|
-
depth,
|
|
395
|
-
isExpanded,
|
|
396
|
-
onToggleExpand: () => onToggleExpand(block.id)
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
return Renderer({
|
|
400
|
-
block,
|
|
401
|
-
isDragging,
|
|
402
|
-
depth
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
),
|
|
407
|
-
!isLastInOriginal && /* @__PURE__ */ jsxRuntime.jsx(
|
|
408
|
-
DropZone,
|
|
409
|
-
{
|
|
410
|
-
id: `after-${block.id}`,
|
|
411
|
-
parentId: block.parentId,
|
|
412
|
-
onHover,
|
|
413
|
-
activeId,
|
|
414
|
-
className: dropZoneClassName,
|
|
415
|
-
activeClassName: dropZoneActiveClassName
|
|
416
|
-
}
|
|
417
|
-
)
|
|
418
|
-
] }, block.id);
|
|
419
|
-
}),
|
|
420
|
-
showGhostHere && previewPosition.index >= filteredBlocks.length && draggedBlock && (() => {
|
|
421
|
-
const GhostRenderer = renderers[draggedBlock.type];
|
|
422
|
-
return GhostRenderer ? /* @__PURE__ */ jsxRuntime.jsx(GhostPreview, { children: GhostRenderer({
|
|
423
|
-
block: draggedBlock,
|
|
424
|
-
isDragging: true,
|
|
425
|
-
depth
|
|
426
|
-
}) }) : null;
|
|
427
|
-
})(),
|
|
428
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
429
|
-
DropZone,
|
|
430
|
-
{
|
|
431
|
-
id: parentId ? `end-${parentId}` : "root-end",
|
|
432
|
-
parentId,
|
|
433
|
-
onHover,
|
|
434
|
-
activeId,
|
|
435
|
-
className: dropZoneClassName,
|
|
436
|
-
activeClassName: dropZoneActiveClassName
|
|
437
|
-
}
|
|
438
|
-
)
|
|
439
|
-
] });
|
|
440
|
-
}
|
|
441
|
-
var TreeRenderer = react.memo(TreeRendererInner);
|
|
442
|
-
function DragOverlay({
|
|
443
|
-
activeBlock,
|
|
444
|
-
children,
|
|
445
|
-
selectedCount = 0
|
|
446
|
-
}) {
|
|
447
|
-
const showBadge = selectedCount > 1;
|
|
448
|
-
return /* @__PURE__ */ jsxRuntime.jsx(core.DragOverlay, { children: activeBlock && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
|
|
449
|
-
showBadge && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
450
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
451
|
-
"div",
|
|
452
|
-
{
|
|
453
|
-
style: {
|
|
454
|
-
position: "absolute",
|
|
455
|
-
top: 4,
|
|
456
|
-
left: 4,
|
|
457
|
-
right: -4,
|
|
458
|
-
bottom: -4,
|
|
459
|
-
borderRadius: 8,
|
|
460
|
-
border: "1px solid #d1d5db",
|
|
461
|
-
background: "#f3f4f6",
|
|
462
|
-
opacity: 0.6,
|
|
463
|
-
zIndex: -1
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
),
|
|
467
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
468
|
-
"div",
|
|
469
|
-
{
|
|
470
|
-
style: {
|
|
471
|
-
position: "absolute",
|
|
472
|
-
top: -8,
|
|
473
|
-
right: -8,
|
|
474
|
-
background: "#3b82f6",
|
|
475
|
-
color: "white",
|
|
476
|
-
borderRadius: "50%",
|
|
477
|
-
width: 22,
|
|
478
|
-
height: 22,
|
|
479
|
-
display: "flex",
|
|
480
|
-
alignItems: "center",
|
|
481
|
-
justifyContent: "center",
|
|
482
|
-
fontSize: 11,
|
|
483
|
-
fontWeight: 700,
|
|
484
|
-
zIndex: 10,
|
|
485
|
-
boxShadow: "0 1px 3px rgba(0,0,0,0.2)"
|
|
486
|
-
},
|
|
487
|
-
children: selectedCount
|
|
488
|
-
}
|
|
489
|
-
)
|
|
490
|
-
] }),
|
|
491
|
-
children ? children(activeBlock) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white border border-gray-300 shadow-md rounded-md p-3 text-sm w-64 pointer-events-none", children: [
|
|
492
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500 uppercase text-xs tracking-wide mb-1", children: activeBlock.type }),
|
|
493
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "font-semibold text-gray-800", children: [
|
|
494
|
-
"Block ",
|
|
495
|
-
activeBlock.id.slice(0, 8)
|
|
496
|
-
] })
|
|
497
|
-
] })
|
|
498
|
-
] }) });
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// src/utils/fractional.ts
|
|
502
|
-
var ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz";
|
|
503
|
-
var BASE = ALPHABET.length;
|
|
504
|
-
var FLOOR = ALPHABET[0];
|
|
505
|
-
var MID_IDX = Math.floor(BASE / 2);
|
|
506
|
-
var MID_CHAR = ALPHABET[MID_IDX];
|
|
507
|
-
function charToIdx(c) {
|
|
508
|
-
const n = ALPHABET.indexOf(c);
|
|
509
|
-
if (n === -1) throw new Error(`Invalid fractional key character: "${c}"`);
|
|
510
|
-
return n;
|
|
511
|
-
}
|
|
512
|
-
function computeMidpoint(lo, hi) {
|
|
513
|
-
const len = Math.max(lo.length, hi.length) + 1;
|
|
514
|
-
const loD = Array.from(lo.padEnd(len, FLOOR)).map(charToIdx);
|
|
515
|
-
const hiD = Array.from(hi.padEnd(len, FLOOR)).map(charToIdx);
|
|
516
|
-
const sum = new Array(len + 1).fill(0);
|
|
517
|
-
let carry = 0;
|
|
518
|
-
for (let i = len - 1; i >= 0; i--) {
|
|
519
|
-
const s = loD[i] + hiD[i] + carry;
|
|
520
|
-
sum[i + 1] = s % BASE;
|
|
521
|
-
carry = Math.floor(s / BASE);
|
|
522
|
-
}
|
|
523
|
-
sum[0] = carry;
|
|
524
|
-
const mid = new Array(len + 1).fill(0);
|
|
525
|
-
let rem = 0;
|
|
526
|
-
for (let i = 0; i <= len; i++) {
|
|
527
|
-
const val = rem * BASE + sum[i];
|
|
528
|
-
mid[i] = Math.floor(val / 2);
|
|
529
|
-
rem = val % 2;
|
|
530
|
-
}
|
|
531
|
-
const digits = mid.slice(1);
|
|
532
|
-
let end = digits.length;
|
|
533
|
-
while (end > 1 && digits[end - 1] === 0) end--;
|
|
534
|
-
return digits.slice(0, end).map((n) => ALPHABET[n]).join("");
|
|
535
|
-
}
|
|
536
|
-
function keyBefore(hi) {
|
|
537
|
-
const firstIdx = charToIdx(hi[0]);
|
|
538
|
-
if (firstIdx > 1) {
|
|
539
|
-
return ALPHABET[Math.floor(firstIdx / 2)];
|
|
540
|
-
}
|
|
541
|
-
if (hi.length === 1) {
|
|
542
|
-
return FLOOR + MID_CHAR;
|
|
543
|
-
}
|
|
544
|
-
return FLOOR + keyBefore(hi.slice(1));
|
|
545
|
-
}
|
|
546
|
-
function generateKeyBetween(lo, hi) {
|
|
547
|
-
if (lo !== null && hi !== null) {
|
|
548
|
-
if (lo >= hi) throw new Error(`lo must be strictly less than hi: "${lo}" >= "${hi}"`);
|
|
549
|
-
return computeMidpoint(lo, hi);
|
|
550
|
-
}
|
|
551
|
-
if (lo === null && hi === null) return MID_CHAR;
|
|
552
|
-
if (lo === null) return keyBefore(hi);
|
|
553
|
-
return lo + MID_CHAR;
|
|
554
|
-
}
|
|
555
|
-
function generateNKeysBetween(lo, hi, n) {
|
|
556
|
-
if (n <= 0) return [];
|
|
557
|
-
if (n === 1) return [generateKeyBetween(lo, hi)];
|
|
558
|
-
const keys = new Array(n);
|
|
559
|
-
function fill(loKey, hiKey, from, to) {
|
|
560
|
-
if (from >= to) return;
|
|
561
|
-
const mid = Math.floor((from + to) / 2);
|
|
562
|
-
keys[mid] = generateKeyBetween(loKey, hiKey);
|
|
563
|
-
fill(loKey, keys[mid], from, mid);
|
|
564
|
-
fill(keys[mid], hiKey, mid + 1, to);
|
|
565
|
-
}
|
|
566
|
-
fill(lo, hi, 0, n);
|
|
567
|
-
return keys;
|
|
568
|
-
}
|
|
569
|
-
function generateInitialKeys(n) {
|
|
570
|
-
return generateNKeysBetween(null, null, n);
|
|
571
|
-
}
|
|
572
|
-
function compareFractionalKeys(a, b) {
|
|
573
|
-
return a < b ? -1 : a > b ? 1 : 0;
|
|
574
|
-
}
|
|
575
|
-
function initFractionalOrder(blocks) {
|
|
576
|
-
const byParent = /* @__PURE__ */ new Map();
|
|
577
|
-
for (const block of blocks) {
|
|
578
|
-
const key = block.parentId ?? null;
|
|
579
|
-
const list = byParent.get(key) ?? [];
|
|
580
|
-
list.push(block);
|
|
581
|
-
byParent.set(key, list);
|
|
582
|
-
}
|
|
583
|
-
const updated = /* @__PURE__ */ new Map();
|
|
584
|
-
for (const siblings of byParent.values()) {
|
|
585
|
-
siblings.sort((a, b) => {
|
|
586
|
-
const ao = a.order, bo = b.order;
|
|
587
|
-
if (typeof ao === "number" && typeof bo === "number") return ao - bo;
|
|
588
|
-
return String(ao) < String(bo) ? -1 : String(ao) > String(bo) ? 1 : 0;
|
|
589
|
-
});
|
|
590
|
-
const keys = generateNKeysBetween(null, null, siblings.length);
|
|
591
|
-
siblings.forEach((block, i) => {
|
|
592
|
-
updated.set(block.id, { ...block, order: keys[i] });
|
|
593
|
-
});
|
|
594
|
-
}
|
|
595
|
-
return blocks.map((b) => updated.get(b.id) ?? b);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
// src/utils/blocks.ts
|
|
599
|
-
function cloneMap(map) {
|
|
600
|
-
return new Map(map);
|
|
601
|
-
}
|
|
602
|
-
function cloneParentMap(map) {
|
|
603
|
-
const newMap = /* @__PURE__ */ new Map();
|
|
604
|
-
for (const [k, v] of map.entries()) {
|
|
605
|
-
newMap.set(k, [...v]);
|
|
606
|
-
}
|
|
607
|
-
return newMap;
|
|
608
|
-
}
|
|
609
|
-
function computeNormalizedIndex(blocks, orderingStrategy = "integer") {
|
|
610
|
-
const byId = /* @__PURE__ */ new Map();
|
|
611
|
-
const byParent = /* @__PURE__ */ new Map();
|
|
612
|
-
for (const block of blocks) {
|
|
613
|
-
byId.set(block.id, block);
|
|
614
|
-
const key = block.parentId ?? null;
|
|
615
|
-
const list = byParent.get(key) ?? [];
|
|
616
|
-
byParent.set(key, [...list, block.id]);
|
|
617
|
-
}
|
|
618
|
-
if (orderingStrategy === "fractional") {
|
|
619
|
-
for (const [parentId, ids] of byParent.entries()) {
|
|
620
|
-
ids.sort((a, b) => {
|
|
621
|
-
const orderA = String(byId.get(a).order);
|
|
622
|
-
const orderB = String(byId.get(b).order);
|
|
623
|
-
return compareFractionalKeys(orderA, orderB);
|
|
624
|
-
});
|
|
625
|
-
byParent.set(parentId, ids);
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
return { byId, byParent };
|
|
629
|
-
}
|
|
630
|
-
function buildOrderedBlocks(index, containerTypes = [], orderingStrategy = "integer") {
|
|
631
|
-
const result = [];
|
|
632
|
-
const walk = (parentId) => {
|
|
633
|
-
const children = index.byParent.get(parentId) ?? [];
|
|
634
|
-
for (let i = 0; i < children.length; i++) {
|
|
635
|
-
const id = children[i];
|
|
636
|
-
const block = index.byId.get(id);
|
|
637
|
-
if (block) {
|
|
638
|
-
result.push(orderingStrategy === "fractional" ? block : { ...block, order: i });
|
|
639
|
-
if (containerTypes.includes(block.type)) {
|
|
640
|
-
walk(block.id);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
};
|
|
645
|
-
walk(null);
|
|
646
|
-
return result;
|
|
647
|
-
}
|
|
648
|
-
function reparentBlockIndex(state, activeId, targetZone, containerTypes = [], orderingStrategy = "integer", maxDepth) {
|
|
649
|
-
const byId = cloneMap(state.byId);
|
|
650
|
-
const byParent = cloneParentMap(state.byParent);
|
|
651
|
-
const dragged = byId.get(String(activeId));
|
|
652
|
-
if (!dragged) return state;
|
|
653
|
-
const isRootStart = targetZone === "root-start";
|
|
654
|
-
const isRootEnd = targetZone === "root-end";
|
|
655
|
-
const isEnd = targetZone.startsWith("end-") || isRootEnd;
|
|
656
|
-
const zoneTargetId = extractUUID(targetZone);
|
|
657
|
-
const isAfter = targetZone.startsWith("after-");
|
|
658
|
-
const isInto = targetZone.startsWith("into-") || isRootStart;
|
|
659
|
-
const target = byId.get(zoneTargetId);
|
|
660
|
-
const oldParentId = dragged.parentId ?? null;
|
|
661
|
-
const newParentId = isRootStart || isRootEnd ? null : isInto || isEnd ? zoneTargetId : target?.parentId ?? null;
|
|
662
|
-
if (containerTypes.includes(dragged.type) && newParentId !== null) {
|
|
663
|
-
const newParent = byId.get(newParentId);
|
|
664
|
-
if (newParent && !containerTypes.includes(newParent.type)) {
|
|
665
|
-
return state;
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
if (maxDepth != null) {
|
|
669
|
-
const parentDepth = newParentId !== null ? getBlockDepth(state, newParentId) : 0;
|
|
670
|
-
const subtreeDepth = getSubtreeDepth(state, dragged.id);
|
|
671
|
-
if (parentDepth + subtreeDepth > maxDepth) return state;
|
|
672
|
-
}
|
|
673
|
-
if (dragged.id === zoneTargetId) return state;
|
|
674
|
-
if (newParentId !== null && getDescendantIds(state, dragged.id).has(newParentId)) {
|
|
675
|
-
return state;
|
|
676
|
-
}
|
|
677
|
-
const oldList = byParent.get(oldParentId) ?? [];
|
|
678
|
-
const currentIndexInOldParent = oldList.indexOf(dragged.id);
|
|
679
|
-
const preNewList = byParent.get(newParentId) ?? [];
|
|
680
|
-
let targetIndex;
|
|
681
|
-
if (isInto) {
|
|
682
|
-
targetIndex = 0;
|
|
683
|
-
} else if (isEnd) {
|
|
684
|
-
targetIndex = preNewList.length;
|
|
685
|
-
} else {
|
|
686
|
-
const idx = preNewList.indexOf(zoneTargetId);
|
|
687
|
-
targetIndex = idx === -1 ? preNewList.length : isAfter ? idx + 1 : idx;
|
|
688
|
-
}
|
|
689
|
-
if (oldParentId === newParentId && currentIndexInOldParent !== -1) {
|
|
690
|
-
const adjustedTarget = targetIndex > currentIndexInOldParent ? targetIndex - 1 : targetIndex;
|
|
691
|
-
if (adjustedTarget === currentIndexInOldParent) {
|
|
692
|
-
return state;
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
const filtered = oldList.filter((id) => id !== dragged.id);
|
|
696
|
-
byParent.set(oldParentId, filtered);
|
|
697
|
-
const newList = [...byParent.get(newParentId) ?? []];
|
|
698
|
-
let insertIndex;
|
|
699
|
-
if (isInto) {
|
|
700
|
-
insertIndex = 0;
|
|
701
|
-
} else if (isEnd) {
|
|
702
|
-
insertIndex = newList.length;
|
|
703
|
-
} else {
|
|
704
|
-
const idx = newList.indexOf(zoneTargetId);
|
|
705
|
-
insertIndex = idx === -1 ? newList.length : isAfter ? idx + 1 : idx;
|
|
706
|
-
}
|
|
707
|
-
newList.splice(insertIndex, 0, dragged.id);
|
|
708
|
-
byParent.set(newParentId, newList);
|
|
709
|
-
let newOrder = dragged.order;
|
|
710
|
-
if (orderingStrategy === "fractional") {
|
|
711
|
-
const siblings = newList;
|
|
712
|
-
const movedIdx = siblings.indexOf(dragged.id);
|
|
713
|
-
const prevId = movedIdx > 0 ? siblings[movedIdx - 1] : null;
|
|
714
|
-
const nextId = movedIdx < siblings.length - 1 ? siblings[movedIdx + 1] : null;
|
|
715
|
-
const prevOrder = prevId ? String(byId.get(prevId).order) : null;
|
|
716
|
-
const nextOrder = nextId ? String(byId.get(nextId).order) : null;
|
|
717
|
-
newOrder = generateKeyBetween(prevOrder, nextOrder);
|
|
718
|
-
}
|
|
719
|
-
byId.set(dragged.id, {
|
|
720
|
-
...dragged,
|
|
721
|
-
parentId: newParentId,
|
|
722
|
-
order: newOrder
|
|
723
|
-
});
|
|
724
|
-
return { byId, byParent };
|
|
725
|
-
}
|
|
726
|
-
function getBlockDepth(index, blockId) {
|
|
727
|
-
let depth = 0;
|
|
728
|
-
let current = blockId;
|
|
729
|
-
const visited = /* @__PURE__ */ new Set();
|
|
730
|
-
while (current !== null) {
|
|
731
|
-
if (visited.has(current)) break;
|
|
732
|
-
visited.add(current);
|
|
733
|
-
depth++;
|
|
734
|
-
const block = index.byId.get(current);
|
|
735
|
-
current = block?.parentId ?? null;
|
|
736
|
-
}
|
|
737
|
-
return depth;
|
|
738
|
-
}
|
|
739
|
-
function getSubtreeDepth(index, blockId, visited = /* @__PURE__ */ new Set()) {
|
|
740
|
-
if (visited.has(blockId)) return 0;
|
|
741
|
-
visited.add(blockId);
|
|
742
|
-
const children = index.byParent.get(blockId) ?? [];
|
|
743
|
-
if (children.length === 0) return 1;
|
|
744
|
-
let max = 0;
|
|
745
|
-
for (const childId of children) {
|
|
746
|
-
max = Math.max(max, getSubtreeDepth(index, childId, visited));
|
|
747
|
-
}
|
|
748
|
-
return 1 + max;
|
|
749
|
-
}
|
|
750
|
-
function getDescendantIds(state, parentId) {
|
|
751
|
-
const toDelete = /* @__PURE__ */ new Set();
|
|
752
|
-
const stack = [parentId];
|
|
753
|
-
while (stack.length > 0) {
|
|
754
|
-
const current = stack.pop();
|
|
755
|
-
toDelete.add(current);
|
|
756
|
-
const children = state.byParent.get(current) ?? [];
|
|
757
|
-
stack.push(...children);
|
|
758
|
-
}
|
|
759
|
-
return toDelete;
|
|
760
|
-
}
|
|
761
|
-
function reparentMultipleBlocks(state, blockIds, targetZone, containerTypes = [], orderingStrategy = "integer", maxDepth) {
|
|
762
|
-
if (blockIds.length === 0) return state;
|
|
763
|
-
if (blockIds.length === 1) {
|
|
764
|
-
return reparentBlockIndex(state, blockIds[0], targetZone, containerTypes, orderingStrategy, maxDepth);
|
|
765
|
-
}
|
|
766
|
-
let result = reparentBlockIndex(state, blockIds[0], targetZone, containerTypes, orderingStrategy, maxDepth);
|
|
767
|
-
if (result === state) return state;
|
|
768
|
-
for (let i = 1; i < blockIds.length; i++) {
|
|
769
|
-
result = reparentBlockIndex(result, blockIds[i], `after-${blockIds[i - 1]}`, containerTypes, orderingStrategy, maxDepth);
|
|
770
|
-
}
|
|
771
|
-
return result;
|
|
772
|
-
}
|
|
773
|
-
function validateBlockTree(index) {
|
|
774
|
-
const issues = [];
|
|
775
|
-
for (const [id] of index.byId) {
|
|
776
|
-
const visited = /* @__PURE__ */ new Set();
|
|
777
|
-
let current = id;
|
|
778
|
-
while (current !== null) {
|
|
779
|
-
if (visited.has(current)) {
|
|
780
|
-
issues.push(`Cycle detected: block "${id}" has a circular parentId chain`);
|
|
781
|
-
break;
|
|
782
|
-
}
|
|
783
|
-
visited.add(current);
|
|
784
|
-
const block = index.byId.get(current);
|
|
785
|
-
current = block?.parentId ?? null;
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
for (const [id, block] of index.byId) {
|
|
789
|
-
if (block.parentId !== null && !index.byId.has(block.parentId)) {
|
|
790
|
-
issues.push(`Orphan: block "${id}" references non-existent parent "${block.parentId}"`);
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
for (const [parentId, childIds] of index.byParent) {
|
|
794
|
-
for (const childId of childIds) {
|
|
795
|
-
if (!index.byId.has(childId)) {
|
|
796
|
-
issues.push(`Stale ref: byParent key "${parentId}" lists non-existent block "${childId}"`);
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
if (issues.length > 0) {
|
|
801
|
-
for (const issue of issues) {
|
|
802
|
-
console.warn(`[dnd-block-tree] ${issue}`);
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
return { valid: issues.length === 0, issues };
|
|
806
|
-
}
|
|
807
|
-
function deleteBlockAndDescendants(state, id) {
|
|
808
|
-
const byId = cloneMap(state.byId);
|
|
809
|
-
const byParent = cloneParentMap(state.byParent);
|
|
810
|
-
const idsToDelete = getDescendantIds(state, id);
|
|
811
|
-
for (const deleteId of idsToDelete) {
|
|
812
|
-
byId.delete(deleteId);
|
|
813
|
-
byParent.delete(deleteId);
|
|
814
|
-
}
|
|
815
|
-
for (const [parent, list] of byParent.entries()) {
|
|
816
|
-
byParent.set(parent, list.filter((itemId) => !idsToDelete.has(itemId)));
|
|
817
|
-
}
|
|
818
|
-
return { byId, byParent };
|
|
819
|
-
}
|
|
820
|
-
function getBlockPosition(blocks, blockId) {
|
|
821
|
-
const block = blocks.find((b) => b.id === blockId);
|
|
822
|
-
if (!block) return { parentId: null, index: 0 };
|
|
823
|
-
const siblings = blocks.filter((b) => b.parentId === block.parentId);
|
|
824
|
-
const index = siblings.findIndex((b) => b.id === blockId);
|
|
825
|
-
return { parentId: block.parentId, index };
|
|
826
|
-
}
|
|
827
|
-
function computeInitialExpanded(blocks, containerTypes, initialExpanded) {
|
|
828
|
-
if (initialExpanded === "none") {
|
|
829
|
-
const expandedMap2 = {};
|
|
830
|
-
const containers2 = blocks.filter((b) => containerTypes.includes(b.type));
|
|
831
|
-
for (const container of containers2) {
|
|
832
|
-
expandedMap2[container.id] = false;
|
|
833
|
-
}
|
|
834
|
-
return expandedMap2;
|
|
835
|
-
}
|
|
836
|
-
const expandedMap = {};
|
|
837
|
-
const containers = blocks.filter((b) => containerTypes.includes(b.type));
|
|
838
|
-
if (initialExpanded === "all" || initialExpanded === void 0) {
|
|
839
|
-
for (const container of containers) {
|
|
840
|
-
expandedMap[container.id] = true;
|
|
841
|
-
}
|
|
842
|
-
} else if (Array.isArray(initialExpanded)) {
|
|
843
|
-
for (const id of initialExpanded) {
|
|
844
|
-
expandedMap[id] = true;
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
return expandedMap;
|
|
848
|
-
}
|
|
849
|
-
function getVisibleBlockIds(blocksByParent, containerTypes, expandedMap, parentId = null) {
|
|
850
|
-
const result = [];
|
|
851
|
-
const children = blocksByParent.get(parentId) ?? [];
|
|
852
|
-
for (const block of children) {
|
|
853
|
-
result.push(block.id);
|
|
854
|
-
if (containerTypes.includes(block.type) && expandedMap[block.id] !== false) {
|
|
855
|
-
result.push(...getVisibleBlockIds(blocksByParent, containerTypes, expandedMap, block.id));
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
return result;
|
|
859
|
-
}
|
|
860
|
-
function BlockTree({
|
|
861
|
-
blocks,
|
|
862
|
-
renderers,
|
|
863
|
-
containerTypes = [],
|
|
864
|
-
onChange,
|
|
865
|
-
dragOverlay,
|
|
866
|
-
activationDistance = 8,
|
|
867
|
-
previewDebounce = 150,
|
|
868
|
-
className = "flex flex-col gap-1",
|
|
869
|
-
dropZoneClassName,
|
|
870
|
-
dropZoneActiveClassName,
|
|
871
|
-
indentClassName,
|
|
872
|
-
showDropPreview = true,
|
|
873
|
-
// Callbacks
|
|
874
|
-
onDragStart,
|
|
875
|
-
onDragMove,
|
|
876
|
-
onDragEnd,
|
|
877
|
-
onDragCancel,
|
|
878
|
-
onBeforeMove,
|
|
879
|
-
onBlockMove,
|
|
880
|
-
onExpandChange,
|
|
881
|
-
onHoverChange,
|
|
882
|
-
// Customization
|
|
883
|
-
canDrag,
|
|
884
|
-
canDrop,
|
|
885
|
-
collisionDetection,
|
|
886
|
-
sensors: sensorConfig,
|
|
887
|
-
animation,
|
|
888
|
-
initialExpanded,
|
|
889
|
-
orderingStrategy = "integer",
|
|
890
|
-
maxDepth,
|
|
891
|
-
keyboardNavigation = false,
|
|
892
|
-
multiSelect = false,
|
|
893
|
-
selectedIds: externalSelectedIds,
|
|
894
|
-
onSelectionChange,
|
|
895
|
-
virtualize
|
|
896
|
-
}) {
|
|
897
|
-
const sensors = useConfiguredSensors({
|
|
898
|
-
activationDistance: sensorConfig?.activationDistance ?? activationDistance,
|
|
899
|
-
activationDelay: sensorConfig?.activationDelay,
|
|
900
|
-
tolerance: sensorConfig?.tolerance,
|
|
901
|
-
longPressDelay: sensorConfig?.longPressDelay
|
|
902
|
-
});
|
|
903
|
-
const initialExpandedMap = react.useMemo(
|
|
904
|
-
() => computeInitialExpanded(blocks, containerTypes, initialExpanded),
|
|
905
|
-
// Only compute on mount
|
|
906
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
907
|
-
[]
|
|
908
|
-
);
|
|
909
|
-
const stateRef = react.useRef({
|
|
910
|
-
activeId: null,
|
|
911
|
-
hoverZone: null,
|
|
912
|
-
expandedMap: initialExpandedMap,
|
|
913
|
-
virtualState: null,
|
|
914
|
-
isDragging: false
|
|
915
|
-
});
|
|
916
|
-
const initialBlocksRef = react.useRef([]);
|
|
917
|
-
const cachedReorderRef = react.useRef(null);
|
|
918
|
-
const fromPositionRef = react.useRef(null);
|
|
919
|
-
const draggedIdsRef = react.useRef([]);
|
|
920
|
-
const snapshotRectsRef = react.useRef(null);
|
|
921
|
-
const needsResnapshot = react.useRef(false);
|
|
922
|
-
const stickyCollisionRef = react.useRef(createStickyCollision(20, snapshotRectsRef));
|
|
923
|
-
const snapshotZoneRects = react.useCallback(() => {
|
|
924
|
-
const root = rootRef.current;
|
|
925
|
-
if (!root) return;
|
|
926
|
-
const zones = root.querySelectorAll("[data-zone-id]");
|
|
927
|
-
const map = /* @__PURE__ */ new Map();
|
|
928
|
-
zones.forEach((el) => {
|
|
929
|
-
const id = el.getAttribute("data-zone-id");
|
|
930
|
-
if (id) map.set(id, el.getBoundingClientRect());
|
|
931
|
-
});
|
|
932
|
-
snapshotRectsRef.current = map;
|
|
933
|
-
}, []);
|
|
934
|
-
const [, forceRender] = react.useReducer((x) => x + 1, 0);
|
|
935
|
-
const debouncedSetVirtual = react.useRef(
|
|
936
|
-
debounce((newBlocks) => {
|
|
937
|
-
if (newBlocks) {
|
|
938
|
-
stateRef.current.virtualState = computeNormalizedIndex(newBlocks);
|
|
939
|
-
} else {
|
|
940
|
-
stateRef.current.virtualState = null;
|
|
941
|
-
}
|
|
942
|
-
needsResnapshot.current = true;
|
|
943
|
-
forceRender();
|
|
944
|
-
}, previewDebounce)
|
|
945
|
-
).current;
|
|
946
|
-
const debouncedDragMove = react.useRef(
|
|
947
|
-
debounce((event) => {
|
|
948
|
-
onDragMove?.(event);
|
|
949
|
-
}, 50)
|
|
950
|
-
).current;
|
|
951
|
-
const originalIndex = react.useMemo(
|
|
952
|
-
() => computeNormalizedIndex(blocks, orderingStrategy),
|
|
953
|
-
[blocks, orderingStrategy]
|
|
954
|
-
);
|
|
955
|
-
const blocksByParent = react.useMemo(() => {
|
|
956
|
-
const map = /* @__PURE__ */ new Map();
|
|
957
|
-
for (const [parentId, ids] of originalIndex.byParent.entries()) {
|
|
958
|
-
map.set(parentId, ids.map((id) => originalIndex.byId.get(id)).filter(Boolean));
|
|
959
|
-
}
|
|
960
|
-
return map;
|
|
961
|
-
}, [originalIndex]);
|
|
962
|
-
const focusedIdRef = react.useRef(null);
|
|
963
|
-
const rootRef = react.useRef(null);
|
|
964
|
-
const lastClickedIdRef = react.useRef(null);
|
|
965
|
-
const [internalSelectedIds, setInternalSelectedIds] = react.useReducer(
|
|
966
|
-
(_, next) => next,
|
|
967
|
-
/* @__PURE__ */ new Set()
|
|
968
|
-
);
|
|
969
|
-
const selectedIds = externalSelectedIds ?? internalSelectedIds;
|
|
970
|
-
const setSelectedIds = react.useCallback((ids) => {
|
|
971
|
-
if (onSelectionChange) {
|
|
972
|
-
onSelectionChange(ids);
|
|
973
|
-
} else {
|
|
974
|
-
setInternalSelectedIds(ids);
|
|
975
|
-
}
|
|
976
|
-
}, [onSelectionChange]);
|
|
977
|
-
const visibleBlockIds = react.useMemo(
|
|
978
|
-
() => getVisibleBlockIds(blocksByParent, containerTypes, stateRef.current.expandedMap),
|
|
979
|
-
[blocksByParent, containerTypes, stateRef.current.expandedMap]
|
|
980
|
-
);
|
|
981
|
-
const focusBlock = react.useCallback((id) => {
|
|
982
|
-
focusedIdRef.current = id;
|
|
983
|
-
if (id && rootRef.current) {
|
|
984
|
-
const el = rootRef.current.querySelector(`[data-block-id="${id}"]`);
|
|
985
|
-
el?.focus();
|
|
986
|
-
}
|
|
987
|
-
forceRender();
|
|
988
|
-
}, []);
|
|
989
|
-
const toggleExpandRef = react.useRef(() => {
|
|
990
|
-
});
|
|
991
|
-
const handleKeyDown = react.useCallback((event) => {
|
|
992
|
-
if (!keyboardNavigation) return;
|
|
993
|
-
const currentId = focusedIdRef.current;
|
|
994
|
-
const currentIndex = currentId ? visibleBlockIds.indexOf(currentId) : -1;
|
|
995
|
-
switch (event.key) {
|
|
996
|
-
case "ArrowDown": {
|
|
997
|
-
event.preventDefault();
|
|
998
|
-
const nextIndex = currentIndex < visibleBlockIds.length - 1 ? currentIndex + 1 : currentIndex;
|
|
999
|
-
focusBlock(visibleBlockIds[nextIndex] ?? null);
|
|
1000
|
-
break;
|
|
1001
|
-
}
|
|
1002
|
-
case "ArrowUp": {
|
|
1003
|
-
event.preventDefault();
|
|
1004
|
-
const prevIndex = currentIndex > 0 ? currentIndex - 1 : 0;
|
|
1005
|
-
focusBlock(visibleBlockIds[prevIndex] ?? null);
|
|
1006
|
-
break;
|
|
1007
|
-
}
|
|
1008
|
-
case "ArrowRight": {
|
|
1009
|
-
event.preventDefault();
|
|
1010
|
-
if (currentId) {
|
|
1011
|
-
const block = originalIndex.byId.get(currentId);
|
|
1012
|
-
if (block && containerTypes.includes(block.type)) {
|
|
1013
|
-
if (stateRef.current.expandedMap[currentId] === false) {
|
|
1014
|
-
toggleExpandRef.current(currentId);
|
|
1015
|
-
} else {
|
|
1016
|
-
const children = blocksByParent.get(currentId) ?? [];
|
|
1017
|
-
if (children.length > 0) focusBlock(children[0].id);
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
break;
|
|
1022
|
-
}
|
|
1023
|
-
case "ArrowLeft": {
|
|
1024
|
-
event.preventDefault();
|
|
1025
|
-
if (currentId) {
|
|
1026
|
-
const block = originalIndex.byId.get(currentId);
|
|
1027
|
-
if (block && containerTypes.includes(block.type) && stateRef.current.expandedMap[currentId] !== false) {
|
|
1028
|
-
toggleExpandRef.current(currentId);
|
|
1029
|
-
} else if (block?.parentId) {
|
|
1030
|
-
focusBlock(block.parentId);
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
break;
|
|
1034
|
-
}
|
|
1035
|
-
case "Enter":
|
|
1036
|
-
case " ": {
|
|
1037
|
-
event.preventDefault();
|
|
1038
|
-
if (currentId) {
|
|
1039
|
-
const block = originalIndex.byId.get(currentId);
|
|
1040
|
-
if (block && containerTypes.includes(block.type)) {
|
|
1041
|
-
toggleExpandRef.current(currentId);
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
break;
|
|
1045
|
-
}
|
|
1046
|
-
case "Home": {
|
|
1047
|
-
event.preventDefault();
|
|
1048
|
-
if (visibleBlockIds.length > 0) focusBlock(visibleBlockIds[0]);
|
|
1049
|
-
break;
|
|
1050
|
-
}
|
|
1051
|
-
case "End": {
|
|
1052
|
-
event.preventDefault();
|
|
1053
|
-
if (visibleBlockIds.length > 0) focusBlock(visibleBlockIds[visibleBlockIds.length - 1]);
|
|
1054
|
-
break;
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
}, [keyboardNavigation, visibleBlockIds, focusBlock, originalIndex, containerTypes, blocksByParent]);
|
|
1058
|
-
const handleBlockClick = react.useCallback((blockId, event) => {
|
|
1059
|
-
if (!multiSelect) return;
|
|
1060
|
-
if (event.metaKey || event.ctrlKey) {
|
|
1061
|
-
const next = new Set(selectedIds);
|
|
1062
|
-
if (next.has(blockId)) {
|
|
1063
|
-
next.delete(blockId);
|
|
1064
|
-
} else {
|
|
1065
|
-
next.add(blockId);
|
|
1066
|
-
}
|
|
1067
|
-
setSelectedIds(next);
|
|
1068
|
-
} else if (event.shiftKey && lastClickedIdRef.current) {
|
|
1069
|
-
const startIdx = visibleBlockIds.indexOf(lastClickedIdRef.current);
|
|
1070
|
-
const endIdx = visibleBlockIds.indexOf(blockId);
|
|
1071
|
-
if (startIdx !== -1 && endIdx !== -1) {
|
|
1072
|
-
const [from, to] = startIdx < endIdx ? [startIdx, endIdx] : [endIdx, startIdx];
|
|
1073
|
-
const next = new Set(selectedIds);
|
|
1074
|
-
for (let i = from; i <= to; i++) {
|
|
1075
|
-
next.add(visibleBlockIds[i]);
|
|
1076
|
-
}
|
|
1077
|
-
setSelectedIds(next);
|
|
1078
|
-
}
|
|
1079
|
-
} else {
|
|
1080
|
-
setSelectedIds(/* @__PURE__ */ new Set([blockId]));
|
|
1081
|
-
}
|
|
1082
|
-
lastClickedIdRef.current = blockId;
|
|
1083
|
-
}, [multiSelect, selectedIds, setSelectedIds, visibleBlockIds]);
|
|
1084
|
-
react.useEffect(() => {
|
|
1085
|
-
if (!needsResnapshot.current || !stateRef.current.isDragging) return;
|
|
1086
|
-
needsResnapshot.current = false;
|
|
1087
|
-
requestAnimationFrame(() => {
|
|
1088
|
-
snapshotZoneRects();
|
|
1089
|
-
});
|
|
1090
|
-
});
|
|
1091
|
-
react.useEffect(() => {
|
|
1092
|
-
if (!keyboardNavigation || !focusedIdRef.current || !rootRef.current) return;
|
|
1093
|
-
const el = rootRef.current.querySelector(`[data-block-id="${focusedIdRef.current}"]`);
|
|
1094
|
-
el?.focus();
|
|
1095
|
-
});
|
|
1096
|
-
const previewPosition = react.useMemo(() => {
|
|
1097
|
-
if (!showDropPreview || !stateRef.current.virtualState || !stateRef.current.activeId) {
|
|
1098
|
-
return null;
|
|
1099
|
-
}
|
|
1100
|
-
const virtualIndex = stateRef.current.virtualState;
|
|
1101
|
-
const activeId = stateRef.current.activeId;
|
|
1102
|
-
const block = virtualIndex.byId.get(activeId);
|
|
1103
|
-
if (!block) return null;
|
|
1104
|
-
const parentId = block.parentId ?? null;
|
|
1105
|
-
const siblings = virtualIndex.byParent.get(parentId) ?? [];
|
|
1106
|
-
const index = siblings.indexOf(activeId);
|
|
1107
|
-
return { parentId, index };
|
|
1108
|
-
}, [showDropPreview, stateRef.current.virtualState, stateRef.current.activeId]);
|
|
1109
|
-
const activeBlock = stateRef.current.activeId ? originalIndex.byId.get(stateRef.current.activeId) ?? null : null;
|
|
1110
|
-
const draggedBlock = activeBlock;
|
|
1111
|
-
const handleDragStart = react.useCallback((event) => {
|
|
1112
|
-
const id = String(event.active.id);
|
|
1113
|
-
const block = blocks.find((b) => b.id === id);
|
|
1114
|
-
if (!block) return;
|
|
1115
|
-
if (canDrag && !canDrag(block)) {
|
|
1116
|
-
return;
|
|
1117
|
-
}
|
|
1118
|
-
const dragEvent = {
|
|
1119
|
-
block,
|
|
1120
|
-
blockId: id
|
|
1121
|
-
};
|
|
1122
|
-
const result = onDragStart?.(dragEvent);
|
|
1123
|
-
if (result === false) {
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
fromPositionRef.current = getBlockPosition(blocks, id);
|
|
1127
|
-
stickyCollisionRef.current.reset();
|
|
1128
|
-
if (multiSelect && selectedIds.has(id)) {
|
|
1129
|
-
draggedIdsRef.current = visibleBlockIds.filter((vid) => selectedIds.has(vid));
|
|
1130
|
-
} else {
|
|
1131
|
-
draggedIdsRef.current = [id];
|
|
1132
|
-
if (multiSelect) {
|
|
1133
|
-
setSelectedIds(/* @__PURE__ */ new Set([id]));
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
if (sensorConfig?.hapticFeedback) {
|
|
1137
|
-
triggerHaptic();
|
|
1138
|
-
}
|
|
1139
|
-
stateRef.current.activeId = id;
|
|
1140
|
-
stateRef.current.isDragging = true;
|
|
1141
|
-
initialBlocksRef.current = [...blocks];
|
|
1142
|
-
cachedReorderRef.current = null;
|
|
1143
|
-
needsResnapshot.current = true;
|
|
1144
|
-
forceRender();
|
|
1145
|
-
}, [blocks, canDrag, onDragStart, multiSelect, selectedIds, setSelectedIds, visibleBlockIds, sensorConfig?.hapticFeedback]);
|
|
1146
|
-
const handleDragMove = react.useCallback((event) => {
|
|
1147
|
-
if (!onDragMove) return;
|
|
1148
|
-
const id = stateRef.current.activeId;
|
|
1149
|
-
if (!id) return;
|
|
1150
|
-
const block = blocks.find((b) => b.id === id);
|
|
1151
|
-
if (!block) return;
|
|
1152
|
-
const moveEvent = {
|
|
1153
|
-
block,
|
|
1154
|
-
blockId: id,
|
|
1155
|
-
overZone: stateRef.current.hoverZone,
|
|
1156
|
-
coordinates: {
|
|
1157
|
-
x: event.delta.x,
|
|
1158
|
-
y: event.delta.y
|
|
1159
|
-
}
|
|
1160
|
-
};
|
|
1161
|
-
debouncedDragMove(moveEvent);
|
|
1162
|
-
}, [blocks, onDragMove, debouncedDragMove]);
|
|
1163
|
-
const handleDragOver = react.useCallback((event) => {
|
|
1164
|
-
if (!event.over) return;
|
|
1165
|
-
const targetZone = String(event.over.id);
|
|
1166
|
-
const activeId = stateRef.current.activeId;
|
|
1167
|
-
if (!activeId) return;
|
|
1168
|
-
const activeBlock2 = blocks.find((b) => b.id === activeId);
|
|
1169
|
-
const targetBlockId = extractBlockId(targetZone);
|
|
1170
|
-
const targetBlock = blocks.find((b) => b.id === targetBlockId) ?? null;
|
|
1171
|
-
if (canDrop && activeBlock2 && !canDrop(activeBlock2, targetZone, targetBlock)) {
|
|
1172
|
-
return;
|
|
1173
|
-
}
|
|
1174
|
-
if (maxDepth != null && activeBlock2) {
|
|
1175
|
-
const baseIndex2 = computeNormalizedIndex(initialBlocksRef.current, orderingStrategy);
|
|
1176
|
-
const testResult = reparentBlockIndex(baseIndex2, activeId, targetZone, containerTypes, orderingStrategy, maxDepth);
|
|
1177
|
-
if (testResult === baseIndex2) return;
|
|
1178
|
-
}
|
|
1179
|
-
if (stateRef.current.hoverZone !== targetZone) {
|
|
1180
|
-
const zoneType = getDropZoneType(targetZone);
|
|
1181
|
-
const hoverEvent = {
|
|
1182
|
-
zoneId: targetZone,
|
|
1183
|
-
zoneType,
|
|
1184
|
-
targetBlock
|
|
1185
|
-
};
|
|
1186
|
-
onHoverChange?.(hoverEvent);
|
|
1187
|
-
}
|
|
1188
|
-
stateRef.current.hoverZone = targetZone;
|
|
1189
|
-
const baseIndex = computeNormalizedIndex(initialBlocksRef.current, orderingStrategy);
|
|
1190
|
-
const ids = draggedIdsRef.current;
|
|
1191
|
-
const updatedIndex = ids.length > 1 ? reparentMultipleBlocks(baseIndex, ids, targetZone, containerTypes, orderingStrategy, maxDepth) : reparentBlockIndex(baseIndex, activeId, targetZone, containerTypes, orderingStrategy, maxDepth);
|
|
1192
|
-
const orderedBlocks = buildOrderedBlocks(updatedIndex, containerTypes, orderingStrategy);
|
|
1193
|
-
cachedReorderRef.current = { targetId: targetZone, reorderedBlocks: orderedBlocks };
|
|
1194
|
-
if (showDropPreview) {
|
|
1195
|
-
debouncedSetVirtual(orderedBlocks);
|
|
1196
|
-
}
|
|
1197
|
-
}, [blocks, containerTypes, debouncedSetVirtual, canDrop, onHoverChange, showDropPreview, maxDepth, orderingStrategy]);
|
|
1198
|
-
const handleDragEnd = react.useCallback((_event) => {
|
|
1199
|
-
debouncedSetVirtual.cancel();
|
|
1200
|
-
debouncedDragMove.cancel();
|
|
1201
|
-
let cached = cachedReorderRef.current;
|
|
1202
|
-
const activeId = stateRef.current.activeId;
|
|
1203
|
-
const activeBlockData = activeId ? blocks.find((b) => b.id === activeId) : null;
|
|
1204
|
-
if (cached && activeBlockData && fromPositionRef.current && onBeforeMove) {
|
|
1205
|
-
const operation = {
|
|
1206
|
-
block: activeBlockData,
|
|
1207
|
-
from: fromPositionRef.current,
|
|
1208
|
-
targetZone: cached.targetId
|
|
1209
|
-
};
|
|
1210
|
-
const result = onBeforeMove(operation);
|
|
1211
|
-
if (result === false) {
|
|
1212
|
-
stateRef.current.activeId = null;
|
|
1213
|
-
stateRef.current.hoverZone = null;
|
|
1214
|
-
stateRef.current.virtualState = null;
|
|
1215
|
-
stateRef.current.isDragging = false;
|
|
1216
|
-
cachedReorderRef.current = null;
|
|
1217
|
-
initialBlocksRef.current = [];
|
|
1218
|
-
fromPositionRef.current = null;
|
|
1219
|
-
snapshotRectsRef.current = null;
|
|
1220
|
-
forceRender();
|
|
1221
|
-
return;
|
|
1222
|
-
}
|
|
1223
|
-
if (result && result.targetZone !== cached.targetId) {
|
|
1224
|
-
const baseIndex = computeNormalizedIndex(initialBlocksRef.current, orderingStrategy);
|
|
1225
|
-
const ids = draggedIdsRef.current;
|
|
1226
|
-
const updatedIndex = ids.length > 1 ? reparentMultipleBlocks(baseIndex, ids, result.targetZone, containerTypes, orderingStrategy, maxDepth) : reparentBlockIndex(baseIndex, activeId, result.targetZone, containerTypes, orderingStrategy, maxDepth);
|
|
1227
|
-
const reorderedBlocks = buildOrderedBlocks(updatedIndex, containerTypes, orderingStrategy);
|
|
1228
|
-
cached = { targetId: result.targetZone, reorderedBlocks };
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
if (activeBlockData) {
|
|
1232
|
-
const endEvent = {
|
|
1233
|
-
block: activeBlockData,
|
|
1234
|
-
blockId: activeId,
|
|
1235
|
-
targetZone: cached?.targetId ?? null,
|
|
1236
|
-
cancelled: false
|
|
1237
|
-
};
|
|
1238
|
-
onDragEnd?.(endEvent);
|
|
1239
|
-
}
|
|
1240
|
-
if (cached && activeBlockData && fromPositionRef.current) {
|
|
1241
|
-
const toPosition = getBlockPosition(cached.reorderedBlocks, activeBlockData.id);
|
|
1242
|
-
const moveEvent = {
|
|
1243
|
-
block: activeBlockData,
|
|
1244
|
-
from: fromPositionRef.current,
|
|
1245
|
-
to: toPosition,
|
|
1246
|
-
blocks: cached.reorderedBlocks,
|
|
1247
|
-
movedIds: [...draggedIdsRef.current]
|
|
1248
|
-
};
|
|
1249
|
-
onBlockMove?.(moveEvent);
|
|
1250
|
-
}
|
|
1251
|
-
stateRef.current.activeId = null;
|
|
1252
|
-
stateRef.current.hoverZone = null;
|
|
1253
|
-
stateRef.current.virtualState = null;
|
|
1254
|
-
stateRef.current.isDragging = false;
|
|
1255
|
-
cachedReorderRef.current = null;
|
|
1256
|
-
initialBlocksRef.current = [];
|
|
1257
|
-
fromPositionRef.current = null;
|
|
1258
|
-
draggedIdsRef.current = [];
|
|
1259
|
-
snapshotRectsRef.current = null;
|
|
1260
|
-
if (cached && onChange) {
|
|
1261
|
-
onChange(cached.reorderedBlocks);
|
|
1262
|
-
}
|
|
1263
|
-
forceRender();
|
|
1264
|
-
}, [blocks, containerTypes, orderingStrategy, debouncedSetVirtual, debouncedDragMove, onChange, onDragEnd, onBlockMove, onBeforeMove]);
|
|
1265
|
-
const handleDragCancel = react.useCallback((_event) => {
|
|
1266
|
-
debouncedSetVirtual.cancel();
|
|
1267
|
-
debouncedDragMove.cancel();
|
|
1268
|
-
const activeId = stateRef.current.activeId;
|
|
1269
|
-
const activeBlockData = activeId ? blocks.find((b) => b.id === activeId) : null;
|
|
1270
|
-
if (activeBlockData) {
|
|
1271
|
-
const cancelEvent = {
|
|
1272
|
-
block: activeBlockData,
|
|
1273
|
-
blockId: activeId,
|
|
1274
|
-
targetZone: null,
|
|
1275
|
-
cancelled: true
|
|
1276
|
-
};
|
|
1277
|
-
onDragCancel?.(cancelEvent);
|
|
1278
|
-
onDragEnd?.(cancelEvent);
|
|
1279
|
-
}
|
|
1280
|
-
stateRef.current.activeId = null;
|
|
1281
|
-
stateRef.current.hoverZone = null;
|
|
1282
|
-
stateRef.current.virtualState = null;
|
|
1283
|
-
stateRef.current.isDragging = false;
|
|
1284
|
-
cachedReorderRef.current = null;
|
|
1285
|
-
initialBlocksRef.current = [];
|
|
1286
|
-
fromPositionRef.current = null;
|
|
1287
|
-
draggedIdsRef.current = [];
|
|
1288
|
-
snapshotRectsRef.current = null;
|
|
1289
|
-
forceRender();
|
|
1290
|
-
}, [blocks, debouncedSetVirtual, debouncedDragMove, onDragCancel, onDragEnd]);
|
|
1291
|
-
const handleHover = react.useCallback((zoneId, _parentId) => {
|
|
1292
|
-
const activeId = stateRef.current.activeId;
|
|
1293
|
-
if (!activeId) return;
|
|
1294
|
-
const activeBlockData = blocks.find((b) => b.id === activeId);
|
|
1295
|
-
const targetBlockId = extractBlockId(zoneId);
|
|
1296
|
-
const targetBlock = blocks.find((b) => b.id === targetBlockId) ?? null;
|
|
1297
|
-
if (canDrop && activeBlockData && !canDrop(activeBlockData, zoneId, targetBlock)) {
|
|
1298
|
-
return;
|
|
1299
|
-
}
|
|
1300
|
-
if (maxDepth != null && activeBlockData) {
|
|
1301
|
-
const baseIdx = computeNormalizedIndex(initialBlocksRef.current, orderingStrategy);
|
|
1302
|
-
const testResult = reparentBlockIndex(baseIdx, activeId, zoneId, containerTypes, orderingStrategy, maxDepth);
|
|
1303
|
-
if (testResult === baseIdx) return;
|
|
1304
|
-
}
|
|
1305
|
-
if (stateRef.current.hoverZone !== zoneId) {
|
|
1306
|
-
const zoneType = getDropZoneType(zoneId);
|
|
1307
|
-
const hoverEvent = {
|
|
1308
|
-
zoneId,
|
|
1309
|
-
zoneType,
|
|
1310
|
-
targetBlock
|
|
1311
|
-
};
|
|
1312
|
-
onHoverChange?.(hoverEvent);
|
|
1313
|
-
}
|
|
1314
|
-
stateRef.current.hoverZone = zoneId;
|
|
1315
|
-
const baseIndex = computeNormalizedIndex(initialBlocksRef.current, orderingStrategy);
|
|
1316
|
-
const ids = draggedIdsRef.current;
|
|
1317
|
-
const updatedIndex = ids.length > 1 ? reparentMultipleBlocks(baseIndex, ids, zoneId, containerTypes, orderingStrategy, maxDepth) : reparentBlockIndex(baseIndex, activeId, zoneId, containerTypes, orderingStrategy, maxDepth);
|
|
1318
|
-
const orderedBlocks = buildOrderedBlocks(updatedIndex, containerTypes, orderingStrategy);
|
|
1319
|
-
cachedReorderRef.current = { targetId: zoneId, reorderedBlocks: orderedBlocks };
|
|
1320
|
-
if (showDropPreview) {
|
|
1321
|
-
debouncedSetVirtual(orderedBlocks);
|
|
1322
|
-
}
|
|
1323
|
-
}, [blocks, containerTypes, orderingStrategy, debouncedSetVirtual, canDrop, onHoverChange, showDropPreview, maxDepth]);
|
|
1324
|
-
const handleToggleExpand = react.useCallback((id) => {
|
|
1325
|
-
const newExpanded = stateRef.current.expandedMap[id] === false;
|
|
1326
|
-
stateRef.current.expandedMap = {
|
|
1327
|
-
...stateRef.current.expandedMap,
|
|
1328
|
-
[id]: newExpanded
|
|
1329
|
-
};
|
|
1330
|
-
const block = blocks.find((b) => b.id === id);
|
|
1331
|
-
if (block && onExpandChange) {
|
|
1332
|
-
const expandEvent = {
|
|
1333
|
-
block,
|
|
1334
|
-
blockId: id,
|
|
1335
|
-
expanded: newExpanded
|
|
1336
|
-
};
|
|
1337
|
-
onExpandChange(expandEvent);
|
|
1338
|
-
}
|
|
1339
|
-
forceRender();
|
|
1340
|
-
}, [blocks, onExpandChange]);
|
|
1341
|
-
toggleExpandRef.current = handleToggleExpand;
|
|
1342
|
-
const virtualContainerRef = react.useRef(null);
|
|
1343
|
-
const [virtualScroll, setVirtualScroll] = react.useState({ scrollTop: 0, clientHeight: 0 });
|
|
1344
|
-
react.useEffect(() => {
|
|
1345
|
-
if (!virtualize) return;
|
|
1346
|
-
const el = virtualContainerRef.current;
|
|
1347
|
-
if (!el) return;
|
|
1348
|
-
setVirtualScroll({ scrollTop: el.scrollTop, clientHeight: el.clientHeight });
|
|
1349
|
-
const onScroll = () => {
|
|
1350
|
-
setVirtualScroll({ scrollTop: el.scrollTop, clientHeight: el.clientHeight });
|
|
1351
|
-
};
|
|
1352
|
-
el.addEventListener("scroll", onScroll, { passive: true });
|
|
1353
|
-
return () => el.removeEventListener("scroll", onScroll);
|
|
1354
|
-
}, [virtualize]);
|
|
1355
|
-
const virtualResult = react.useMemo(() => {
|
|
1356
|
-
if (!virtualize) return null;
|
|
1357
|
-
const { itemHeight, overscan = 5 } = virtualize;
|
|
1358
|
-
const { scrollTop, clientHeight } = virtualScroll;
|
|
1359
|
-
const totalHeight = visibleBlockIds.length * itemHeight;
|
|
1360
|
-
const startRaw = Math.floor(scrollTop / itemHeight);
|
|
1361
|
-
const visibleCount = Math.ceil(clientHeight / itemHeight);
|
|
1362
|
-
const start = Math.max(0, startRaw - overscan);
|
|
1363
|
-
const end = Math.min(visibleBlockIds.length - 1, startRaw + visibleCount + overscan);
|
|
1364
|
-
const offsetY = start * itemHeight;
|
|
1365
|
-
const visibleSet = /* @__PURE__ */ new Set();
|
|
1366
|
-
for (let i = start; i <= end; i++) {
|
|
1367
|
-
visibleSet.add(visibleBlockIds[i]);
|
|
1368
|
-
}
|
|
1369
|
-
return { totalHeight, offsetY, visibleSet };
|
|
1370
|
-
}, [virtualize, virtualScroll, visibleBlockIds]);
|
|
1371
|
-
const treeContent = /* @__PURE__ */ jsxRuntime.jsx(
|
|
1372
|
-
TreeRenderer,
|
|
1373
|
-
{
|
|
1374
|
-
blocks,
|
|
1375
|
-
blocksByParent,
|
|
1376
|
-
parentId: null,
|
|
1377
|
-
activeId: stateRef.current.activeId,
|
|
1378
|
-
expandedMap: stateRef.current.expandedMap,
|
|
1379
|
-
renderers,
|
|
1380
|
-
containerTypes,
|
|
1381
|
-
onHover: handleHover,
|
|
1382
|
-
onToggleExpand: handleToggleExpand,
|
|
1383
|
-
dropZoneClassName,
|
|
1384
|
-
dropZoneActiveClassName,
|
|
1385
|
-
indentClassName,
|
|
1386
|
-
rootClassName: className,
|
|
1387
|
-
canDrag,
|
|
1388
|
-
previewPosition,
|
|
1389
|
-
draggedBlock,
|
|
1390
|
-
focusedId: keyboardNavigation ? focusedIdRef.current : void 0,
|
|
1391
|
-
selectedIds: multiSelect ? selectedIds : void 0,
|
|
1392
|
-
onBlockClick: multiSelect ? handleBlockClick : void 0,
|
|
1393
|
-
animation,
|
|
1394
|
-
virtualVisibleIds: virtualResult?.visibleSet ?? null
|
|
1395
|
-
}
|
|
1396
|
-
);
|
|
1397
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1398
|
-
core.DndContext,
|
|
1399
|
-
{
|
|
1400
|
-
sensors,
|
|
1401
|
-
collisionDetection: collisionDetection ?? stickyCollisionRef.current,
|
|
1402
|
-
onDragStart: handleDragStart,
|
|
1403
|
-
onDragMove: handleDragMove,
|
|
1404
|
-
onDragOver: handleDragOver,
|
|
1405
|
-
onDragEnd: handleDragEnd,
|
|
1406
|
-
onDragCancel: handleDragCancel,
|
|
1407
|
-
children: [
|
|
1408
|
-
virtualize ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1409
|
-
"div",
|
|
1410
|
-
{
|
|
1411
|
-
ref: (el) => {
|
|
1412
|
-
virtualContainerRef.current = el;
|
|
1413
|
-
rootRef.current = el;
|
|
1414
|
-
},
|
|
1415
|
-
className,
|
|
1416
|
-
style: { minWidth: 0, overflow: "auto", position: "relative" },
|
|
1417
|
-
onKeyDown: keyboardNavigation ? handleKeyDown : void 0,
|
|
1418
|
-
role: keyboardNavigation ? "tree" : void 0,
|
|
1419
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: virtualResult.totalHeight, position: "relative" }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", top: virtualResult.offsetY, left: 0, right: 0 }, children: treeContent }) })
|
|
1420
|
-
}
|
|
1421
|
-
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1422
|
-
"div",
|
|
1423
|
-
{
|
|
1424
|
-
ref: rootRef,
|
|
1425
|
-
className,
|
|
1426
|
-
style: { minWidth: 0 },
|
|
1427
|
-
onKeyDown: keyboardNavigation ? handleKeyDown : void 0,
|
|
1428
|
-
role: keyboardNavigation ? "tree" : void 0,
|
|
1429
|
-
children: treeContent
|
|
1430
|
-
}
|
|
1431
|
-
),
|
|
1432
|
-
/* @__PURE__ */ jsxRuntime.jsx(DragOverlay, { activeBlock, selectedCount: multiSelect ? selectedIds.size : 0, children: dragOverlay })
|
|
1433
|
-
]
|
|
1434
|
-
}
|
|
1435
|
-
);
|
|
1436
|
-
}
|
|
1437
|
-
function BlockTreeSSR({ fallback = null, ...props }) {
|
|
1438
|
-
const [mounted, setMounted] = react.useState(false);
|
|
1439
|
-
react.useEffect(() => {
|
|
1440
|
-
setMounted(true);
|
|
1441
|
-
}, []);
|
|
1442
|
-
if (!mounted) {
|
|
1443
|
-
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback });
|
|
1444
|
-
}
|
|
1445
|
-
return /* @__PURE__ */ jsxRuntime.jsx(BlockTree, { ...props });
|
|
1446
|
-
}
|
|
1447
|
-
function blockReducer(state, action, containerTypes = [], orderingStrategy = "integer", maxDepth) {
|
|
1448
|
-
switch (action.type) {
|
|
1449
|
-
case "ADD_ITEM": {
|
|
1450
|
-
const byId = cloneMap(state.byId);
|
|
1451
|
-
const byParent = cloneParentMap(state.byParent);
|
|
1452
|
-
const item = action.payload;
|
|
1453
|
-
byId.set(item.id, item);
|
|
1454
|
-
const parentKey = item.parentId ?? null;
|
|
1455
|
-
const list = byParent.get(parentKey) ?? [];
|
|
1456
|
-
const insertAt = typeof item.order === "number" && item.order <= list.length ? item.order : list.length;
|
|
1457
|
-
const newList = [...list];
|
|
1458
|
-
newList.splice(insertAt, 0, item.id);
|
|
1459
|
-
byParent.set(parentKey, newList);
|
|
1460
|
-
return { byId, byParent };
|
|
1461
|
-
}
|
|
1462
|
-
case "INSERT_ITEM": {
|
|
1463
|
-
const { item, parentId, index } = action.payload;
|
|
1464
|
-
const updated = new Map(state.byParent);
|
|
1465
|
-
const siblings = [...updated.get(parentId) ?? []];
|
|
1466
|
-
siblings.splice(index, 0, item.id);
|
|
1467
|
-
updated.set(parentId, siblings);
|
|
1468
|
-
return {
|
|
1469
|
-
byId: new Map(state.byId).set(item.id, item),
|
|
1470
|
-
byParent: updated
|
|
1471
|
-
};
|
|
1472
|
-
}
|
|
1473
|
-
case "DELETE_ITEM": {
|
|
1474
|
-
return deleteBlockAndDescendants(state, action.payload.id);
|
|
1475
|
-
}
|
|
1476
|
-
case "SET_ALL": {
|
|
1477
|
-
return computeNormalizedIndex(action.payload);
|
|
1478
|
-
}
|
|
1479
|
-
case "MOVE_ITEM": {
|
|
1480
|
-
return reparentBlockIndex(
|
|
1481
|
-
state,
|
|
1482
|
-
action.payload.activeId,
|
|
1483
|
-
action.payload.targetZone,
|
|
1484
|
-
containerTypes,
|
|
1485
|
-
orderingStrategy,
|
|
1486
|
-
maxDepth
|
|
1487
|
-
);
|
|
1488
|
-
}
|
|
1489
|
-
default:
|
|
1490
|
-
return state;
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
function createBlockState() {
|
|
1494
|
-
const BlockContext = react.createContext(null);
|
|
1495
|
-
function useBlockState() {
|
|
1496
|
-
const ctx = react.useContext(BlockContext);
|
|
1497
|
-
if (!ctx) throw new Error("useBlockState must be used inside BlockStateProvider");
|
|
1498
|
-
return ctx;
|
|
1499
|
-
}
|
|
1500
|
-
function BlockStateProvider({
|
|
1501
|
-
children,
|
|
1502
|
-
initialBlocks = [],
|
|
1503
|
-
containerTypes = [],
|
|
1504
|
-
onChange,
|
|
1505
|
-
orderingStrategy = "integer",
|
|
1506
|
-
maxDepth,
|
|
1507
|
-
onBlockAdd,
|
|
1508
|
-
onBlockDelete
|
|
1509
|
-
}) {
|
|
1510
|
-
const reducerWithOptions = react.useCallback(
|
|
1511
|
-
(state2, action) => blockReducer(state2, action, containerTypes, orderingStrategy, maxDepth),
|
|
1512
|
-
[containerTypes, orderingStrategy, maxDepth]
|
|
1513
|
-
);
|
|
1514
|
-
const [state, dispatch] = react.useReducer(
|
|
1515
|
-
reducerWithOptions,
|
|
1516
|
-
computeNormalizedIndex(initialBlocks, orderingStrategy)
|
|
1517
|
-
);
|
|
1518
|
-
const blocks = react.useMemo(() => {
|
|
1519
|
-
const result = [];
|
|
1520
|
-
const walk = (parentId) => {
|
|
1521
|
-
const children2 = state.byParent.get(parentId) ?? [];
|
|
1522
|
-
for (let i = 0; i < children2.length; i++) {
|
|
1523
|
-
const id = children2[i];
|
|
1524
|
-
const b = state.byId.get(id);
|
|
1525
|
-
if (b) {
|
|
1526
|
-
result.push(orderingStrategy === "fractional" ? b : { ...b, order: i });
|
|
1527
|
-
if (containerTypes.includes(b.type)) walk(b.id);
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
};
|
|
1531
|
-
walk(null);
|
|
1532
|
-
return result;
|
|
1533
|
-
}, [state, containerTypes, orderingStrategy]);
|
|
1534
|
-
react.useMemo(() => {
|
|
1535
|
-
onChange?.(blocks);
|
|
1536
|
-
}, [blocks, onChange]);
|
|
1537
|
-
const blockMap = react.useMemo(() => state.byId, [state]);
|
|
1538
|
-
const childrenMap = react.useMemo(() => {
|
|
1539
|
-
const map = /* @__PURE__ */ new Map();
|
|
1540
|
-
for (const [parentId, ids] of state.byParent.entries()) {
|
|
1541
|
-
map.set(
|
|
1542
|
-
parentId,
|
|
1543
|
-
ids.map((id) => state.byId.get(id)).filter(Boolean)
|
|
1544
|
-
);
|
|
1545
|
-
}
|
|
1546
|
-
return map;
|
|
1547
|
-
}, [state]);
|
|
1548
|
-
const indexMap = react.useMemo(() => {
|
|
1549
|
-
const map = /* @__PURE__ */ new Map();
|
|
1550
|
-
for (const ids of state.byParent.values()) {
|
|
1551
|
-
ids.forEach((id, index) => {
|
|
1552
|
-
map.set(id, index);
|
|
1553
|
-
});
|
|
1554
|
-
}
|
|
1555
|
-
return map;
|
|
1556
|
-
}, [state]);
|
|
1557
|
-
const createItem = react.useCallback(
|
|
1558
|
-
(type, parentId = null) => {
|
|
1559
|
-
const siblings = state.byParent.get(parentId) ?? [];
|
|
1560
|
-
let order = siblings.length;
|
|
1561
|
-
if (orderingStrategy === "fractional") {
|
|
1562
|
-
const lastId = siblings[siblings.length - 1];
|
|
1563
|
-
const lastOrder = lastId ? String(state.byId.get(lastId).order) : null;
|
|
1564
|
-
order = generateKeyBetween(lastOrder, null);
|
|
1565
|
-
}
|
|
1566
|
-
const newItem = { id: generateId(), type, parentId, order };
|
|
1567
|
-
dispatch({ type: "ADD_ITEM", payload: newItem });
|
|
1568
|
-
onBlockAdd?.({ block: newItem, parentId, index: siblings.length });
|
|
1569
|
-
return newItem;
|
|
1570
|
-
},
|
|
1571
|
-
[state, orderingStrategy, onBlockAdd]
|
|
1572
|
-
);
|
|
1573
|
-
const insertItem = react.useCallback(
|
|
1574
|
-
(type, referenceId, position) => {
|
|
1575
|
-
const referenceBlock = state.byId.get(referenceId);
|
|
1576
|
-
if (!referenceBlock) throw new Error(`Reference block ${referenceId} not found`);
|
|
1577
|
-
const parentId = referenceBlock.parentId ?? null;
|
|
1578
|
-
const siblings = state.byParent.get(parentId) ?? [];
|
|
1579
|
-
const index = siblings.indexOf(referenceId);
|
|
1580
|
-
const insertIndex = position === "before" ? index : index + 1;
|
|
1581
|
-
let order = insertIndex;
|
|
1582
|
-
if (orderingStrategy === "fractional") {
|
|
1583
|
-
const prevId = insertIndex > 0 ? siblings[insertIndex - 1] : null;
|
|
1584
|
-
const nextId = insertIndex < siblings.length ? siblings[insertIndex] : null;
|
|
1585
|
-
const prevOrder = prevId ? String(state.byId.get(prevId).order) : null;
|
|
1586
|
-
const nextOrder = nextId ? String(state.byId.get(nextId).order) : null;
|
|
1587
|
-
order = generateKeyBetween(prevOrder, nextOrder);
|
|
1588
|
-
}
|
|
1589
|
-
const newItem = { id: generateId(), type, parentId, order };
|
|
1590
|
-
dispatch({
|
|
1591
|
-
type: "INSERT_ITEM",
|
|
1592
|
-
payload: { item: newItem, parentId, index: insertIndex }
|
|
1593
|
-
});
|
|
1594
|
-
onBlockAdd?.({ block: newItem, parentId, index: insertIndex });
|
|
1595
|
-
return newItem;
|
|
1596
|
-
},
|
|
1597
|
-
[state, orderingStrategy, onBlockAdd]
|
|
1598
|
-
);
|
|
1599
|
-
const deleteItem = react.useCallback((id) => {
|
|
1600
|
-
const block = state.byId.get(id);
|
|
1601
|
-
if (block && onBlockDelete) {
|
|
1602
|
-
const deletedIds = [...getDescendantIds(state, id)];
|
|
1603
|
-
onBlockDelete({ block, deletedIds, parentId: block.parentId });
|
|
1604
|
-
}
|
|
1605
|
-
dispatch({ type: "DELETE_ITEM", payload: { id } });
|
|
1606
|
-
}, [state, onBlockDelete]);
|
|
1607
|
-
const moveItem = react.useCallback((activeId, targetZone) => {
|
|
1608
|
-
dispatch({ type: "MOVE_ITEM", payload: { activeId, targetZone } });
|
|
1609
|
-
}, []);
|
|
1610
|
-
const setAll = react.useCallback((all) => {
|
|
1611
|
-
dispatch({ type: "SET_ALL", payload: all });
|
|
1612
|
-
}, []);
|
|
1613
|
-
const value = react.useMemo(
|
|
1614
|
-
() => ({
|
|
1615
|
-
blocks,
|
|
1616
|
-
blockMap,
|
|
1617
|
-
childrenMap,
|
|
1618
|
-
indexMap,
|
|
1619
|
-
normalizedIndex: state,
|
|
1620
|
-
createItem,
|
|
1621
|
-
insertItem,
|
|
1622
|
-
deleteItem,
|
|
1623
|
-
moveItem,
|
|
1624
|
-
setAll
|
|
1625
|
-
}),
|
|
1626
|
-
[
|
|
1627
|
-
blocks,
|
|
1628
|
-
blockMap,
|
|
1629
|
-
childrenMap,
|
|
1630
|
-
indexMap,
|
|
1631
|
-
state,
|
|
1632
|
-
createItem,
|
|
1633
|
-
insertItem,
|
|
1634
|
-
deleteItem,
|
|
1635
|
-
moveItem,
|
|
1636
|
-
setAll
|
|
1637
|
-
]
|
|
1638
|
-
);
|
|
1639
|
-
return /* @__PURE__ */ jsxRuntime.jsx(BlockContext.Provider, { value, children });
|
|
1640
|
-
}
|
|
1641
|
-
return {
|
|
1642
|
-
BlockStateProvider,
|
|
1643
|
-
useBlockState
|
|
1644
|
-
};
|
|
1645
|
-
}
|
|
1646
|
-
function expandReducer(state, action) {
|
|
1647
|
-
switch (action.type) {
|
|
1648
|
-
case "TOGGLE":
|
|
1649
|
-
return { ...state, [action.id]: !state[action.id] };
|
|
1650
|
-
case "SET_ALL": {
|
|
1651
|
-
const newState = {};
|
|
1652
|
-
for (const id of action.ids) {
|
|
1653
|
-
newState[id] = action.expanded;
|
|
1654
|
-
}
|
|
1655
|
-
return newState;
|
|
1656
|
-
}
|
|
1657
|
-
default:
|
|
1658
|
-
return state;
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
function createTreeState(options = {}) {
|
|
1662
|
-
const { previewDebounce = 150, containerTypes = [] } = options;
|
|
1663
|
-
const TreeContext = react.createContext(null);
|
|
1664
|
-
function useTreeState() {
|
|
1665
|
-
const ctx = react.useContext(TreeContext);
|
|
1666
|
-
if (!ctx) throw new Error("useTreeState must be used inside TreeStateProvider");
|
|
1667
|
-
return ctx;
|
|
1668
|
-
}
|
|
1669
|
-
function TreeStateProvider({ children, blocks, blockMap }) {
|
|
1670
|
-
const [activeId, setActiveId] = react.useState(null);
|
|
1671
|
-
const [hoverZone, setHoverZone] = react.useState(null);
|
|
1672
|
-
const [virtualState, setVirtualState] = react.useState(null);
|
|
1673
|
-
const [expandedMap, dispatchExpand] = react.useReducer(expandReducer, {});
|
|
1674
|
-
const initialBlocksRef = react.useRef([]);
|
|
1675
|
-
const cachedReorderRef = react.useRef(null);
|
|
1676
|
-
const activeBlock = react.useMemo(() => {
|
|
1677
|
-
if (!activeId) return null;
|
|
1678
|
-
return blockMap.get(activeId) ?? null;
|
|
1679
|
-
}, [activeId, blockMap]);
|
|
1680
|
-
const debouncedSetVirtualBlocks = react.useMemo(
|
|
1681
|
-
() => debounce((newBlocks) => {
|
|
1682
|
-
if (!newBlocks) {
|
|
1683
|
-
setVirtualState(null);
|
|
1684
|
-
} else {
|
|
1685
|
-
setVirtualState(computeNormalizedIndex(newBlocks));
|
|
1686
|
-
}
|
|
1687
|
-
}, previewDebounce),
|
|
1688
|
-
[previewDebounce]
|
|
1689
|
-
);
|
|
1690
|
-
const effectiveState = react.useMemo(() => {
|
|
1691
|
-
return virtualState ?? computeNormalizedIndex(blocks);
|
|
1692
|
-
}, [virtualState, blocks]);
|
|
1693
|
-
const effectiveBlocks = react.useMemo(() => {
|
|
1694
|
-
return buildOrderedBlocks(effectiveState, containerTypes);
|
|
1695
|
-
}, [effectiveState, containerTypes]);
|
|
1696
|
-
const blocksByParent = react.useMemo(() => {
|
|
1697
|
-
const map = /* @__PURE__ */ new Map();
|
|
1698
|
-
for (const [parentId, ids] of effectiveState.byParent.entries()) {
|
|
1699
|
-
map.set(
|
|
1700
|
-
parentId,
|
|
1701
|
-
ids.map((id) => effectiveState.byId.get(id)).filter(Boolean)
|
|
1702
|
-
);
|
|
1703
|
-
}
|
|
1704
|
-
return map;
|
|
1705
|
-
}, [effectiveState]);
|
|
1706
|
-
const handleDragStart = react.useCallback(
|
|
1707
|
-
(id) => {
|
|
1708
|
-
setActiveId(id);
|
|
1709
|
-
if (id) {
|
|
1710
|
-
initialBlocksRef.current = [...blocks];
|
|
1711
|
-
cachedReorderRef.current = null;
|
|
1712
|
-
}
|
|
1713
|
-
},
|
|
1714
|
-
[blocks]
|
|
1715
|
-
);
|
|
1716
|
-
const handleDragOver = react.useCallback(
|
|
1717
|
-
(targetZone) => {
|
|
1718
|
-
if (!activeId) return;
|
|
1719
|
-
setHoverZone(targetZone);
|
|
1720
|
-
const baseIndex = computeNormalizedIndex(initialBlocksRef.current);
|
|
1721
|
-
const updatedIndex = reparentBlockIndex(baseIndex, activeId, targetZone, containerTypes);
|
|
1722
|
-
const orderedBlocks = buildOrderedBlocks(updatedIndex, containerTypes);
|
|
1723
|
-
cachedReorderRef.current = { targetId: targetZone, reorderedBlocks: orderedBlocks };
|
|
1724
|
-
debouncedSetVirtualBlocks(orderedBlocks);
|
|
1725
|
-
},
|
|
1726
|
-
[activeId, debouncedSetVirtualBlocks, containerTypes]
|
|
1727
|
-
);
|
|
1728
|
-
react.useCallback(() => {
|
|
1729
|
-
debouncedSetVirtualBlocks.cancel();
|
|
1730
|
-
setVirtualState(null);
|
|
1731
|
-
setActiveId(null);
|
|
1732
|
-
setHoverZone(null);
|
|
1733
|
-
const result = cachedReorderRef.current;
|
|
1734
|
-
cachedReorderRef.current = null;
|
|
1735
|
-
initialBlocksRef.current = [];
|
|
1736
|
-
return result;
|
|
1737
|
-
}, [debouncedSetVirtualBlocks]);
|
|
1738
|
-
const handleHover = react.useCallback(
|
|
1739
|
-
(zoneId, _parentId) => {
|
|
1740
|
-
if (!activeId) return;
|
|
1741
|
-
handleDragOver(zoneId);
|
|
1742
|
-
},
|
|
1743
|
-
[activeId, handleDragOver]
|
|
1744
|
-
);
|
|
1745
|
-
const toggleExpand = react.useCallback((id) => {
|
|
1746
|
-
dispatchExpand({ type: "TOGGLE", id });
|
|
1747
|
-
}, []);
|
|
1748
|
-
const setExpandAll = react.useCallback(
|
|
1749
|
-
(expanded) => {
|
|
1750
|
-
const containerIds = blocks.filter((b) => containerTypes.includes(b.type)).map((b) => b.id);
|
|
1751
|
-
dispatchExpand({ type: "SET_ALL", expanded, ids: containerIds });
|
|
1752
|
-
},
|
|
1753
|
-
[blocks, containerTypes]
|
|
1754
|
-
);
|
|
1755
|
-
react.useEffect(() => {
|
|
1756
|
-
return () => {
|
|
1757
|
-
debouncedSetVirtualBlocks.cancel();
|
|
1758
|
-
};
|
|
1759
|
-
}, [debouncedSetVirtualBlocks]);
|
|
1760
|
-
const value = react.useMemo(
|
|
1761
|
-
() => ({
|
|
1762
|
-
activeId,
|
|
1763
|
-
activeBlock,
|
|
1764
|
-
hoverZone,
|
|
1765
|
-
expandedMap,
|
|
1766
|
-
effectiveBlocks,
|
|
1767
|
-
blocksByParent,
|
|
1768
|
-
setActiveId: handleDragStart,
|
|
1769
|
-
setHoverZone,
|
|
1770
|
-
toggleExpand,
|
|
1771
|
-
setExpandAll,
|
|
1772
|
-
handleHover
|
|
1773
|
-
}),
|
|
1774
|
-
[
|
|
1775
|
-
activeId,
|
|
1776
|
-
activeBlock,
|
|
1777
|
-
hoverZone,
|
|
1778
|
-
expandedMap,
|
|
1779
|
-
effectiveBlocks,
|
|
1780
|
-
blocksByParent,
|
|
1781
|
-
handleDragStart,
|
|
1782
|
-
toggleExpand,
|
|
1783
|
-
setExpandAll,
|
|
1784
|
-
handleHover
|
|
1785
|
-
]
|
|
1786
|
-
);
|
|
1787
|
-
return /* @__PURE__ */ jsxRuntime.jsx(TreeContext.Provider, { value, children });
|
|
1788
|
-
}
|
|
1789
|
-
return {
|
|
1790
|
-
TreeStateProvider,
|
|
1791
|
-
useTreeState
|
|
1792
|
-
};
|
|
1793
|
-
}
|
|
1794
|
-
function historyReducer(state, action) {
|
|
1795
|
-
switch (action.type) {
|
|
1796
|
-
case "SET": {
|
|
1797
|
-
const past = [...state.past, state.present];
|
|
1798
|
-
if (past.length > action.maxSteps) {
|
|
1799
|
-
past.shift();
|
|
1800
|
-
}
|
|
1801
|
-
return {
|
|
1802
|
-
past,
|
|
1803
|
-
present: action.payload,
|
|
1804
|
-
future: []
|
|
1805
|
-
};
|
|
1806
|
-
}
|
|
1807
|
-
case "UNDO": {
|
|
1808
|
-
if (state.past.length === 0) return state;
|
|
1809
|
-
const previous = state.past[state.past.length - 1];
|
|
1810
|
-
return {
|
|
1811
|
-
past: state.past.slice(0, -1),
|
|
1812
|
-
present: previous,
|
|
1813
|
-
future: [state.present, ...state.future]
|
|
1814
|
-
};
|
|
1815
|
-
}
|
|
1816
|
-
case "REDO": {
|
|
1817
|
-
if (state.future.length === 0) return state;
|
|
1818
|
-
const next = state.future[0];
|
|
1819
|
-
return {
|
|
1820
|
-
past: [...state.past, state.present],
|
|
1821
|
-
present: next,
|
|
1822
|
-
future: state.future.slice(1)
|
|
1823
|
-
};
|
|
1824
|
-
}
|
|
1825
|
-
default:
|
|
1826
|
-
return state;
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
function useBlockHistory(initialBlocks, options = {}) {
|
|
1830
|
-
const { maxSteps = 50 } = options;
|
|
1831
|
-
const [state, dispatch] = react.useReducer(historyReducer, {
|
|
1832
|
-
past: [],
|
|
1833
|
-
present: initialBlocks,
|
|
1834
|
-
future: []
|
|
1835
|
-
});
|
|
1836
|
-
const set = react.useCallback(
|
|
1837
|
-
(blocks) => dispatch({ type: "SET", payload: blocks, maxSteps }),
|
|
1838
|
-
[maxSteps]
|
|
1839
|
-
);
|
|
1840
|
-
const undo = react.useCallback(() => dispatch({ type: "UNDO" }), []);
|
|
1841
|
-
const redo = react.useCallback(() => dispatch({ type: "REDO" }), []);
|
|
1842
|
-
return {
|
|
1843
|
-
blocks: state.present,
|
|
1844
|
-
set,
|
|
1845
|
-
undo,
|
|
1846
|
-
redo,
|
|
1847
|
-
canUndo: state.past.length > 0,
|
|
1848
|
-
canRedo: state.future.length > 0
|
|
1849
|
-
};
|
|
1850
|
-
}
|
|
1851
|
-
function useLayoutAnimation(containerRef, options = {}) {
|
|
1852
|
-
const {
|
|
1853
|
-
duration = 200,
|
|
1854
|
-
easing = "ease",
|
|
1855
|
-
selector = "[data-block-id]"
|
|
1856
|
-
} = options;
|
|
1857
|
-
const prevPositions = react.useRef(/* @__PURE__ */ new Map());
|
|
1858
|
-
react.useLayoutEffect(() => {
|
|
1859
|
-
const container = containerRef.current;
|
|
1860
|
-
if (!container) return;
|
|
1861
|
-
const children = container.querySelectorAll(selector);
|
|
1862
|
-
const currentPositions = /* @__PURE__ */ new Map();
|
|
1863
|
-
children.forEach((el) => {
|
|
1864
|
-
const id = el.dataset.blockId;
|
|
1865
|
-
if (id) {
|
|
1866
|
-
currentPositions.set(id, el.getBoundingClientRect());
|
|
1867
|
-
}
|
|
1868
|
-
});
|
|
1869
|
-
children.forEach((el) => {
|
|
1870
|
-
const htmlEl = el;
|
|
1871
|
-
const id = htmlEl.dataset.blockId;
|
|
1872
|
-
if (!id) return;
|
|
1873
|
-
const prev = prevPositions.current.get(id);
|
|
1874
|
-
const curr = currentPositions.get(id);
|
|
1875
|
-
if (!prev || !curr) return;
|
|
1876
|
-
const deltaY = prev.top - curr.top;
|
|
1877
|
-
const deltaX = prev.left - curr.left;
|
|
1878
|
-
if (deltaY === 0 && deltaX === 0) return;
|
|
1879
|
-
htmlEl.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
|
|
1880
|
-
htmlEl.style.transition = "none";
|
|
1881
|
-
requestAnimationFrame(() => {
|
|
1882
|
-
htmlEl.style.transition = `transform ${duration}ms ${easing}`;
|
|
1883
|
-
htmlEl.style.transform = "";
|
|
1884
|
-
const onEnd = () => {
|
|
1885
|
-
htmlEl.style.transition = "";
|
|
1886
|
-
htmlEl.removeEventListener("transitionend", onEnd);
|
|
1887
|
-
};
|
|
1888
|
-
htmlEl.addEventListener("transitionend", onEnd);
|
|
1889
|
-
});
|
|
1890
|
-
});
|
|
1891
|
-
prevPositions.current = currentPositions;
|
|
1892
|
-
});
|
|
1893
|
-
}
|
|
1894
|
-
function useVirtualTree({
|
|
1895
|
-
containerRef,
|
|
1896
|
-
itemCount,
|
|
1897
|
-
itemHeight,
|
|
1898
|
-
overscan = 5
|
|
1899
|
-
}) {
|
|
1900
|
-
const [scrollTop, setScrollTop] = react.useState(0);
|
|
1901
|
-
const [containerHeight, setContainerHeight] = react.useState(0);
|
|
1902
|
-
const rafId = react.useRef(0);
|
|
1903
|
-
const handleScroll = react.useCallback(() => {
|
|
1904
|
-
cancelAnimationFrame(rafId.current);
|
|
1905
|
-
rafId.current = requestAnimationFrame(() => {
|
|
1906
|
-
const el = containerRef.current;
|
|
1907
|
-
if (el) {
|
|
1908
|
-
setScrollTop(el.scrollTop);
|
|
1909
|
-
setContainerHeight(el.clientHeight);
|
|
1910
|
-
}
|
|
1911
|
-
});
|
|
1912
|
-
}, [containerRef]);
|
|
1913
|
-
react.useEffect(() => {
|
|
1914
|
-
const el = containerRef.current;
|
|
1915
|
-
if (!el) return;
|
|
1916
|
-
setScrollTop(el.scrollTop);
|
|
1917
|
-
setContainerHeight(el.clientHeight);
|
|
1918
|
-
el.addEventListener("scroll", handleScroll, { passive: true });
|
|
1919
|
-
return () => {
|
|
1920
|
-
el.removeEventListener("scroll", handleScroll);
|
|
1921
|
-
cancelAnimationFrame(rafId.current);
|
|
1922
|
-
};
|
|
1923
|
-
}, [containerRef, handleScroll]);
|
|
1924
|
-
const totalHeight = itemCount * itemHeight;
|
|
1925
|
-
const startRaw = Math.floor(scrollTop / itemHeight);
|
|
1926
|
-
const visibleCount = Math.ceil(containerHeight / itemHeight);
|
|
1927
|
-
const start = Math.max(0, startRaw - overscan);
|
|
1928
|
-
const end = Math.min(itemCount - 1, startRaw + visibleCount + overscan);
|
|
1929
|
-
const offsetY = start * itemHeight;
|
|
1930
|
-
return {
|
|
1931
|
-
visibleRange: { start, end },
|
|
1932
|
-
totalHeight,
|
|
1933
|
-
offsetY
|
|
1934
|
-
};
|
|
1935
|
-
}
|
|
1936
|
-
var MAX_EVENTS = 100;
|
|
1937
|
-
function useDevToolsCallbacks() {
|
|
1938
|
-
const [events, setEvents] = react.useState([]);
|
|
1939
|
-
const nextIdRef = react.useRef(1);
|
|
1940
|
-
const addEvent = react.useCallback((type, summary) => {
|
|
1941
|
-
const entry = {
|
|
1942
|
-
id: nextIdRef.current++,
|
|
1943
|
-
timestamp: Date.now(),
|
|
1944
|
-
type,
|
|
1945
|
-
summary
|
|
1946
|
-
};
|
|
1947
|
-
setEvents((prev) => [entry, ...prev].slice(0, MAX_EVENTS));
|
|
1948
|
-
}, []);
|
|
1949
|
-
const clearEvents = react.useCallback(() => {
|
|
1950
|
-
setEvents([]);
|
|
1951
|
-
}, []);
|
|
1952
|
-
const callbacks = react.useMemo(() => ({
|
|
1953
|
-
onDragStart: (event) => {
|
|
1954
|
-
addEvent("dragStart", `Started dragging "${event.blockId}"`);
|
|
1955
|
-
},
|
|
1956
|
-
onDragEnd: (event) => {
|
|
1957
|
-
if (event.cancelled) {
|
|
1958
|
-
addEvent("dragEnd", `Cancelled drag of "${event.blockId}"`);
|
|
1959
|
-
} else {
|
|
1960
|
-
addEvent("dragEnd", `Dropped "${event.blockId}" at ${event.targetZone ?? "none"}`);
|
|
1961
|
-
}
|
|
1962
|
-
},
|
|
1963
|
-
onBlockMove: (event) => {
|
|
1964
|
-
const fromStr = `parent=${event.from.parentId ?? "root"}[${event.from.index}]`;
|
|
1965
|
-
const toStr = `parent=${event.to.parentId ?? "root"}[${event.to.index}]`;
|
|
1966
|
-
const ids = event.movedIds.length > 1 ? ` (${event.movedIds.length} blocks)` : "";
|
|
1967
|
-
addEvent("blockMove", `Moved "${event.block.id}" from ${fromStr} to ${toStr}${ids}`);
|
|
1968
|
-
},
|
|
1969
|
-
onExpandChange: (event) => {
|
|
1970
|
-
addEvent("expandChange", `${event.expanded ? "Expanded" : "Collapsed"} "${event.blockId}"`);
|
|
1971
|
-
},
|
|
1972
|
-
onHoverChange: (event) => {
|
|
1973
|
-
if (event.zoneId) {
|
|
1974
|
-
addEvent("hoverChange", `Hovering over zone "${event.zoneId}"`);
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
}), [addEvent]);
|
|
1978
|
-
return { callbacks, events, clearEvents };
|
|
1979
|
-
}
|
|
1980
|
-
function DevToolsLogo({ size = 20 }) {
|
|
1981
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1982
|
-
"svg",
|
|
1983
|
-
{
|
|
1984
|
-
width: size,
|
|
1985
|
-
height: size,
|
|
1986
|
-
viewBox: "0 0 24 24",
|
|
1987
|
-
fill: "none",
|
|
1988
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
1989
|
-
children: [
|
|
1990
|
-
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "2", y: "2", width: "8", height: "5", rx: "1", fill: "#3b82f6", opacity: "0.9" }),
|
|
1991
|
-
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "8", y: "10", width: "8", height: "5", rx: "1", fill: "#10b981", opacity: "0.9" }),
|
|
1992
|
-
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "14", y: "18", width: "8", height: "5", rx: "1", fill: "#f59e0b", opacity: "0.9" }),
|
|
1993
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 7 L6 10 L8 10", stroke: "#888", strokeWidth: "1.5", fill: "none", opacity: "0.6" }),
|
|
1994
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 15 L12 18 L14 18", stroke: "#888", strokeWidth: "1.5", fill: "none", opacity: "0.6" })
|
|
1995
|
-
]
|
|
1996
|
-
}
|
|
1997
|
-
);
|
|
1998
|
-
}
|
|
1999
|
-
function computeDiffMap(prev, next) {
|
|
2000
|
-
const prevMap = new Map(prev.map((b) => [b.id, b]));
|
|
2001
|
-
const changeMap = /* @__PURE__ */ new Map();
|
|
2002
|
-
for (const block of next) {
|
|
2003
|
-
const prevBlock = prevMap.get(block.id);
|
|
2004
|
-
if (!prevBlock) {
|
|
2005
|
-
changeMap.set(block.id, "added");
|
|
2006
|
-
} else if (prevBlock.parentId !== block.parentId || prevBlock.order !== block.order) {
|
|
2007
|
-
changeMap.set(block.id, "moved");
|
|
2008
|
-
} else {
|
|
2009
|
-
changeMap.set(block.id, "unchanged");
|
|
2010
|
-
}
|
|
2011
|
-
}
|
|
2012
|
-
return changeMap;
|
|
2013
|
-
}
|
|
2014
|
-
function buildDiffTree(blocks, changeMap) {
|
|
2015
|
-
const result = [];
|
|
2016
|
-
const byParent = /* @__PURE__ */ new Map();
|
|
2017
|
-
for (const block of blocks) {
|
|
2018
|
-
const key = block.parentId ?? null;
|
|
2019
|
-
const list = byParent.get(key) ?? [];
|
|
2020
|
-
list.push(block);
|
|
2021
|
-
byParent.set(key, list);
|
|
2022
|
-
}
|
|
2023
|
-
function walk(parentId, depth) {
|
|
2024
|
-
const children = byParent.get(parentId) ?? [];
|
|
2025
|
-
children.sort((a, b) => {
|
|
2026
|
-
const ao = a.order, bo = b.order;
|
|
2027
|
-
if (typeof ao === "number" && typeof bo === "number") return ao - bo;
|
|
2028
|
-
return String(ao) < String(bo) ? -1 : String(ao) > String(bo) ? 1 : 0;
|
|
2029
|
-
});
|
|
2030
|
-
for (const block of children) {
|
|
2031
|
-
result.push({ block, changeType: changeMap.get(block.id) ?? "unchanged", depth });
|
|
2032
|
-
walk(block.id, depth + 1);
|
|
2033
|
-
}
|
|
2034
|
-
}
|
|
2035
|
-
walk(null, 0);
|
|
2036
|
-
return result;
|
|
2037
|
-
}
|
|
2038
|
-
var TYPE_COLORS = {
|
|
2039
|
-
dragStart: "#3b82f6",
|
|
2040
|
-
dragEnd: "#10b981",
|
|
2041
|
-
blockMove: "#f59e0b",
|
|
2042
|
-
expandChange: "#8b5cf6",
|
|
2043
|
-
hoverChange: "#6b7280"
|
|
2044
|
-
};
|
|
2045
|
-
var TYPE_LABELS = {
|
|
2046
|
-
dragStart: "DRAG",
|
|
2047
|
-
dragEnd: "DROP",
|
|
2048
|
-
blockMove: "MOVE",
|
|
2049
|
-
expandChange: "EXPAND",
|
|
2050
|
-
hoverChange: "HOVER"
|
|
2051
|
-
};
|
|
2052
|
-
var TYPE_TOOLTIPS = {
|
|
2053
|
-
dragStart: "onDragStart \u2014 a block was picked up",
|
|
2054
|
-
dragEnd: "onDragEnd \u2014 a block was dropped or drag was cancelled",
|
|
2055
|
-
blockMove: "onBlockMove \u2014 a block was reparented to a new position",
|
|
2056
|
-
expandChange: "onExpandChange \u2014 a container was expanded or collapsed",
|
|
2057
|
-
hoverChange: "onHoverChange \u2014 the pointer entered a drop zone"
|
|
2058
|
-
};
|
|
2059
|
-
var DEFAULT_WIDTH = 340;
|
|
2060
|
-
var DEFAULT_HEIGHT = 420;
|
|
2061
|
-
var DIFF_EXTRA_WIDTH = 300;
|
|
2062
|
-
var MIN_WIDTH = 280;
|
|
2063
|
-
var MIN_HEIGHT = 200;
|
|
2064
|
-
function getAnchorCSS(position) {
|
|
2065
|
-
switch (position) {
|
|
2066
|
-
case "bottom-right":
|
|
2067
|
-
return { bottom: 16, right: 16 };
|
|
2068
|
-
case "top-left":
|
|
2069
|
-
return { top: 16, left: 16 };
|
|
2070
|
-
case "top-right":
|
|
2071
|
-
return { top: 16, right: 16 };
|
|
2072
|
-
case "bottom-left":
|
|
2073
|
-
default:
|
|
2074
|
-
return { bottom: 16, left: 16 };
|
|
2075
|
-
}
|
|
2076
|
-
}
|
|
2077
|
-
function computeCardOrigin(position, width, height) {
|
|
2078
|
-
const vw = typeof window !== "undefined" ? window.innerWidth : 1024;
|
|
2079
|
-
const vh = typeof window !== "undefined" ? window.innerHeight : 768;
|
|
2080
|
-
switch (position) {
|
|
2081
|
-
case "bottom-right":
|
|
2082
|
-
return { x: Math.max(0, vw - 16 - width), y: Math.max(0, vh - 16 - height - 48) };
|
|
2083
|
-
case "top-left":
|
|
2084
|
-
return { x: 16, y: 16 + 48 };
|
|
2085
|
-
case "top-right":
|
|
2086
|
-
return { x: Math.max(0, vw - 16 - width), y: 16 + 48 };
|
|
2087
|
-
case "bottom-left":
|
|
2088
|
-
default:
|
|
2089
|
-
return { x: 16, y: Math.max(0, vh - 16 - height - 48) };
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
2092
|
-
function BlockTreeDevTools({
|
|
2093
|
-
blocks,
|
|
2094
|
-
containerTypes = [],
|
|
2095
|
-
events,
|
|
2096
|
-
onClearEvents,
|
|
2097
|
-
getLabel = (b) => b.type,
|
|
2098
|
-
initialOpen = false,
|
|
2099
|
-
position = "bottom-left",
|
|
2100
|
-
buttonStyle,
|
|
2101
|
-
panelStyle
|
|
2102
|
-
}) {
|
|
2103
|
-
const [isOpen, setIsOpen] = react.useState(initialOpen);
|
|
2104
|
-
const [showDiff, setShowDiff] = react.useState(false);
|
|
2105
|
-
const [cardPos, setCardPos] = react.useState(null);
|
|
2106
|
-
const [cardSize, setCardSize] = react.useState({ w: DEFAULT_WIDTH, h: DEFAULT_HEIGHT });
|
|
2107
|
-
const [showTooltip, setShowTooltip] = react.useState(false);
|
|
2108
|
-
const dragRef = react.useRef({
|
|
2109
|
-
dragging: false,
|
|
2110
|
-
startX: 0,
|
|
2111
|
-
startY: 0,
|
|
2112
|
-
origX: 0,
|
|
2113
|
-
origY: 0
|
|
2114
|
-
});
|
|
2115
|
-
const resizeRef = react.useRef({ active: false, edge: "", startX: 0, startY: 0, origX: 0, origY: 0, origW: 0, origH: 0 });
|
|
2116
|
-
const cardRef = react.useRef(null);
|
|
2117
|
-
const prevBlocksRef = react.useRef(blocks);
|
|
2118
|
-
const diffChangeMap = react.useMemo(() => computeDiffMap(prevBlocksRef.current, blocks), [blocks]);
|
|
2119
|
-
react.useEffect(() => {
|
|
2120
|
-
prevBlocksRef.current = blocks;
|
|
2121
|
-
}, [blocks]);
|
|
2122
|
-
const diffTree = react.useMemo(() => buildDiffTree(blocks, diffChangeMap), [blocks, diffChangeMap]);
|
|
2123
|
-
const diffStats = react.useMemo(() => {
|
|
2124
|
-
let added = 0, moved = 0;
|
|
2125
|
-
for (const { changeType } of diffTree) {
|
|
2126
|
-
if (changeType === "added") added++;
|
|
2127
|
-
if (changeType === "moved") moved++;
|
|
2128
|
-
}
|
|
2129
|
-
return { added, moved };
|
|
2130
|
-
}, [diffTree]);
|
|
2131
|
-
react.useEffect(() => {
|
|
2132
|
-
setCardSize((prev) => {
|
|
2133
|
-
const targetW = showDiff ? DEFAULT_WIDTH + DIFF_EXTRA_WIDTH : DEFAULT_WIDTH;
|
|
2134
|
-
const wasDefault = Math.abs(prev.w - DEFAULT_WIDTH) < 20;
|
|
2135
|
-
const wasExpanded = Math.abs(prev.w - (DEFAULT_WIDTH + DIFF_EXTRA_WIDTH)) < 20;
|
|
2136
|
-
if (showDiff && (wasDefault || prev.w < targetW)) {
|
|
2137
|
-
return { ...prev, w: targetW };
|
|
2138
|
-
}
|
|
2139
|
-
if (!showDiff && wasExpanded) {
|
|
2140
|
-
return { ...prev, w: DEFAULT_WIDTH };
|
|
2141
|
-
}
|
|
2142
|
-
return prev;
|
|
2143
|
-
});
|
|
2144
|
-
}, [showDiff]);
|
|
2145
|
-
const getDefaultCardPos = react.useCallback(() => {
|
|
2146
|
-
return computeCardOrigin(position, cardSize.w, cardSize.h);
|
|
2147
|
-
}, [position, cardSize.w, cardSize.h]);
|
|
2148
|
-
react.useEffect(() => {
|
|
2149
|
-
if (isOpen && !cardPos) {
|
|
2150
|
-
setCardPos(getDefaultCardPos());
|
|
2151
|
-
}
|
|
2152
|
-
}, [isOpen, cardPos, getDefaultCardPos]);
|
|
2153
|
-
const handleDragPointerDown = react.useCallback((e) => {
|
|
2154
|
-
e.preventDefault();
|
|
2155
|
-
dragRef.current = {
|
|
2156
|
-
dragging: true,
|
|
2157
|
-
startX: e.clientX,
|
|
2158
|
-
startY: e.clientY,
|
|
2159
|
-
origX: cardPos?.x ?? 0,
|
|
2160
|
-
origY: cardPos?.y ?? 0
|
|
2161
|
-
};
|
|
2162
|
-
e.target.setPointerCapture(e.pointerId);
|
|
2163
|
-
}, [cardPos]);
|
|
2164
|
-
const handleDragPointerMove = react.useCallback((e) => {
|
|
2165
|
-
if (!dragRef.current.dragging) return;
|
|
2166
|
-
const dx = e.clientX - dragRef.current.startX;
|
|
2167
|
-
const dy = e.clientY - dragRef.current.startY;
|
|
2168
|
-
const newX = dragRef.current.origX + dx;
|
|
2169
|
-
const newY = dragRef.current.origY + dy;
|
|
2170
|
-
const maxX = window.innerWidth - cardSize.w;
|
|
2171
|
-
const maxY = window.innerHeight - 40;
|
|
2172
|
-
setCardPos({
|
|
2173
|
-
x: Math.max(0, Math.min(newX, maxX)),
|
|
2174
|
-
y: Math.max(0, Math.min(newY, maxY))
|
|
2175
|
-
});
|
|
2176
|
-
}, [cardSize.w]);
|
|
2177
|
-
const handleDragPointerUp = react.useCallback(() => {
|
|
2178
|
-
dragRef.current.dragging = false;
|
|
2179
|
-
}, []);
|
|
2180
|
-
const handleResizePointerDown = react.useCallback((edge) => (e) => {
|
|
2181
|
-
e.preventDefault();
|
|
2182
|
-
e.stopPropagation();
|
|
2183
|
-
resizeRef.current = {
|
|
2184
|
-
active: true,
|
|
2185
|
-
edge,
|
|
2186
|
-
startX: e.clientX,
|
|
2187
|
-
startY: e.clientY,
|
|
2188
|
-
origX: cardPos?.x ?? 0,
|
|
2189
|
-
origY: cardPos?.y ?? 0,
|
|
2190
|
-
origW: cardSize.w,
|
|
2191
|
-
origH: cardSize.h
|
|
2192
|
-
};
|
|
2193
|
-
e.target.setPointerCapture(e.pointerId);
|
|
2194
|
-
}, [cardPos, cardSize]);
|
|
2195
|
-
const handleResizePointerMove = react.useCallback((e) => {
|
|
2196
|
-
const r = resizeRef.current;
|
|
2197
|
-
if (!r.active) return;
|
|
2198
|
-
const dx = e.clientX - r.startX;
|
|
2199
|
-
const dy = e.clientY - r.startY;
|
|
2200
|
-
let newW = r.origW;
|
|
2201
|
-
let newH = r.origH;
|
|
2202
|
-
let newX = r.origX;
|
|
2203
|
-
let newY = r.origY;
|
|
2204
|
-
if (r.edge.includes("e")) newW = Math.max(MIN_WIDTH, r.origW + dx);
|
|
2205
|
-
if (r.edge.includes("w")) {
|
|
2206
|
-
newW = Math.max(MIN_WIDTH, r.origW - dx);
|
|
2207
|
-
newX = r.origX + (r.origW - newW);
|
|
2208
|
-
}
|
|
2209
|
-
if (r.edge.includes("s")) newH = Math.max(MIN_HEIGHT, r.origH + dy);
|
|
2210
|
-
if (r.edge.includes("n")) {
|
|
2211
|
-
newH = Math.max(MIN_HEIGHT, r.origH - dy);
|
|
2212
|
-
newY = r.origY + (r.origH - newH);
|
|
2213
|
-
}
|
|
2214
|
-
setCardSize({ w: newW, h: newH });
|
|
2215
|
-
setCardPos({ x: newX, y: newY });
|
|
2216
|
-
}, []);
|
|
2217
|
-
const handleResizePointerUp = react.useCallback(() => {
|
|
2218
|
-
resizeRef.current.active = false;
|
|
2219
|
-
}, []);
|
|
2220
|
-
const toggle = react.useCallback(() => {
|
|
2221
|
-
setIsOpen((prev) => !prev);
|
|
2222
|
-
}, []);
|
|
2223
|
-
const toggleDiff = react.useCallback(() => {
|
|
2224
|
-
setShowDiff((prev) => !prev);
|
|
2225
|
-
}, []);
|
|
2226
|
-
const renderCountRef = react.useRef(0);
|
|
2227
|
-
const lastRenderTimeRef = react.useRef(performance.now());
|
|
2228
|
-
const prevBlockCountRef = react.useRef(blocks.length);
|
|
2229
|
-
renderCountRef.current++;
|
|
2230
|
-
const renderTime = performance.now() - lastRenderTimeRef.current;
|
|
2231
|
-
lastRenderTimeRef.current = performance.now();
|
|
2232
|
-
const blockCountDelta = blocks.length - prevBlockCountRef.current;
|
|
2233
|
-
react.useEffect(() => {
|
|
2234
|
-
prevBlockCountRef.current = blocks.length;
|
|
2235
|
-
}, [blocks.length]);
|
|
2236
|
-
const treeStats = react.useMemo(() => {
|
|
2237
|
-
const index = computeNormalizedIndex(blocks);
|
|
2238
|
-
const containers = blocks.filter((b) => containerTypes.includes(b.type));
|
|
2239
|
-
let maxDepthVal = 0;
|
|
2240
|
-
for (const block of blocks) {
|
|
2241
|
-
const d = getBlockDepth(index, block.id);
|
|
2242
|
-
if (d > maxDepthVal) maxDepthVal = d;
|
|
2243
|
-
}
|
|
2244
|
-
const validation = validateBlockTree(index);
|
|
2245
|
-
return {
|
|
2246
|
-
blockCount: blocks.length,
|
|
2247
|
-
containerCount: containers.length,
|
|
2248
|
-
maxDepth: maxDepthVal,
|
|
2249
|
-
validation
|
|
2250
|
-
};
|
|
2251
|
-
}, [blocks, containerTypes]);
|
|
2252
|
-
const formatTime = (ts) => {
|
|
2253
|
-
const d = new Date(ts);
|
|
2254
|
-
return `${d.getHours().toString().padStart(2, "0")}:${d.getMinutes().toString().padStart(2, "0")}:${d.getSeconds().toString().padStart(2, "0")}`;
|
|
2255
|
-
};
|
|
2256
|
-
const anchor = getAnchorCSS(position);
|
|
2257
|
-
const triggerBtnStyle = {
|
|
2258
|
-
position: "fixed",
|
|
2259
|
-
...anchor,
|
|
2260
|
-
zIndex: 99998,
|
|
2261
|
-
width: 40,
|
|
2262
|
-
height: 40,
|
|
2263
|
-
borderRadius: "50%",
|
|
2264
|
-
border: "none",
|
|
2265
|
-
background: isOpen ? "rgba(59, 130, 246, 0.9)" : "rgba(30, 30, 30, 0.85)",
|
|
2266
|
-
color: "#fff",
|
|
2267
|
-
cursor: "pointer",
|
|
2268
|
-
display: "flex",
|
|
2269
|
-
alignItems: "center",
|
|
2270
|
-
justifyContent: "center",
|
|
2271
|
-
boxShadow: "0 2px 8px rgba(0,0,0,0.3)",
|
|
2272
|
-
transition: "background 0.15s, transform 0.15s",
|
|
2273
|
-
...buttonStyle
|
|
2274
|
-
};
|
|
2275
|
-
const tooltipStyle = {
|
|
2276
|
-
position: "absolute",
|
|
2277
|
-
whiteSpace: "nowrap",
|
|
2278
|
-
fontSize: 11,
|
|
2279
|
-
fontWeight: 500,
|
|
2280
|
-
padding: "4px 10px",
|
|
2281
|
-
borderRadius: 6,
|
|
2282
|
-
background: "rgba(15, 15, 20, 0.95)",
|
|
2283
|
-
color: "#ccc",
|
|
2284
|
-
boxShadow: "0 2px 8px rgba(0,0,0,0.3)",
|
|
2285
|
-
pointerEvents: "none",
|
|
2286
|
-
// Position based on anchor corner
|
|
2287
|
-
...anchor.bottom !== void 0 ? { bottom: "100%", marginBottom: 8 } : { top: "100%", marginTop: 8 },
|
|
2288
|
-
...anchor.left !== void 0 ? { left: 0 } : { right: 0 }
|
|
2289
|
-
};
|
|
2290
|
-
const cardStyle = {
|
|
2291
|
-
position: "fixed",
|
|
2292
|
-
left: cardPos?.x ?? 0,
|
|
2293
|
-
top: cardPos?.y ?? 0,
|
|
2294
|
-
zIndex: 99999,
|
|
2295
|
-
width: cardSize.w,
|
|
2296
|
-
height: cardSize.h,
|
|
2297
|
-
background: "rgba(20, 20, 24, 0.95)",
|
|
2298
|
-
backdropFilter: "blur(12px)",
|
|
2299
|
-
border: "1px solid rgba(255,255,255,0.1)",
|
|
2300
|
-
borderRadius: 10,
|
|
2301
|
-
color: "#e0e0e0",
|
|
2302
|
-
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
2303
|
-
fontSize: 12,
|
|
2304
|
-
display: "flex",
|
|
2305
|
-
flexDirection: "column",
|
|
2306
|
-
boxShadow: "0 8px 32px rgba(0,0,0,0.5)",
|
|
2307
|
-
overflow: "hidden",
|
|
2308
|
-
...panelStyle
|
|
2309
|
-
};
|
|
2310
|
-
const titleBarStyle = {
|
|
2311
|
-
display: "flex",
|
|
2312
|
-
alignItems: "center",
|
|
2313
|
-
justifyContent: "space-between",
|
|
2314
|
-
padding: "8px 12px",
|
|
2315
|
-
gap: 8,
|
|
2316
|
-
background: "rgba(255,255,255,0.04)",
|
|
2317
|
-
borderBottom: "1px solid rgba(255,255,255,0.08)",
|
|
2318
|
-
cursor: "grab",
|
|
2319
|
-
userSelect: "none",
|
|
2320
|
-
flexShrink: 0,
|
|
2321
|
-
touchAction: "none"
|
|
2322
|
-
};
|
|
2323
|
-
const titleTextStyle = {
|
|
2324
|
-
fontSize: 11,
|
|
2325
|
-
fontWeight: 600,
|
|
2326
|
-
letterSpacing: "0.03em",
|
|
2327
|
-
opacity: 0.8,
|
|
2328
|
-
flexShrink: 0
|
|
2329
|
-
};
|
|
2330
|
-
const titleBtnStyle = (active) => ({
|
|
2331
|
-
height: 22,
|
|
2332
|
-
padding: "0 8px",
|
|
2333
|
-
borderRadius: 4,
|
|
2334
|
-
border: "1px solid " + (active ? "rgba(59,130,246,0.5)" : "rgba(128,128,128,0.25)"),
|
|
2335
|
-
background: active ? "rgba(59,130,246,0.15)" : "transparent",
|
|
2336
|
-
color: active ? "#93b8f7" : "#999",
|
|
2337
|
-
cursor: "pointer",
|
|
2338
|
-
fontSize: 10,
|
|
2339
|
-
fontWeight: 600,
|
|
2340
|
-
letterSpacing: "0.02em",
|
|
2341
|
-
flexShrink: 0
|
|
2342
|
-
});
|
|
2343
|
-
const closeBtnStyle = {
|
|
2344
|
-
width: 20,
|
|
2345
|
-
height: 20,
|
|
2346
|
-
borderRadius: 4,
|
|
2347
|
-
border: "none",
|
|
2348
|
-
background: "transparent",
|
|
2349
|
-
color: "#999",
|
|
2350
|
-
cursor: "pointer",
|
|
2351
|
-
display: "flex",
|
|
2352
|
-
alignItems: "center",
|
|
2353
|
-
justifyContent: "center",
|
|
2354
|
-
fontSize: 14,
|
|
2355
|
-
lineHeight: "1",
|
|
2356
|
-
padding: 0,
|
|
2357
|
-
flexShrink: 0
|
|
2358
|
-
};
|
|
2359
|
-
const bodyStyle = {
|
|
2360
|
-
flex: 1,
|
|
2361
|
-
display: "flex",
|
|
2362
|
-
flexDirection: "row",
|
|
2363
|
-
overflow: "hidden",
|
|
2364
|
-
minHeight: 0
|
|
2365
|
-
};
|
|
2366
|
-
const mainColumnStyle = {
|
|
2367
|
-
flex: showDiff ? "0 0 50%" : "1 1 100%",
|
|
2368
|
-
overflow: "auto",
|
|
2369
|
-
padding: "10px 12px",
|
|
2370
|
-
minWidth: 0
|
|
2371
|
-
};
|
|
2372
|
-
const diffColumnStyle = {
|
|
2373
|
-
flex: "0 0 50%",
|
|
2374
|
-
overflow: "auto",
|
|
2375
|
-
padding: "10px 12px",
|
|
2376
|
-
borderLeft: "1px solid rgba(255,255,255,0.08)",
|
|
2377
|
-
minWidth: 0
|
|
2378
|
-
};
|
|
2379
|
-
const sectionStyle = {
|
|
2380
|
-
marginBottom: 8
|
|
2381
|
-
};
|
|
2382
|
-
const headingStyle = {
|
|
2383
|
-
fontSize: 11,
|
|
2384
|
-
fontWeight: 600,
|
|
2385
|
-
textTransform: "uppercase",
|
|
2386
|
-
letterSpacing: "0.05em",
|
|
2387
|
-
opacity: 0.6,
|
|
2388
|
-
marginBottom: 6
|
|
2389
|
-
};
|
|
2390
|
-
const statRowStyle = {
|
|
2391
|
-
display: "flex",
|
|
2392
|
-
justifyContent: "space-between",
|
|
2393
|
-
alignItems: "center",
|
|
2394
|
-
fontSize: 12,
|
|
2395
|
-
padding: "2px 0"
|
|
2396
|
-
};
|
|
2397
|
-
const statValueStyle = {
|
|
2398
|
-
fontFamily: "monospace",
|
|
2399
|
-
fontSize: 11,
|
|
2400
|
-
opacity: 0.8
|
|
2401
|
-
};
|
|
2402
|
-
const badgeStyle = (color) => ({
|
|
2403
|
-
display: "inline-block",
|
|
2404
|
-
fontSize: 9,
|
|
2405
|
-
fontWeight: 700,
|
|
2406
|
-
fontFamily: "monospace",
|
|
2407
|
-
padding: "1px 5px",
|
|
2408
|
-
borderRadius: 3,
|
|
2409
|
-
backgroundColor: color + "22",
|
|
2410
|
-
color,
|
|
2411
|
-
marginRight: 6,
|
|
2412
|
-
flexShrink: 0
|
|
2413
|
-
});
|
|
2414
|
-
const eventListStyle = {
|
|
2415
|
-
maxHeight: 160,
|
|
2416
|
-
overflowY: "auto",
|
|
2417
|
-
fontSize: 11,
|
|
2418
|
-
lineHeight: "1.6"
|
|
2419
|
-
};
|
|
2420
|
-
const eventItemStyle = {
|
|
2421
|
-
display: "flex",
|
|
2422
|
-
alignItems: "flex-start",
|
|
2423
|
-
gap: 4,
|
|
2424
|
-
padding: "2px 0",
|
|
2425
|
-
borderBottom: "1px solid rgba(128,128,128,0.1)"
|
|
2426
|
-
};
|
|
2427
|
-
const timeStyle = {
|
|
2428
|
-
fontFamily: "monospace",
|
|
2429
|
-
fontSize: 10,
|
|
2430
|
-
opacity: 0.4,
|
|
2431
|
-
flexShrink: 0,
|
|
2432
|
-
minWidth: 52
|
|
2433
|
-
};
|
|
2434
|
-
const clearBtnStyle = {
|
|
2435
|
-
fontSize: 10,
|
|
2436
|
-
padding: "2px 8px",
|
|
2437
|
-
border: "1px solid rgba(128,128,128,0.3)",
|
|
2438
|
-
borderRadius: 4,
|
|
2439
|
-
background: "transparent",
|
|
2440
|
-
color: "#ccc",
|
|
2441
|
-
cursor: "pointer",
|
|
2442
|
-
opacity: 0.7
|
|
2443
|
-
};
|
|
2444
|
-
const EDGE_SIZE = 6;
|
|
2445
|
-
const resizeEdge = (edge) => {
|
|
2446
|
-
const base = {
|
|
2447
|
-
position: "absolute",
|
|
2448
|
-
zIndex: 1
|
|
2449
|
-
};
|
|
2450
|
-
const cursors = {
|
|
2451
|
-
n: "ns-resize",
|
|
2452
|
-
s: "ns-resize",
|
|
2453
|
-
e: "ew-resize",
|
|
2454
|
-
w: "ew-resize",
|
|
2455
|
-
ne: "nesw-resize",
|
|
2456
|
-
nw: "nwse-resize",
|
|
2457
|
-
se: "nwse-resize",
|
|
2458
|
-
sw: "nesw-resize"
|
|
2459
|
-
};
|
|
2460
|
-
const styles = {
|
|
2461
|
-
n: { top: 0, left: EDGE_SIZE, right: EDGE_SIZE, height: EDGE_SIZE },
|
|
2462
|
-
s: { bottom: 0, left: EDGE_SIZE, right: EDGE_SIZE, height: EDGE_SIZE },
|
|
2463
|
-
e: { right: 0, top: EDGE_SIZE, bottom: EDGE_SIZE, width: EDGE_SIZE },
|
|
2464
|
-
w: { left: 0, top: EDGE_SIZE, bottom: EDGE_SIZE, width: EDGE_SIZE },
|
|
2465
|
-
ne: { top: 0, right: 0, width: EDGE_SIZE * 2, height: EDGE_SIZE * 2 },
|
|
2466
|
-
nw: { top: 0, left: 0, width: EDGE_SIZE * 2, height: EDGE_SIZE * 2 },
|
|
2467
|
-
se: { bottom: 0, right: 0, width: EDGE_SIZE * 2, height: EDGE_SIZE * 2 },
|
|
2468
|
-
sw: { bottom: 0, left: 0, width: EDGE_SIZE * 2, height: EDGE_SIZE * 2 }
|
|
2469
|
-
};
|
|
2470
|
-
return { ...base, cursor: cursors[edge], ...styles[edge] };
|
|
2471
|
-
};
|
|
2472
|
-
const EDGES = ["n", "s", "e", "w", "ne", "nw", "se", "sw"];
|
|
2473
|
-
const diffRowColor = (ct) => {
|
|
2474
|
-
if (ct === "added") return { background: "rgba(16,185,129,0.1)", color: "#34d399" };
|
|
2475
|
-
if (ct === "moved") return { background: "rgba(245,158,11,0.1)", color: "#fbbf24" };
|
|
2476
|
-
return { color: "rgba(200,200,200,0.5)" };
|
|
2477
|
-
};
|
|
2478
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2479
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "fixed", ...anchor, zIndex: 99998 }, children: [
|
|
2480
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2481
|
-
"button",
|
|
2482
|
-
{
|
|
2483
|
-
onClick: toggle,
|
|
2484
|
-
onMouseEnter: () => setShowTooltip(true),
|
|
2485
|
-
onMouseLeave: () => setShowTooltip(false),
|
|
2486
|
-
style: triggerBtnStyle,
|
|
2487
|
-
"aria-label": isOpen ? "Close DevTools" : "Open DevTools",
|
|
2488
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(DevToolsLogo, { size: 20 })
|
|
2489
|
-
}
|
|
2490
|
-
),
|
|
2491
|
-
showTooltip && !isOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { style: tooltipStyle, children: "dnd-block-tree DevTools" })
|
|
2492
|
-
] }),
|
|
2493
|
-
isOpen && cardPos && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2494
|
-
"div",
|
|
2495
|
-
{
|
|
2496
|
-
ref: cardRef,
|
|
2497
|
-
style: cardStyle,
|
|
2498
|
-
"data-devtools-root": "",
|
|
2499
|
-
children: [
|
|
2500
|
-
EDGES.map((edge) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2501
|
-
"div",
|
|
2502
|
-
{
|
|
2503
|
-
style: resizeEdge(edge),
|
|
2504
|
-
onPointerDown: handleResizePointerDown(edge),
|
|
2505
|
-
onPointerMove: handleResizePointerMove,
|
|
2506
|
-
onPointerUp: handleResizePointerUp
|
|
2507
|
-
},
|
|
2508
|
-
edge
|
|
2509
|
-
)),
|
|
2510
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2511
|
-
"div",
|
|
2512
|
-
{
|
|
2513
|
-
style: titleBarStyle,
|
|
2514
|
-
onPointerDown: handleDragPointerDown,
|
|
2515
|
-
onPointerMove: handleDragPointerMove,
|
|
2516
|
-
onPointerUp: handleDragPointerUp,
|
|
2517
|
-
children: [
|
|
2518
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: titleTextStyle, children: "DevTools" }),
|
|
2519
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1 } }),
|
|
2520
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2521
|
-
"button",
|
|
2522
|
-
{
|
|
2523
|
-
onClick: toggleDiff,
|
|
2524
|
-
style: titleBtnStyle(showDiff),
|
|
2525
|
-
onPointerDown: (e) => e.stopPropagation(),
|
|
2526
|
-
title: "Toggle structure diff \u2014 shows which blocks were added, moved, or unchanged",
|
|
2527
|
-
children: "Diff"
|
|
2528
|
-
}
|
|
2529
|
-
),
|
|
2530
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2531
|
-
"button",
|
|
2532
|
-
{
|
|
2533
|
-
onClick: toggle,
|
|
2534
|
-
style: closeBtnStyle,
|
|
2535
|
-
onPointerDown: (e) => e.stopPropagation(),
|
|
2536
|
-
"aria-label": "Close DevTools",
|
|
2537
|
-
children: "\xD7"
|
|
2538
|
-
}
|
|
2539
|
-
)
|
|
2540
|
-
]
|
|
2541
|
-
}
|
|
2542
|
-
),
|
|
2543
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: bodyStyle, children: [
|
|
2544
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: mainColumnStyle, children: [
|
|
2545
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, "data-devtools-section": "tree-state", children: [
|
|
2546
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: headingStyle, title: "Live snapshot of the block tree structure", children: "Tree State" }),
|
|
2547
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: statRowStyle, title: "Total number of blocks in the flat array", children: [
|
|
2548
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Blocks" }),
|
|
2549
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: statValueStyle, children: treeStats.blockCount })
|
|
2550
|
-
] }),
|
|
2551
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: statRowStyle, title: "Blocks whose type is in containerTypes (can have children)", children: [
|
|
2552
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Containers" }),
|
|
2553
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: statValueStyle, children: treeStats.containerCount })
|
|
2554
|
-
] }),
|
|
2555
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: statRowStyle, title: "Deepest nesting level in the tree (root = 1)", children: [
|
|
2556
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Max Depth" }),
|
|
2557
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: statValueStyle, children: treeStats.maxDepth })
|
|
2558
|
-
] }),
|
|
2559
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: statRowStyle, title: "Checks for cycles, orphans, and stale parent refs", children: [
|
|
2560
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Validation" }),
|
|
2561
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: {
|
|
2562
|
-
...statValueStyle,
|
|
2563
|
-
color: treeStats.validation.valid ? "#10b981" : "#ef4444"
|
|
2564
|
-
}, children: treeStats.validation.valid ? "Valid" : `${treeStats.validation.issues.length} issue(s)` })
|
|
2565
|
-
] }),
|
|
2566
|
-
!treeStats.validation.valid && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 10, color: "#ef4444", marginTop: 4 }, children: treeStats.validation.issues.map((issue, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { children: issue }, i)) })
|
|
2567
|
-
] }),
|
|
2568
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, "data-devtools-section": "event-log", children: [
|
|
2569
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }, children: [
|
|
2570
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: headingStyle, title: "Chronological log of drag, drop, move, expand, and hover events", children: [
|
|
2571
|
-
"Event Log (",
|
|
2572
|
-
events.length,
|
|
2573
|
-
")"
|
|
2574
|
-
] }),
|
|
2575
|
-
events.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onClearEvents, style: clearBtnStyle, children: "Clear" })
|
|
2576
|
-
] }),
|
|
2577
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: eventListStyle, children: events.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, opacity: 0.4, padding: "8px 0" }, children: "No events yet. Drag some blocks!" }) : events.map((event) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: eventItemStyle, children: [
|
|
2578
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: timeStyle, children: formatTime(event.timestamp) }),
|
|
2579
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: badgeStyle(TYPE_COLORS[event.type]), title: TYPE_TOOLTIPS[event.type], children: TYPE_LABELS[event.type] }),
|
|
2580
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { wordBreak: "break-word" }, children: event.summary })
|
|
2581
|
-
] }, event.id)) })
|
|
2582
|
-
] }),
|
|
2583
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, "data-devtools-section": "performance", children: [
|
|
2584
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: headingStyle, title: "Render metrics for the DevTools component itself", children: "Performance" }),
|
|
2585
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: statRowStyle, title: "How many times this DevTools component has rendered", children: [
|
|
2586
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Render Count" }),
|
|
2587
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: statValueStyle, children: renderCountRef.current })
|
|
2588
|
-
] }),
|
|
2589
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: statRowStyle, title: "Time since the previous render of this component", children: [
|
|
2590
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Last Render" }),
|
|
2591
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: statValueStyle, children: [
|
|
2592
|
-
renderTime.toFixed(1),
|
|
2593
|
-
"ms"
|
|
2594
|
-
] })
|
|
2595
|
-
] }),
|
|
2596
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: statRowStyle, title: "Change in block count since the last render (+added / -removed)", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { style: {
|
|
2597
|
-
...statValueStyle,
|
|
2598
|
-
color: blockCountDelta > 0 ? "#10b981" : blockCountDelta < 0 ? "#ef4444" : void 0
|
|
2599
|
-
}, children: [
|
|
2600
|
-
blockCountDelta > 0 ? "+" : "",
|
|
2601
|
-
blockCountDelta
|
|
2602
|
-
] }) })
|
|
2603
|
-
] })
|
|
2604
|
-
] }),
|
|
2605
|
-
showDiff && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: diffColumnStyle, children: [
|
|
2606
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }, children: [
|
|
2607
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: headingStyle, title: "Tree structure diff \u2014 compares current state to the previous render", children: "Structure" }),
|
|
2608
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 10, fontSize: 11, fontWeight: 500 }, children: [
|
|
2609
|
-
diffStats.added > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color: "#34d399", display: "flex", alignItems: "center", gap: 4 }, children: [
|
|
2610
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { width: 5, height: 5, borderRadius: "50%", background: "#34d399", display: "inline-block" } }),
|
|
2611
|
-
diffStats.added,
|
|
2612
|
-
" new"
|
|
2613
|
-
] }),
|
|
2614
|
-
diffStats.moved > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color: "#fbbf24", display: "flex", alignItems: "center", gap: 4 }, children: [
|
|
2615
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { width: 5, height: 5, borderRadius: "50%", background: "#fbbf24", display: "inline-block" } }),
|
|
2616
|
-
diffStats.moved,
|
|
2617
|
-
" moved"
|
|
2618
|
-
] }),
|
|
2619
|
-
diffStats.added === 0 && diffStats.moved === 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.4 }, children: "No changes" })
|
|
2620
|
-
] })
|
|
2621
|
-
] }),
|
|
2622
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontFamily: "monospace", fontSize: 11, lineHeight: "1.7" }, children: diffTree.map(({ block, changeType, depth }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2623
|
-
"div",
|
|
2624
|
-
{
|
|
2625
|
-
style: {
|
|
2626
|
-
paddingLeft: depth * 14,
|
|
2627
|
-
padding: "2px 6px 2px " + (depth * 14 + 6) + "px",
|
|
2628
|
-
borderRadius: 4,
|
|
2629
|
-
display: "flex",
|
|
2630
|
-
alignItems: "center",
|
|
2631
|
-
gap: 6,
|
|
2632
|
-
...diffRowColor(changeType)
|
|
2633
|
-
},
|
|
2634
|
-
children: [
|
|
2635
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: { width: 12, textAlign: "center", fontWeight: 700, fontSize: 10 }, children: [
|
|
2636
|
-
changeType === "added" && "+",
|
|
2637
|
-
changeType === "moved" && "~"
|
|
2638
|
-
] }),
|
|
2639
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: {
|
|
2640
|
-
textTransform: "uppercase",
|
|
2641
|
-
fontSize: 9,
|
|
2642
|
-
letterSpacing: "0.05em",
|
|
2643
|
-
width: 50,
|
|
2644
|
-
flexShrink: 0,
|
|
2645
|
-
opacity: changeType === "unchanged" ? 0.5 : 1
|
|
2646
|
-
}, children: block.type }),
|
|
2647
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: {
|
|
2648
|
-
overflow: "hidden",
|
|
2649
|
-
textOverflow: "ellipsis",
|
|
2650
|
-
whiteSpace: "nowrap",
|
|
2651
|
-
flex: 1,
|
|
2652
|
-
minWidth: 0
|
|
2653
|
-
}, children: getLabel(block) }),
|
|
2654
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: {
|
|
2655
|
-
fontFamily: "monospace",
|
|
2656
|
-
fontSize: 10,
|
|
2657
|
-
flexShrink: 0,
|
|
2658
|
-
padding: "1px 5px",
|
|
2659
|
-
borderRadius: 3,
|
|
2660
|
-
...changeType === "added" ? { background: "rgba(16,185,129,0.2)", color: "#34d399" } : changeType === "moved" ? { background: "rgba(245,158,11,0.2)", color: "#fbbf24" } : { background: "rgba(128,128,128,0.15)", color: "rgba(200,200,200,0.4)" }
|
|
2661
|
-
}, children: String(block.order) })
|
|
2662
|
-
]
|
|
2663
|
-
},
|
|
2664
|
-
block.id
|
|
2665
|
-
)) })
|
|
2666
|
-
] })
|
|
2667
|
-
] })
|
|
2668
|
-
]
|
|
2669
|
-
}
|
|
2670
|
-
)
|
|
2671
|
-
] });
|
|
2672
|
-
}
|
|
2673
|
-
|
|
2674
|
-
// src/utils/serialization.ts
|
|
2675
|
-
function flatToNested(blocks) {
|
|
2676
|
-
const byParent = /* @__PURE__ */ new Map();
|
|
2677
|
-
for (const block of blocks) {
|
|
2678
|
-
const key = block.parentId ?? null;
|
|
2679
|
-
const list = byParent.get(key);
|
|
2680
|
-
if (list) {
|
|
2681
|
-
list.push(block);
|
|
2682
|
-
} else {
|
|
2683
|
-
byParent.set(key, [block]);
|
|
2684
|
-
}
|
|
2685
|
-
}
|
|
2686
|
-
for (const list of byParent.values()) {
|
|
2687
|
-
list.sort((a, b) => {
|
|
2688
|
-
if (typeof a.order === "string" && typeof b.order === "string") {
|
|
2689
|
-
return a.order < b.order ? -1 : a.order > b.order ? 1 : 0;
|
|
2690
|
-
}
|
|
2691
|
-
return Number(a.order) - Number(b.order);
|
|
2692
|
-
});
|
|
2693
|
-
}
|
|
2694
|
-
function buildChildren(parentId) {
|
|
2695
|
-
const siblings = byParent.get(parentId) ?? [];
|
|
2696
|
-
return siblings.map((block) => {
|
|
2697
|
-
const { parentId: _p, order: _o, ...rest } = block;
|
|
2698
|
-
return {
|
|
2699
|
-
...rest,
|
|
2700
|
-
children: buildChildren(block.id)
|
|
2701
|
-
};
|
|
2702
|
-
});
|
|
2703
|
-
}
|
|
2704
|
-
return buildChildren(null);
|
|
2705
|
-
}
|
|
2706
|
-
function nestedToFlat(nested) {
|
|
2707
|
-
const result = [];
|
|
2708
|
-
function walk(nodes, parentId) {
|
|
2709
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
2710
|
-
const { children, ...rest } = nodes[i];
|
|
2711
|
-
result.push({
|
|
2712
|
-
...rest,
|
|
2713
|
-
parentId,
|
|
2714
|
-
order: i
|
|
2715
|
-
});
|
|
2716
|
-
walk(children, rest.id);
|
|
2717
|
-
}
|
|
2718
|
-
}
|
|
2719
|
-
walk(nested, null);
|
|
2720
|
-
return result;
|
|
2721
|
-
}
|
|
2722
|
-
|
|
2723
|
-
exports.BlockTree = BlockTree;
|
|
2724
|
-
exports.BlockTreeDevTools = BlockTreeDevTools;
|
|
2725
|
-
exports.BlockTreeSSR = BlockTreeSSR;
|
|
2726
|
-
exports.DragOverlay = DragOverlay;
|
|
2727
|
-
exports.DropZone = DropZone;
|
|
2728
|
-
exports.TreeRenderer = TreeRenderer;
|
|
2729
|
-
exports.buildOrderedBlocks = buildOrderedBlocks;
|
|
2730
|
-
exports.cloneMap = cloneMap;
|
|
2731
|
-
exports.cloneParentMap = cloneParentMap;
|
|
2732
|
-
exports.closestCenterCollision = closestCenterCollision;
|
|
2733
|
-
exports.compareFractionalKeys = compareFractionalKeys;
|
|
2734
|
-
exports.computeNormalizedIndex = computeNormalizedIndex;
|
|
2735
|
-
exports.createBlockState = createBlockState;
|
|
2736
|
-
exports.createStickyCollision = createStickyCollision;
|
|
2737
|
-
exports.createTreeState = createTreeState;
|
|
2738
|
-
exports.debounce = debounce;
|
|
2739
|
-
exports.deleteBlockAndDescendants = deleteBlockAndDescendants;
|
|
2740
|
-
exports.extractBlockId = extractBlockId;
|
|
2741
|
-
exports.extractUUID = extractUUID;
|
|
2742
|
-
exports.flatToNested = flatToNested;
|
|
2743
|
-
exports.generateId = generateId;
|
|
2744
|
-
exports.generateInitialKeys = generateInitialKeys;
|
|
2745
|
-
exports.generateKeyBetween = generateKeyBetween;
|
|
2746
|
-
exports.generateNKeysBetween = generateNKeysBetween;
|
|
2747
|
-
exports.getBlockDepth = getBlockDepth;
|
|
2748
|
-
exports.getDescendantIds = getDescendantIds;
|
|
2749
|
-
exports.getDropZoneType = getDropZoneType;
|
|
2750
|
-
exports.getSensorConfig = getSensorConfig;
|
|
2751
|
-
exports.getSubtreeDepth = getSubtreeDepth;
|
|
2752
|
-
exports.initFractionalOrder = initFractionalOrder;
|
|
2753
|
-
exports.nestedToFlat = nestedToFlat;
|
|
2754
|
-
exports.reparentBlockIndex = reparentBlockIndex;
|
|
2755
|
-
exports.reparentMultipleBlocks = reparentMultipleBlocks;
|
|
2756
|
-
exports.triggerHaptic = triggerHaptic;
|
|
2757
|
-
exports.useBlockHistory = useBlockHistory;
|
|
2758
|
-
exports.useConfiguredSensors = useConfiguredSensors;
|
|
2759
|
-
exports.useDevToolsCallbacks = useDevToolsCallbacks;
|
|
2760
|
-
exports.useLayoutAnimation = useLayoutAnimation;
|
|
2761
|
-
exports.useVirtualTree = useVirtualTree;
|
|
2762
|
-
exports.validateBlockTree = validateBlockTree;
|
|
2763
|
-
exports.weightedVerticalCollision = weightedVerticalCollision;
|
|
7
|
+
Object.keys(react).forEach(function (k) {
|
|
8
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
get: function () { return react[k]; }
|
|
11
|
+
});
|
|
12
|
+
});
|
|
2764
13
|
//# sourceMappingURL=index.js.map
|
|
2765
14
|
//# sourceMappingURL=index.js.map
|