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