dnd-block-tree 1.1.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
@@ -1933,6 +1933,743 @@ function useVirtualTree({
1933
1933
  offsetY
1934
1934
  };
1935
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
+ }
1936
2673
 
1937
2674
  // src/utils/serialization.ts
1938
2675
  function flatToNested(blocks) {
@@ -1984,6 +2721,7 @@ function nestedToFlat(nested) {
1984
2721
  }
1985
2722
 
1986
2723
  exports.BlockTree = BlockTree;
2724
+ exports.BlockTreeDevTools = BlockTreeDevTools;
1987
2725
  exports.BlockTreeSSR = BlockTreeSSR;
1988
2726
  exports.DragOverlay = DragOverlay;
1989
2727
  exports.DropZone = DropZone;
@@ -2018,6 +2756,7 @@ exports.reparentMultipleBlocks = reparentMultipleBlocks;
2018
2756
  exports.triggerHaptic = triggerHaptic;
2019
2757
  exports.useBlockHistory = useBlockHistory;
2020
2758
  exports.useConfiguredSensors = useConfiguredSensors;
2759
+ exports.useDevToolsCallbacks = useDevToolsCallbacks;
2021
2760
  exports.useLayoutAnimation = useLayoutAnimation;
2022
2761
  exports.useVirtualTree = useVirtualTree;
2023
2762
  exports.validateBlockTree = validateBlockTree;