dnd-block-tree 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -15,6 +15,12 @@ function extractBlockId(zoneId) {
15
15
  }
16
16
 
17
17
  // src/core/collision.ts
18
+ function collisionValue(d) {
19
+ return d.data.value;
20
+ }
21
+ function collisionLeft(d) {
22
+ return d.data.left;
23
+ }
18
24
  function computeCollisionScores(droppableContainers, collisionRect, snapshotRects) {
19
25
  const pointerX = collisionRect.left + collisionRect.width / 2;
20
26
  const pointerY = collisionRect.top + collisionRect.height / 2;
@@ -43,11 +49,7 @@ function computeCollisionScores(droppableContainers, collisionRect, snapshotRect
43
49
  }
44
50
  };
45
51
  }).filter((c) => c !== null);
46
- candidates.sort((a, b) => {
47
- const aValue = a.data.value;
48
- const bValue = b.data.value;
49
- return aValue - bValue;
50
- });
52
+ candidates.sort((a, b) => collisionValue(a) - collisionValue(b));
51
53
  return candidates;
52
54
  }
53
55
  var weightedVerticalCollision = ({
@@ -68,13 +70,13 @@ function createStickyCollision(threshold = 15, snapshotRef) {
68
70
  const candidates = computeCollisionScores(droppableContainers, collisionRect, snapshotRef?.current);
69
71
  if (candidates.length === 0) return [];
70
72
  const bestCandidate = candidates[0];
71
- const bestScore = bestCandidate.data.value;
73
+ const bestScore = collisionValue(bestCandidate);
72
74
  if (currentZoneId !== null) {
73
75
  const currentCandidate = candidates.find((c) => c.id === currentZoneId);
74
76
  if (currentCandidate) {
75
- const currentScore = currentCandidate.data.value;
76
- const currentLeft = currentCandidate.data.left;
77
- const bestLeft = bestCandidate.data.left;
77
+ const currentScore = collisionValue(currentCandidate);
78
+ const currentLeft = collisionLeft(currentCandidate);
79
+ const bestLeft = collisionLeft(bestCandidate);
78
80
  const crossDepth = Math.abs(currentLeft - bestLeft) > 20;
79
81
  const effectiveThreshold = crossDepth ? threshold * 0.25 : threshold;
80
82
  if (currentScore - bestScore < effectiveThreshold) {
@@ -113,11 +115,7 @@ var closestCenterCollision = ({
113
115
  }
114
116
  };
115
117
  }).filter((c) => c !== null);
116
- candidates.sort((a, b) => {
117
- const aValue = a.data.value;
118
- const bValue = b.data.value;
119
- return aValue - bValue;
120
- });
118
+ candidates.sort((a, b) => collisionValue(a) - collisionValue(b));
121
119
  return candidates.slice(0, 1);
122
120
  };
123
121
  var DEFAULT_ACTIVATION_DISTANCE = 8;
@@ -248,7 +246,12 @@ function DraggableBlock({
248
246
  disabled,
249
247
  focusedId,
250
248
  isSelected,
251
- onBlockClick
249
+ onBlockClick,
250
+ isContainer,
251
+ isExpanded,
252
+ depth,
253
+ posInSet,
254
+ setSize
252
255
  }) {
253
256
  const { attributes, listeners, setNodeRef, isDragging } = core.useDraggable({
254
257
  id: block.id,
@@ -267,11 +270,16 @@ function DraggableBlock({
267
270
  "data-selected": isSelected || void 0,
268
271
  style: { touchAction: "none", minWidth: 0, outline: "none" },
269
272
  role: "treeitem",
273
+ "aria-level": depth + 1,
274
+ "aria-posinset": posInSet,
275
+ "aria-setsize": setSize,
276
+ "aria-expanded": isContainer ? isExpanded : void 0,
277
+ "aria-selected": isSelected ?? void 0,
270
278
  children: children({ isDragging })
271
279
  }
272
280
  );
273
281
  }
274
- function TreeRenderer({
282
+ function TreeRendererInner({
275
283
  blocks,
276
284
  blocksByParent,
277
285
  parentId,
@@ -341,6 +349,11 @@ function TreeRenderer({
341
349
  focusedId,
342
350
  isSelected: selectedIds?.has(block.id),
343
351
  onBlockClick,
352
+ isContainer,
353
+ isExpanded,
354
+ depth,
355
+ posInSet: originalIndex + 1,
356
+ setSize: items.length,
344
357
  children: ({ isDragging }) => {
345
358
  if (isContainer) {
346
359
  const expandStyle = animation?.expandDuration ? {
@@ -425,6 +438,7 @@ function TreeRenderer({
425
438
  )
426
439
  ] });
427
440
  }
441
+ var TreeRenderer = react.memo(TreeRendererInner);
428
442
  function DragOverlay({
429
443
  activeBlock,
430
444
  children,
@@ -657,6 +671,9 @@ function reparentBlockIndex(state, activeId, targetZone, containerTypes = [], or
657
671
  if (parentDepth + subtreeDepth > maxDepth) return state;
658
672
  }
659
673
  if (dragged.id === zoneTargetId) return state;
674
+ if (newParentId !== null && getDescendantIds(state, dragged.id).has(newParentId)) {
675
+ return state;
676
+ }
660
677
  const oldList = byParent.get(oldParentId) ?? [];
661
678
  const currentIndexInOldParent = oldList.indexOf(dragged.id);
662
679
  const preNewList = byParent.get(newParentId) ?? [];
@@ -709,19 +726,24 @@ function reparentBlockIndex(state, activeId, targetZone, containerTypes = [], or
709
726
  function getBlockDepth(index, blockId) {
710
727
  let depth = 0;
711
728
  let current = blockId;
729
+ const visited = /* @__PURE__ */ new Set();
712
730
  while (current !== null) {
731
+ if (visited.has(current)) break;
732
+ visited.add(current);
713
733
  depth++;
714
734
  const block = index.byId.get(current);
715
735
  current = block?.parentId ?? null;
716
736
  }
717
737
  return depth;
718
738
  }
719
- function getSubtreeDepth(index, blockId) {
739
+ function getSubtreeDepth(index, blockId, visited = /* @__PURE__ */ new Set()) {
740
+ if (visited.has(blockId)) return 0;
741
+ visited.add(blockId);
720
742
  const children = index.byParent.get(blockId) ?? [];
721
743
  if (children.length === 0) return 1;
722
744
  let max = 0;
723
745
  for (const childId of children) {
724
- max = Math.max(max, getSubtreeDepth(index, childId));
746
+ max = Math.max(max, getSubtreeDepth(index, childId, visited));
725
747
  }
726
748
  return 1 + max;
727
749
  }
@@ -748,6 +770,40 @@ function reparentMultipleBlocks(state, blockIds, targetZone, containerTypes = []
748
770
  }
749
771
  return result;
750
772
  }
773
+ function validateBlockTree(index) {
774
+ const issues = [];
775
+ for (const [id] of index.byId) {
776
+ const visited = /* @__PURE__ */ new Set();
777
+ let current = id;
778
+ while (current !== null) {
779
+ if (visited.has(current)) {
780
+ issues.push(`Cycle detected: block "${id}" has a circular parentId chain`);
781
+ break;
782
+ }
783
+ visited.add(current);
784
+ const block = index.byId.get(current);
785
+ current = block?.parentId ?? null;
786
+ }
787
+ }
788
+ for (const [id, block] of index.byId) {
789
+ if (block.parentId !== null && !index.byId.has(block.parentId)) {
790
+ issues.push(`Orphan: block "${id}" references non-existent parent "${block.parentId}"`);
791
+ }
792
+ }
793
+ for (const [parentId, childIds] of index.byParent) {
794
+ for (const childId of childIds) {
795
+ if (!index.byId.has(childId)) {
796
+ issues.push(`Stale ref: byParent key "${parentId}" lists non-existent block "${childId}"`);
797
+ }
798
+ }
799
+ }
800
+ if (issues.length > 0) {
801
+ for (const issue of issues) {
802
+ console.warn(`[dnd-block-tree] ${issue}`);
803
+ }
804
+ }
805
+ return { valid: issues.length === 0, issues };
806
+ }
751
807
  function deleteBlockAndDescendants(state, id) {
752
808
  const byId = cloneMap(state.byId);
753
809
  const byParent = cloneParentMap(state.byParent);
@@ -770,7 +826,12 @@ function getBlockPosition(blocks, blockId) {
770
826
  }
771
827
  function computeInitialExpanded(blocks, containerTypes, initialExpanded) {
772
828
  if (initialExpanded === "none") {
773
- return {};
829
+ const expandedMap2 = {};
830
+ const containers2 = blocks.filter((b) => containerTypes.includes(b.type));
831
+ for (const container of containers2) {
832
+ expandedMap2[container.id] = false;
833
+ }
834
+ return expandedMap2;
774
835
  }
775
836
  const expandedMap = {};
776
837
  const containers = blocks.filter((b) => containerTypes.includes(b.type));
@@ -887,14 +948,17 @@ function BlockTree({
887
948
  onDragMove?.(event);
888
949
  }, 50)
889
950
  ).current;
890
- const originalIndex = computeNormalizedIndex(blocks, orderingStrategy);
891
- const blocksByParent = /* @__PURE__ */ new Map();
892
- for (const [parentId, ids] of originalIndex.byParent.entries()) {
893
- blocksByParent.set(
894
- parentId,
895
- ids.map((id) => originalIndex.byId.get(id)).filter(Boolean)
896
- );
897
- }
951
+ const originalIndex = react.useMemo(
952
+ () => computeNormalizedIndex(blocks, orderingStrategy),
953
+ [blocks, orderingStrategy]
954
+ );
955
+ const blocksByParent = react.useMemo(() => {
956
+ const map = /* @__PURE__ */ new Map();
957
+ for (const [parentId, ids] of originalIndex.byParent.entries()) {
958
+ map.set(parentId, ids.map((id) => originalIndex.byId.get(id)).filter(Boolean));
959
+ }
960
+ return map;
961
+ }, [originalIndex]);
898
962
  const focusedIdRef = react.useRef(null);
899
963
  const rootRef = react.useRef(null);
900
964
  const lastClickedIdRef = react.useRef(null);
@@ -1869,6 +1933,743 @@ function useVirtualTree({
1869
1933
  offsetY
1870
1934
  };
1871
1935
  }
1936
+ var MAX_EVENTS = 100;
1937
+ function useDevToolsCallbacks() {
1938
+ const [events, setEvents] = react.useState([]);
1939
+ const nextIdRef = react.useRef(1);
1940
+ const addEvent = react.useCallback((type, summary) => {
1941
+ const entry = {
1942
+ id: nextIdRef.current++,
1943
+ timestamp: Date.now(),
1944
+ type,
1945
+ summary
1946
+ };
1947
+ setEvents((prev) => [entry, ...prev].slice(0, MAX_EVENTS));
1948
+ }, []);
1949
+ const clearEvents = react.useCallback(() => {
1950
+ setEvents([]);
1951
+ }, []);
1952
+ const callbacks = react.useMemo(() => ({
1953
+ onDragStart: (event) => {
1954
+ addEvent("dragStart", `Started dragging "${event.blockId}"`);
1955
+ },
1956
+ onDragEnd: (event) => {
1957
+ if (event.cancelled) {
1958
+ addEvent("dragEnd", `Cancelled drag of "${event.blockId}"`);
1959
+ } else {
1960
+ addEvent("dragEnd", `Dropped "${event.blockId}" at ${event.targetZone ?? "none"}`);
1961
+ }
1962
+ },
1963
+ onBlockMove: (event) => {
1964
+ const fromStr = `parent=${event.from.parentId ?? "root"}[${event.from.index}]`;
1965
+ const toStr = `parent=${event.to.parentId ?? "root"}[${event.to.index}]`;
1966
+ const ids = event.movedIds.length > 1 ? ` (${event.movedIds.length} blocks)` : "";
1967
+ addEvent("blockMove", `Moved "${event.block.id}" from ${fromStr} to ${toStr}${ids}`);
1968
+ },
1969
+ onExpandChange: (event) => {
1970
+ addEvent("expandChange", `${event.expanded ? "Expanded" : "Collapsed"} "${event.blockId}"`);
1971
+ },
1972
+ onHoverChange: (event) => {
1973
+ if (event.zoneId) {
1974
+ addEvent("hoverChange", `Hovering over zone "${event.zoneId}"`);
1975
+ }
1976
+ }
1977
+ }), [addEvent]);
1978
+ return { callbacks, events, clearEvents };
1979
+ }
1980
+ function DevToolsLogo({ size = 20 }) {
1981
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1982
+ "svg",
1983
+ {
1984
+ width: size,
1985
+ height: size,
1986
+ viewBox: "0 0 24 24",
1987
+ fill: "none",
1988
+ xmlns: "http://www.w3.org/2000/svg",
1989
+ children: [
1990
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "2", y: "2", width: "8", height: "5", rx: "1", fill: "#3b82f6", opacity: "0.9" }),
1991
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "8", y: "10", width: "8", height: "5", rx: "1", fill: "#10b981", opacity: "0.9" }),
1992
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "14", y: "18", width: "8", height: "5", rx: "1", fill: "#f59e0b", opacity: "0.9" }),
1993
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 7 L6 10 L8 10", stroke: "#888", strokeWidth: "1.5", fill: "none", opacity: "0.6" }),
1994
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 15 L12 18 L14 18", stroke: "#888", strokeWidth: "1.5", fill: "none", opacity: "0.6" })
1995
+ ]
1996
+ }
1997
+ );
1998
+ }
1999
+ function computeDiffMap(prev, next) {
2000
+ const prevMap = new Map(prev.map((b) => [b.id, b]));
2001
+ const changeMap = /* @__PURE__ */ new Map();
2002
+ for (const block of next) {
2003
+ const prevBlock = prevMap.get(block.id);
2004
+ if (!prevBlock) {
2005
+ changeMap.set(block.id, "added");
2006
+ } else if (prevBlock.parentId !== block.parentId || prevBlock.order !== block.order) {
2007
+ changeMap.set(block.id, "moved");
2008
+ } else {
2009
+ changeMap.set(block.id, "unchanged");
2010
+ }
2011
+ }
2012
+ return changeMap;
2013
+ }
2014
+ function buildDiffTree(blocks, changeMap) {
2015
+ const result = [];
2016
+ const byParent = /* @__PURE__ */ new Map();
2017
+ for (const block of blocks) {
2018
+ const key = block.parentId ?? null;
2019
+ const list = byParent.get(key) ?? [];
2020
+ list.push(block);
2021
+ byParent.set(key, list);
2022
+ }
2023
+ function walk(parentId, depth) {
2024
+ const children = byParent.get(parentId) ?? [];
2025
+ children.sort((a, b) => {
2026
+ const ao = a.order, bo = b.order;
2027
+ if (typeof ao === "number" && typeof bo === "number") return ao - bo;
2028
+ return String(ao) < String(bo) ? -1 : String(ao) > String(bo) ? 1 : 0;
2029
+ });
2030
+ for (const block of children) {
2031
+ result.push({ block, changeType: changeMap.get(block.id) ?? "unchanged", depth });
2032
+ walk(block.id, depth + 1);
2033
+ }
2034
+ }
2035
+ walk(null, 0);
2036
+ return result;
2037
+ }
2038
+ var TYPE_COLORS = {
2039
+ dragStart: "#3b82f6",
2040
+ dragEnd: "#10b981",
2041
+ blockMove: "#f59e0b",
2042
+ expandChange: "#8b5cf6",
2043
+ hoverChange: "#6b7280"
2044
+ };
2045
+ var TYPE_LABELS = {
2046
+ dragStart: "DRAG",
2047
+ dragEnd: "DROP",
2048
+ blockMove: "MOVE",
2049
+ expandChange: "EXPAND",
2050
+ hoverChange: "HOVER"
2051
+ };
2052
+ var TYPE_TOOLTIPS = {
2053
+ dragStart: "onDragStart \u2014 a block was picked up",
2054
+ dragEnd: "onDragEnd \u2014 a block was dropped or drag was cancelled",
2055
+ blockMove: "onBlockMove \u2014 a block was reparented to a new position",
2056
+ expandChange: "onExpandChange \u2014 a container was expanded or collapsed",
2057
+ hoverChange: "onHoverChange \u2014 the pointer entered a drop zone"
2058
+ };
2059
+ var DEFAULT_WIDTH = 340;
2060
+ var DEFAULT_HEIGHT = 420;
2061
+ var DIFF_EXTRA_WIDTH = 300;
2062
+ var MIN_WIDTH = 280;
2063
+ var MIN_HEIGHT = 200;
2064
+ function getAnchorCSS(position) {
2065
+ switch (position) {
2066
+ case "bottom-right":
2067
+ return { bottom: 16, right: 16 };
2068
+ case "top-left":
2069
+ return { top: 16, left: 16 };
2070
+ case "top-right":
2071
+ return { top: 16, right: 16 };
2072
+ case "bottom-left":
2073
+ default:
2074
+ return { bottom: 16, left: 16 };
2075
+ }
2076
+ }
2077
+ function computeCardOrigin(position, width, height) {
2078
+ const vw = typeof window !== "undefined" ? window.innerWidth : 1024;
2079
+ const vh = typeof window !== "undefined" ? window.innerHeight : 768;
2080
+ switch (position) {
2081
+ case "bottom-right":
2082
+ return { x: Math.max(0, vw - 16 - width), y: Math.max(0, vh - 16 - height - 48) };
2083
+ case "top-left":
2084
+ return { x: 16, y: 16 + 48 };
2085
+ case "top-right":
2086
+ return { x: Math.max(0, vw - 16 - width), y: 16 + 48 };
2087
+ case "bottom-left":
2088
+ default:
2089
+ return { x: 16, y: Math.max(0, vh - 16 - height - 48) };
2090
+ }
2091
+ }
2092
+ function BlockTreeDevTools({
2093
+ blocks,
2094
+ containerTypes = [],
2095
+ events,
2096
+ onClearEvents,
2097
+ getLabel = (b) => b.type,
2098
+ initialOpen = false,
2099
+ position = "bottom-left",
2100
+ buttonStyle,
2101
+ panelStyle
2102
+ }) {
2103
+ const [isOpen, setIsOpen] = react.useState(initialOpen);
2104
+ const [showDiff, setShowDiff] = react.useState(false);
2105
+ const [cardPos, setCardPos] = react.useState(null);
2106
+ const [cardSize, setCardSize] = react.useState({ w: DEFAULT_WIDTH, h: DEFAULT_HEIGHT });
2107
+ const [showTooltip, setShowTooltip] = react.useState(false);
2108
+ const dragRef = react.useRef({
2109
+ dragging: false,
2110
+ startX: 0,
2111
+ startY: 0,
2112
+ origX: 0,
2113
+ origY: 0
2114
+ });
2115
+ const resizeRef = react.useRef({ active: false, edge: "", startX: 0, startY: 0, origX: 0, origY: 0, origW: 0, origH: 0 });
2116
+ const cardRef = react.useRef(null);
2117
+ const prevBlocksRef = react.useRef(blocks);
2118
+ const diffChangeMap = react.useMemo(() => computeDiffMap(prevBlocksRef.current, blocks), [blocks]);
2119
+ react.useEffect(() => {
2120
+ prevBlocksRef.current = blocks;
2121
+ }, [blocks]);
2122
+ const diffTree = react.useMemo(() => buildDiffTree(blocks, diffChangeMap), [blocks, diffChangeMap]);
2123
+ const diffStats = react.useMemo(() => {
2124
+ let added = 0, moved = 0;
2125
+ for (const { changeType } of diffTree) {
2126
+ if (changeType === "added") added++;
2127
+ if (changeType === "moved") moved++;
2128
+ }
2129
+ return { added, moved };
2130
+ }, [diffTree]);
2131
+ react.useEffect(() => {
2132
+ setCardSize((prev) => {
2133
+ const targetW = showDiff ? DEFAULT_WIDTH + DIFF_EXTRA_WIDTH : DEFAULT_WIDTH;
2134
+ const wasDefault = Math.abs(prev.w - DEFAULT_WIDTH) < 20;
2135
+ const wasExpanded = Math.abs(prev.w - (DEFAULT_WIDTH + DIFF_EXTRA_WIDTH)) < 20;
2136
+ if (showDiff && (wasDefault || prev.w < targetW)) {
2137
+ return { ...prev, w: targetW };
2138
+ }
2139
+ if (!showDiff && wasExpanded) {
2140
+ return { ...prev, w: DEFAULT_WIDTH };
2141
+ }
2142
+ return prev;
2143
+ });
2144
+ }, [showDiff]);
2145
+ const getDefaultCardPos = react.useCallback(() => {
2146
+ return computeCardOrigin(position, cardSize.w, cardSize.h);
2147
+ }, [position, cardSize.w, cardSize.h]);
2148
+ react.useEffect(() => {
2149
+ if (isOpen && !cardPos) {
2150
+ setCardPos(getDefaultCardPos());
2151
+ }
2152
+ }, [isOpen, cardPos, getDefaultCardPos]);
2153
+ const handleDragPointerDown = react.useCallback((e) => {
2154
+ e.preventDefault();
2155
+ dragRef.current = {
2156
+ dragging: true,
2157
+ startX: e.clientX,
2158
+ startY: e.clientY,
2159
+ origX: cardPos?.x ?? 0,
2160
+ origY: cardPos?.y ?? 0
2161
+ };
2162
+ e.target.setPointerCapture(e.pointerId);
2163
+ }, [cardPos]);
2164
+ const handleDragPointerMove = react.useCallback((e) => {
2165
+ if (!dragRef.current.dragging) return;
2166
+ const dx = e.clientX - dragRef.current.startX;
2167
+ const dy = e.clientY - dragRef.current.startY;
2168
+ const newX = dragRef.current.origX + dx;
2169
+ const newY = dragRef.current.origY + dy;
2170
+ const maxX = window.innerWidth - cardSize.w;
2171
+ const maxY = window.innerHeight - 40;
2172
+ setCardPos({
2173
+ x: Math.max(0, Math.min(newX, maxX)),
2174
+ y: Math.max(0, Math.min(newY, maxY))
2175
+ });
2176
+ }, [cardSize.w]);
2177
+ const handleDragPointerUp = react.useCallback(() => {
2178
+ dragRef.current.dragging = false;
2179
+ }, []);
2180
+ const handleResizePointerDown = react.useCallback((edge) => (e) => {
2181
+ e.preventDefault();
2182
+ e.stopPropagation();
2183
+ resizeRef.current = {
2184
+ active: true,
2185
+ edge,
2186
+ startX: e.clientX,
2187
+ startY: e.clientY,
2188
+ origX: cardPos?.x ?? 0,
2189
+ origY: cardPos?.y ?? 0,
2190
+ origW: cardSize.w,
2191
+ origH: cardSize.h
2192
+ };
2193
+ e.target.setPointerCapture(e.pointerId);
2194
+ }, [cardPos, cardSize]);
2195
+ const handleResizePointerMove = react.useCallback((e) => {
2196
+ const r = resizeRef.current;
2197
+ if (!r.active) return;
2198
+ const dx = e.clientX - r.startX;
2199
+ const dy = e.clientY - r.startY;
2200
+ let newW = r.origW;
2201
+ let newH = r.origH;
2202
+ let newX = r.origX;
2203
+ let newY = r.origY;
2204
+ if (r.edge.includes("e")) newW = Math.max(MIN_WIDTH, r.origW + dx);
2205
+ if (r.edge.includes("w")) {
2206
+ newW = Math.max(MIN_WIDTH, r.origW - dx);
2207
+ newX = r.origX + (r.origW - newW);
2208
+ }
2209
+ if (r.edge.includes("s")) newH = Math.max(MIN_HEIGHT, r.origH + dy);
2210
+ if (r.edge.includes("n")) {
2211
+ newH = Math.max(MIN_HEIGHT, r.origH - dy);
2212
+ newY = r.origY + (r.origH - newH);
2213
+ }
2214
+ setCardSize({ w: newW, h: newH });
2215
+ setCardPos({ x: newX, y: newY });
2216
+ }, []);
2217
+ const handleResizePointerUp = react.useCallback(() => {
2218
+ resizeRef.current.active = false;
2219
+ }, []);
2220
+ const toggle = react.useCallback(() => {
2221
+ setIsOpen((prev) => !prev);
2222
+ }, []);
2223
+ const toggleDiff = react.useCallback(() => {
2224
+ setShowDiff((prev) => !prev);
2225
+ }, []);
2226
+ const renderCountRef = react.useRef(0);
2227
+ const lastRenderTimeRef = react.useRef(performance.now());
2228
+ const prevBlockCountRef = react.useRef(blocks.length);
2229
+ renderCountRef.current++;
2230
+ const renderTime = performance.now() - lastRenderTimeRef.current;
2231
+ lastRenderTimeRef.current = performance.now();
2232
+ const blockCountDelta = blocks.length - prevBlockCountRef.current;
2233
+ react.useEffect(() => {
2234
+ prevBlockCountRef.current = blocks.length;
2235
+ }, [blocks.length]);
2236
+ const treeStats = react.useMemo(() => {
2237
+ const index = computeNormalizedIndex(blocks);
2238
+ const containers = blocks.filter((b) => containerTypes.includes(b.type));
2239
+ let maxDepthVal = 0;
2240
+ for (const block of blocks) {
2241
+ const d = getBlockDepth(index, block.id);
2242
+ if (d > maxDepthVal) maxDepthVal = d;
2243
+ }
2244
+ const validation = validateBlockTree(index);
2245
+ return {
2246
+ blockCount: blocks.length,
2247
+ containerCount: containers.length,
2248
+ maxDepth: maxDepthVal,
2249
+ validation
2250
+ };
2251
+ }, [blocks, containerTypes]);
2252
+ const formatTime = (ts) => {
2253
+ const d = new Date(ts);
2254
+ return `${d.getHours().toString().padStart(2, "0")}:${d.getMinutes().toString().padStart(2, "0")}:${d.getSeconds().toString().padStart(2, "0")}`;
2255
+ };
2256
+ const anchor = getAnchorCSS(position);
2257
+ const triggerBtnStyle = {
2258
+ position: "fixed",
2259
+ ...anchor,
2260
+ zIndex: 99998,
2261
+ width: 40,
2262
+ height: 40,
2263
+ borderRadius: "50%",
2264
+ border: "none",
2265
+ background: isOpen ? "rgba(59, 130, 246, 0.9)" : "rgba(30, 30, 30, 0.85)",
2266
+ color: "#fff",
2267
+ cursor: "pointer",
2268
+ display: "flex",
2269
+ alignItems: "center",
2270
+ justifyContent: "center",
2271
+ boxShadow: "0 2px 8px rgba(0,0,0,0.3)",
2272
+ transition: "background 0.15s, transform 0.15s",
2273
+ ...buttonStyle
2274
+ };
2275
+ const tooltipStyle = {
2276
+ position: "absolute",
2277
+ whiteSpace: "nowrap",
2278
+ fontSize: 11,
2279
+ fontWeight: 500,
2280
+ padding: "4px 10px",
2281
+ borderRadius: 6,
2282
+ background: "rgba(15, 15, 20, 0.95)",
2283
+ color: "#ccc",
2284
+ boxShadow: "0 2px 8px rgba(0,0,0,0.3)",
2285
+ pointerEvents: "none",
2286
+ // Position based on anchor corner
2287
+ ...anchor.bottom !== void 0 ? { bottom: "100%", marginBottom: 8 } : { top: "100%", marginTop: 8 },
2288
+ ...anchor.left !== void 0 ? { left: 0 } : { right: 0 }
2289
+ };
2290
+ const cardStyle = {
2291
+ position: "fixed",
2292
+ left: cardPos?.x ?? 0,
2293
+ top: cardPos?.y ?? 0,
2294
+ zIndex: 99999,
2295
+ width: cardSize.w,
2296
+ height: cardSize.h,
2297
+ background: "rgba(20, 20, 24, 0.95)",
2298
+ backdropFilter: "blur(12px)",
2299
+ border: "1px solid rgba(255,255,255,0.1)",
2300
+ borderRadius: 10,
2301
+ color: "#e0e0e0",
2302
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
2303
+ fontSize: 12,
2304
+ display: "flex",
2305
+ flexDirection: "column",
2306
+ boxShadow: "0 8px 32px rgba(0,0,0,0.5)",
2307
+ overflow: "hidden",
2308
+ ...panelStyle
2309
+ };
2310
+ const titleBarStyle = {
2311
+ display: "flex",
2312
+ alignItems: "center",
2313
+ justifyContent: "space-between",
2314
+ padding: "8px 12px",
2315
+ gap: 8,
2316
+ background: "rgba(255,255,255,0.04)",
2317
+ borderBottom: "1px solid rgba(255,255,255,0.08)",
2318
+ cursor: "grab",
2319
+ userSelect: "none",
2320
+ flexShrink: 0,
2321
+ touchAction: "none"
2322
+ };
2323
+ const titleTextStyle = {
2324
+ fontSize: 11,
2325
+ fontWeight: 600,
2326
+ letterSpacing: "0.03em",
2327
+ opacity: 0.8,
2328
+ flexShrink: 0
2329
+ };
2330
+ const titleBtnStyle = (active) => ({
2331
+ height: 22,
2332
+ padding: "0 8px",
2333
+ borderRadius: 4,
2334
+ border: "1px solid " + (active ? "rgba(59,130,246,0.5)" : "rgba(128,128,128,0.25)"),
2335
+ background: active ? "rgba(59,130,246,0.15)" : "transparent",
2336
+ color: active ? "#93b8f7" : "#999",
2337
+ cursor: "pointer",
2338
+ fontSize: 10,
2339
+ fontWeight: 600,
2340
+ letterSpacing: "0.02em",
2341
+ flexShrink: 0
2342
+ });
2343
+ const closeBtnStyle = {
2344
+ width: 20,
2345
+ height: 20,
2346
+ borderRadius: 4,
2347
+ border: "none",
2348
+ background: "transparent",
2349
+ color: "#999",
2350
+ cursor: "pointer",
2351
+ display: "flex",
2352
+ alignItems: "center",
2353
+ justifyContent: "center",
2354
+ fontSize: 14,
2355
+ lineHeight: "1",
2356
+ padding: 0,
2357
+ flexShrink: 0
2358
+ };
2359
+ const bodyStyle = {
2360
+ flex: 1,
2361
+ display: "flex",
2362
+ flexDirection: "row",
2363
+ overflow: "hidden",
2364
+ minHeight: 0
2365
+ };
2366
+ const mainColumnStyle = {
2367
+ flex: showDiff ? "0 0 50%" : "1 1 100%",
2368
+ overflow: "auto",
2369
+ padding: "10px 12px",
2370
+ minWidth: 0
2371
+ };
2372
+ const diffColumnStyle = {
2373
+ flex: "0 0 50%",
2374
+ overflow: "auto",
2375
+ padding: "10px 12px",
2376
+ borderLeft: "1px solid rgba(255,255,255,0.08)",
2377
+ minWidth: 0
2378
+ };
2379
+ const sectionStyle = {
2380
+ marginBottom: 8
2381
+ };
2382
+ const headingStyle = {
2383
+ fontSize: 11,
2384
+ fontWeight: 600,
2385
+ textTransform: "uppercase",
2386
+ letterSpacing: "0.05em",
2387
+ opacity: 0.6,
2388
+ marginBottom: 6
2389
+ };
2390
+ const statRowStyle = {
2391
+ display: "flex",
2392
+ justifyContent: "space-between",
2393
+ alignItems: "center",
2394
+ fontSize: 12,
2395
+ padding: "2px 0"
2396
+ };
2397
+ const statValueStyle = {
2398
+ fontFamily: "monospace",
2399
+ fontSize: 11,
2400
+ opacity: 0.8
2401
+ };
2402
+ const badgeStyle = (color) => ({
2403
+ display: "inline-block",
2404
+ fontSize: 9,
2405
+ fontWeight: 700,
2406
+ fontFamily: "monospace",
2407
+ padding: "1px 5px",
2408
+ borderRadius: 3,
2409
+ backgroundColor: color + "22",
2410
+ color,
2411
+ marginRight: 6,
2412
+ flexShrink: 0
2413
+ });
2414
+ const eventListStyle = {
2415
+ maxHeight: 160,
2416
+ overflowY: "auto",
2417
+ fontSize: 11,
2418
+ lineHeight: "1.6"
2419
+ };
2420
+ const eventItemStyle = {
2421
+ display: "flex",
2422
+ alignItems: "flex-start",
2423
+ gap: 4,
2424
+ padding: "2px 0",
2425
+ borderBottom: "1px solid rgba(128,128,128,0.1)"
2426
+ };
2427
+ const timeStyle = {
2428
+ fontFamily: "monospace",
2429
+ fontSize: 10,
2430
+ opacity: 0.4,
2431
+ flexShrink: 0,
2432
+ minWidth: 52
2433
+ };
2434
+ const clearBtnStyle = {
2435
+ fontSize: 10,
2436
+ padding: "2px 8px",
2437
+ border: "1px solid rgba(128,128,128,0.3)",
2438
+ borderRadius: 4,
2439
+ background: "transparent",
2440
+ color: "#ccc",
2441
+ cursor: "pointer",
2442
+ opacity: 0.7
2443
+ };
2444
+ const EDGE_SIZE = 6;
2445
+ const resizeEdge = (edge) => {
2446
+ const base = {
2447
+ position: "absolute",
2448
+ zIndex: 1
2449
+ };
2450
+ const cursors = {
2451
+ n: "ns-resize",
2452
+ s: "ns-resize",
2453
+ e: "ew-resize",
2454
+ w: "ew-resize",
2455
+ ne: "nesw-resize",
2456
+ nw: "nwse-resize",
2457
+ se: "nwse-resize",
2458
+ sw: "nesw-resize"
2459
+ };
2460
+ const styles = {
2461
+ n: { top: 0, left: EDGE_SIZE, right: EDGE_SIZE, height: EDGE_SIZE },
2462
+ s: { bottom: 0, left: EDGE_SIZE, right: EDGE_SIZE, height: EDGE_SIZE },
2463
+ e: { right: 0, top: EDGE_SIZE, bottom: EDGE_SIZE, width: EDGE_SIZE },
2464
+ w: { left: 0, top: EDGE_SIZE, bottom: EDGE_SIZE, width: EDGE_SIZE },
2465
+ ne: { top: 0, right: 0, width: EDGE_SIZE * 2, height: EDGE_SIZE * 2 },
2466
+ nw: { top: 0, left: 0, width: EDGE_SIZE * 2, height: EDGE_SIZE * 2 },
2467
+ se: { bottom: 0, right: 0, width: EDGE_SIZE * 2, height: EDGE_SIZE * 2 },
2468
+ sw: { bottom: 0, left: 0, width: EDGE_SIZE * 2, height: EDGE_SIZE * 2 }
2469
+ };
2470
+ return { ...base, cursor: cursors[edge], ...styles[edge] };
2471
+ };
2472
+ const EDGES = ["n", "s", "e", "w", "ne", "nw", "se", "sw"];
2473
+ const diffRowColor = (ct) => {
2474
+ if (ct === "added") return { background: "rgba(16,185,129,0.1)", color: "#34d399" };
2475
+ if (ct === "moved") return { background: "rgba(245,158,11,0.1)", color: "#fbbf24" };
2476
+ return { color: "rgba(200,200,200,0.5)" };
2477
+ };
2478
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2479
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "fixed", ...anchor, zIndex: 99998 }, children: [
2480
+ /* @__PURE__ */ jsxRuntime.jsx(
2481
+ "button",
2482
+ {
2483
+ onClick: toggle,
2484
+ onMouseEnter: () => setShowTooltip(true),
2485
+ onMouseLeave: () => setShowTooltip(false),
2486
+ style: triggerBtnStyle,
2487
+ "aria-label": isOpen ? "Close DevTools" : "Open DevTools",
2488
+ children: /* @__PURE__ */ jsxRuntime.jsx(DevToolsLogo, { size: 20 })
2489
+ }
2490
+ ),
2491
+ showTooltip && !isOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { style: tooltipStyle, children: "dnd-block-tree DevTools" })
2492
+ ] }),
2493
+ isOpen && cardPos && /* @__PURE__ */ jsxRuntime.jsxs(
2494
+ "div",
2495
+ {
2496
+ ref: cardRef,
2497
+ style: cardStyle,
2498
+ "data-devtools-root": "",
2499
+ children: [
2500
+ EDGES.map((edge) => /* @__PURE__ */ jsxRuntime.jsx(
2501
+ "div",
2502
+ {
2503
+ style: resizeEdge(edge),
2504
+ onPointerDown: handleResizePointerDown(edge),
2505
+ onPointerMove: handleResizePointerMove,
2506
+ onPointerUp: handleResizePointerUp
2507
+ },
2508
+ edge
2509
+ )),
2510
+ /* @__PURE__ */ jsxRuntime.jsxs(
2511
+ "div",
2512
+ {
2513
+ style: titleBarStyle,
2514
+ onPointerDown: handleDragPointerDown,
2515
+ onPointerMove: handleDragPointerMove,
2516
+ onPointerUp: handleDragPointerUp,
2517
+ children: [
2518
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: titleTextStyle, children: "DevTools" }),
2519
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1 } }),
2520
+ /* @__PURE__ */ jsxRuntime.jsx(
2521
+ "button",
2522
+ {
2523
+ onClick: toggleDiff,
2524
+ style: titleBtnStyle(showDiff),
2525
+ onPointerDown: (e) => e.stopPropagation(),
2526
+ title: "Toggle structure diff \u2014 shows which blocks were added, moved, or unchanged",
2527
+ children: "Diff"
2528
+ }
2529
+ ),
2530
+ /* @__PURE__ */ jsxRuntime.jsx(
2531
+ "button",
2532
+ {
2533
+ onClick: toggle,
2534
+ style: closeBtnStyle,
2535
+ onPointerDown: (e) => e.stopPropagation(),
2536
+ "aria-label": "Close DevTools",
2537
+ children: "\xD7"
2538
+ }
2539
+ )
2540
+ ]
2541
+ }
2542
+ ),
2543
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: bodyStyle, children: [
2544
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: mainColumnStyle, children: [
2545
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, "data-devtools-section": "tree-state", children: [
2546
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: headingStyle, title: "Live snapshot of the block tree structure", children: "Tree State" }),
2547
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: statRowStyle, title: "Total number of blocks in the flat array", children: [
2548
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Blocks" }),
2549
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: statValueStyle, children: treeStats.blockCount })
2550
+ ] }),
2551
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: statRowStyle, title: "Blocks whose type is in containerTypes (can have children)", children: [
2552
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Containers" }),
2553
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: statValueStyle, children: treeStats.containerCount })
2554
+ ] }),
2555
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: statRowStyle, title: "Deepest nesting level in the tree (root = 1)", children: [
2556
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Max Depth" }),
2557
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: statValueStyle, children: treeStats.maxDepth })
2558
+ ] }),
2559
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: statRowStyle, title: "Checks for cycles, orphans, and stale parent refs", children: [
2560
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Validation" }),
2561
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
2562
+ ...statValueStyle,
2563
+ color: treeStats.validation.valid ? "#10b981" : "#ef4444"
2564
+ }, children: treeStats.validation.valid ? "Valid" : `${treeStats.validation.issues.length} issue(s)` })
2565
+ ] }),
2566
+ !treeStats.validation.valid && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 10, color: "#ef4444", marginTop: 4 }, children: treeStats.validation.issues.map((issue, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { children: issue }, i)) })
2567
+ ] }),
2568
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, "data-devtools-section": "event-log", children: [
2569
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }, children: [
2570
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: headingStyle, title: "Chronological log of drag, drop, move, expand, and hover events", children: [
2571
+ "Event Log (",
2572
+ events.length,
2573
+ ")"
2574
+ ] }),
2575
+ events.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onClearEvents, style: clearBtnStyle, children: "Clear" })
2576
+ ] }),
2577
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: eventListStyle, children: events.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, opacity: 0.4, padding: "8px 0" }, children: "No events yet. Drag some blocks!" }) : events.map((event) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: eventItemStyle, children: [
2578
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: timeStyle, children: formatTime(event.timestamp) }),
2579
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: badgeStyle(TYPE_COLORS[event.type]), title: TYPE_TOOLTIPS[event.type], children: TYPE_LABELS[event.type] }),
2580
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { wordBreak: "break-word" }, children: event.summary })
2581
+ ] }, event.id)) })
2582
+ ] }),
2583
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, "data-devtools-section": "performance", children: [
2584
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: headingStyle, title: "Render metrics for the DevTools component itself", children: "Performance" }),
2585
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: statRowStyle, title: "How many times this DevTools component has rendered", children: [
2586
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Render Count" }),
2587
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: statValueStyle, children: renderCountRef.current })
2588
+ ] }),
2589
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: statRowStyle, title: "Time since the previous render of this component", children: [
2590
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Last Render" }),
2591
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: statValueStyle, children: [
2592
+ renderTime.toFixed(1),
2593
+ "ms"
2594
+ ] })
2595
+ ] }),
2596
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: statRowStyle, title: "Change in block count since the last render (+added / -removed)", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { style: {
2597
+ ...statValueStyle,
2598
+ color: blockCountDelta > 0 ? "#10b981" : blockCountDelta < 0 ? "#ef4444" : void 0
2599
+ }, children: [
2600
+ blockCountDelta > 0 ? "+" : "",
2601
+ blockCountDelta
2602
+ ] }) })
2603
+ ] })
2604
+ ] }),
2605
+ showDiff && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: diffColumnStyle, children: [
2606
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }, children: [
2607
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: headingStyle, title: "Tree structure diff \u2014 compares current state to the previous render", children: "Structure" }),
2608
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 10, fontSize: 11, fontWeight: 500 }, children: [
2609
+ diffStats.added > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color: "#34d399", display: "flex", alignItems: "center", gap: 4 }, children: [
2610
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { width: 5, height: 5, borderRadius: "50%", background: "#34d399", display: "inline-block" } }),
2611
+ diffStats.added,
2612
+ " new"
2613
+ ] }),
2614
+ diffStats.moved > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color: "#fbbf24", display: "flex", alignItems: "center", gap: 4 }, children: [
2615
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { width: 5, height: 5, borderRadius: "50%", background: "#fbbf24", display: "inline-block" } }),
2616
+ diffStats.moved,
2617
+ " moved"
2618
+ ] }),
2619
+ diffStats.added === 0 && diffStats.moved === 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.4 }, children: "No changes" })
2620
+ ] })
2621
+ ] }),
2622
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontFamily: "monospace", fontSize: 11, lineHeight: "1.7" }, children: diffTree.map(({ block, changeType, depth }) => /* @__PURE__ */ jsxRuntime.jsxs(
2623
+ "div",
2624
+ {
2625
+ style: {
2626
+ paddingLeft: depth * 14,
2627
+ padding: "2px 6px 2px " + (depth * 14 + 6) + "px",
2628
+ borderRadius: 4,
2629
+ display: "flex",
2630
+ alignItems: "center",
2631
+ gap: 6,
2632
+ ...diffRowColor(changeType)
2633
+ },
2634
+ children: [
2635
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { width: 12, textAlign: "center", fontWeight: 700, fontSize: 10 }, children: [
2636
+ changeType === "added" && "+",
2637
+ changeType === "moved" && "~"
2638
+ ] }),
2639
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
2640
+ textTransform: "uppercase",
2641
+ fontSize: 9,
2642
+ letterSpacing: "0.05em",
2643
+ width: 50,
2644
+ flexShrink: 0,
2645
+ opacity: changeType === "unchanged" ? 0.5 : 1
2646
+ }, children: block.type }),
2647
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
2648
+ overflow: "hidden",
2649
+ textOverflow: "ellipsis",
2650
+ whiteSpace: "nowrap",
2651
+ flex: 1,
2652
+ minWidth: 0
2653
+ }, children: getLabel(block) }),
2654
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
2655
+ fontFamily: "monospace",
2656
+ fontSize: 10,
2657
+ flexShrink: 0,
2658
+ padding: "1px 5px",
2659
+ borderRadius: 3,
2660
+ ...changeType === "added" ? { background: "rgba(16,185,129,0.2)", color: "#34d399" } : changeType === "moved" ? { background: "rgba(245,158,11,0.2)", color: "#fbbf24" } : { background: "rgba(128,128,128,0.15)", color: "rgba(200,200,200,0.4)" }
2661
+ }, children: String(block.order) })
2662
+ ]
2663
+ },
2664
+ block.id
2665
+ )) })
2666
+ ] })
2667
+ ] })
2668
+ ]
2669
+ }
2670
+ )
2671
+ ] });
2672
+ }
1872
2673
 
1873
2674
  // src/utils/serialization.ts
1874
2675
  function flatToNested(blocks) {
@@ -1920,6 +2721,7 @@ function nestedToFlat(nested) {
1920
2721
  }
1921
2722
 
1922
2723
  exports.BlockTree = BlockTree;
2724
+ exports.BlockTreeDevTools = BlockTreeDevTools;
1923
2725
  exports.BlockTreeSSR = BlockTreeSSR;
1924
2726
  exports.DragOverlay = DragOverlay;
1925
2727
  exports.DropZone = DropZone;
@@ -1954,8 +2756,10 @@ exports.reparentMultipleBlocks = reparentMultipleBlocks;
1954
2756
  exports.triggerHaptic = triggerHaptic;
1955
2757
  exports.useBlockHistory = useBlockHistory;
1956
2758
  exports.useConfiguredSensors = useConfiguredSensors;
2759
+ exports.useDevToolsCallbacks = useDevToolsCallbacks;
1957
2760
  exports.useLayoutAnimation = useLayoutAnimation;
1958
2761
  exports.useVirtualTree = useVirtualTree;
2762
+ exports.validateBlockTree = validateBlockTree;
1959
2763
  exports.weightedVerticalCollision = weightedVerticalCollision;
1960
2764
  //# sourceMappingURL=index.js.map
1961
2765
  //# sourceMappingURL=index.js.map