dnd-block-tree 1.2.0 → 1.3.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.d.mts CHANGED
@@ -793,13 +793,15 @@ interface BlockTreeDevToolsProps<T extends BaseBlock = BaseBlock> {
793
793
  buttonStyle?: React.CSSProperties;
794
794
  /** Custom inline styles for the card panel */
795
795
  panelStyle?: React.CSSProperties;
796
+ /** Force mount in production (default: false). DevTools renders nothing in production unless this is true. */
797
+ forceMount?: boolean;
796
798
  }
797
799
  declare function useDevToolsCallbacks<T extends BaseBlock = BaseBlock>(): {
798
800
  callbacks: DevToolsCallbacks<T>;
799
801
  events: DevToolsEventEntry[];
800
802
  clearEvents: () => void;
801
803
  };
802
- declare function BlockTreeDevTools<T extends BaseBlock = BaseBlock>({ blocks, containerTypes, events, onClearEvents, getLabel, initialOpen, position, buttonStyle, panelStyle, }: BlockTreeDevToolsProps<T>): react_jsx_runtime.JSX.Element;
804
+ declare function BlockTreeDevTools<T extends BaseBlock = BaseBlock>({ blocks, containerTypes, events, onClearEvents, getLabel, initialOpen, position, buttonStyle, panelStyle, forceMount, }: BlockTreeDevToolsProps<T>): react_jsx_runtime.JSX.Element | null;
803
805
 
804
806
  /**
805
807
  * A nested/tree representation of a block.
package/dist/index.d.ts CHANGED
@@ -793,13 +793,15 @@ interface BlockTreeDevToolsProps<T extends BaseBlock = BaseBlock> {
793
793
  buttonStyle?: React.CSSProperties;
794
794
  /** Custom inline styles for the card panel */
795
795
  panelStyle?: React.CSSProperties;
796
+ /** Force mount in production (default: false). DevTools renders nothing in production unless this is true. */
797
+ forceMount?: boolean;
796
798
  }
797
799
  declare function useDevToolsCallbacks<T extends BaseBlock = BaseBlock>(): {
798
800
  callbacks: DevToolsCallbacks<T>;
799
801
  events: DevToolsEventEntry[];
800
802
  clearEvents: () => void;
801
803
  };
802
- declare function BlockTreeDevTools<T extends BaseBlock = BaseBlock>({ blocks, containerTypes, events, onClearEvents, getLabel, initialOpen, position, buttonStyle, panelStyle, }: BlockTreeDevToolsProps<T>): react_jsx_runtime.JSX.Element;
804
+ declare function BlockTreeDevTools<T extends BaseBlock = BaseBlock>({ blocks, containerTypes, events, onClearEvents, getLabel, initialOpen, position, buttonStyle, panelStyle, forceMount, }: BlockTreeDevToolsProps<T>): react_jsx_runtime.JSX.Element | null;
803
805
 
804
806
  /**
805
807
  * A nested/tree representation of a block.
package/dist/index.js CHANGED
@@ -2061,33 +2061,78 @@ var DEFAULT_HEIGHT = 420;
2061
2061
  var DIFF_EXTRA_WIDTH = 300;
2062
2062
  var MIN_WIDTH = 280;
2063
2063
  var MIN_HEIGHT = 200;
2064
- function getAnchorCSS(position) {
2065
- switch (position) {
2066
- case "bottom-right":
2067
- return { bottom: 16, right: 16 };
2064
+ var BTN_SIZE = 40;
2065
+ var BTN_MARGIN = 16;
2066
+ var STORAGE_KEY = "dnd-devtools-position";
2067
+ var BTN_DRAG_THRESHOLD = 5;
2068
+ function cornerToXY(corner) {
2069
+ const vw = typeof window !== "undefined" ? window.innerWidth : 1024;
2070
+ const vh = typeof window !== "undefined" ? window.innerHeight : 768;
2071
+ switch (corner) {
2068
2072
  case "top-left":
2069
- return { top: 16, left: 16 };
2073
+ return { x: BTN_MARGIN, y: BTN_MARGIN };
2070
2074
  case "top-right":
2071
- return { top: 16, right: 16 };
2075
+ return { x: vw - BTN_MARGIN - BTN_SIZE, y: BTN_MARGIN };
2076
+ case "bottom-right":
2077
+ return { x: vw - BTN_MARGIN - BTN_SIZE, y: vh - BTN_MARGIN - BTN_SIZE };
2072
2078
  case "bottom-left":
2073
2079
  default:
2074
- return { bottom: 16, left: 16 };
2080
+ return { x: BTN_MARGIN, y: vh - BTN_MARGIN - BTN_SIZE };
2081
+ }
2082
+ }
2083
+ function xyToCorner(x, y) {
2084
+ const vw = typeof window !== "undefined" ? window.innerWidth : 1024;
2085
+ const vh = typeof window !== "undefined" ? window.innerHeight : 768;
2086
+ const isLeft = x + BTN_SIZE / 2 < vw / 2;
2087
+ const isTop = y + BTN_SIZE / 2 < vh / 2;
2088
+ if (isTop && isLeft) return "top-left";
2089
+ if (isTop) return "top-right";
2090
+ if (isLeft) return "bottom-left";
2091
+ return "bottom-right";
2092
+ }
2093
+ function loadStoredPosition() {
2094
+ if (typeof window === "undefined") return null;
2095
+ try {
2096
+ const v = localStorage.getItem(STORAGE_KEY);
2097
+ if (v === "bottom-left" || v === "bottom-right" || v === "top-left" || v === "top-right") return v;
2098
+ } catch {
2099
+ }
2100
+ return null;
2101
+ }
2102
+ function savePosition(corner) {
2103
+ try {
2104
+ localStorage.setItem(STORAGE_KEY, corner);
2105
+ } catch {
2075
2106
  }
2076
2107
  }
2077
- function computeCardOrigin(position, width, height) {
2108
+ function computeCardOrigin(corner, width, height) {
2078
2109
  const vw = typeof window !== "undefined" ? window.innerWidth : 1024;
2079
2110
  const vh = typeof window !== "undefined" ? window.innerHeight : 768;
2080
- switch (position) {
2111
+ let x;
2112
+ let y;
2113
+ switch (corner) {
2081
2114
  case "bottom-right":
2082
- return { x: Math.max(0, vw - 16 - width), y: Math.max(0, vh - 16 - height - 48) };
2115
+ x = vw - BTN_MARGIN - width;
2116
+ y = vh - BTN_MARGIN - height - BTN_SIZE - 8;
2117
+ break;
2083
2118
  case "top-left":
2084
- return { x: 16, y: 16 + 48 };
2119
+ x = BTN_MARGIN;
2120
+ y = BTN_MARGIN + BTN_SIZE + 8;
2121
+ break;
2085
2122
  case "top-right":
2086
- return { x: Math.max(0, vw - 16 - width), y: 16 + 48 };
2123
+ x = vw - BTN_MARGIN - width;
2124
+ y = BTN_MARGIN + BTN_SIZE + 8;
2125
+ break;
2087
2126
  case "bottom-left":
2088
2127
  default:
2089
- return { x: 16, y: Math.max(0, vh - 16 - height - 48) };
2128
+ x = BTN_MARGIN;
2129
+ y = vh - BTN_MARGIN - height - BTN_SIZE - 8;
2130
+ break;
2090
2131
  }
2132
+ return {
2133
+ x: Math.max(0, Math.min(x, vw - width)),
2134
+ y: Math.max(0, Math.min(y, vh - height))
2135
+ };
2091
2136
  }
2092
2137
  function BlockTreeDevTools({
2093
2138
  blocks,
@@ -2098,13 +2143,89 @@ function BlockTreeDevTools({
2098
2143
  initialOpen = false,
2099
2144
  position = "bottom-left",
2100
2145
  buttonStyle,
2101
- panelStyle
2146
+ panelStyle,
2147
+ forceMount = false
2102
2148
  }) {
2149
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "production" && !forceMount) {
2150
+ return null;
2151
+ }
2103
2152
  const [isOpen, setIsOpen] = react.useState(initialOpen);
2104
2153
  const [showDiff, setShowDiff] = react.useState(false);
2105
2154
  const [cardPos, setCardPos] = react.useState(null);
2106
2155
  const [cardSize, setCardSize] = react.useState({ w: DEFAULT_WIDTH, h: DEFAULT_HEIGHT });
2107
2156
  const [showTooltip, setShowTooltip] = react.useState(false);
2157
+ const [activeCorner, setActiveCorner] = react.useState(() => loadStoredPosition() ?? position);
2158
+ const [btnPos, setBtnPos] = react.useState(() => cornerToXY(loadStoredPosition() ?? position));
2159
+ const [btnDragging, setBtnDragging] = react.useState(false);
2160
+ const [btnTransition, setBtnTransition] = react.useState(false);
2161
+ const btnDragRef = react.useRef({
2162
+ active: false,
2163
+ startX: 0,
2164
+ startY: 0,
2165
+ origX: 0,
2166
+ origY: 0,
2167
+ moved: false
2168
+ });
2169
+ react.useEffect(() => {
2170
+ if (!btnDragging) {
2171
+ setBtnPos(cornerToXY(activeCorner));
2172
+ }
2173
+ }, [activeCorner, btnDragging]);
2174
+ react.useEffect(() => {
2175
+ const onResize = () => {
2176
+ if (!btnDragRef.current.active) {
2177
+ setBtnPos(cornerToXY(activeCorner));
2178
+ }
2179
+ };
2180
+ window.addEventListener("resize", onResize);
2181
+ return () => window.removeEventListener("resize", onResize);
2182
+ }, [activeCorner]);
2183
+ const handleBtnPointerDown = react.useCallback((e) => {
2184
+ btnDragRef.current = {
2185
+ active: true,
2186
+ startX: e.clientX,
2187
+ startY: e.clientY,
2188
+ origX: btnPos.x,
2189
+ origY: btnPos.y,
2190
+ moved: false
2191
+ };
2192
+ e.target.setPointerCapture(e.pointerId);
2193
+ }, [btnPos]);
2194
+ const handleBtnPointerMove = react.useCallback((e) => {
2195
+ const r = btnDragRef.current;
2196
+ if (!r.active) return;
2197
+ const dx = e.clientX - r.startX;
2198
+ const dy = e.clientY - r.startY;
2199
+ if (!r.moved && Math.abs(dx) < BTN_DRAG_THRESHOLD && Math.abs(dy) < BTN_DRAG_THRESHOLD) return;
2200
+ r.moved = true;
2201
+ setBtnDragging(true);
2202
+ const newX = r.origX + dx;
2203
+ const newY = r.origY + dy;
2204
+ const maxX = window.innerWidth - BTN_SIZE;
2205
+ const maxY = window.innerHeight - BTN_SIZE;
2206
+ setBtnPos({
2207
+ x: Math.max(0, Math.min(newX, maxX)),
2208
+ y: Math.max(0, Math.min(newY, maxY))
2209
+ });
2210
+ }, []);
2211
+ const handleBtnPointerUp = react.useCallback(() => {
2212
+ const wasMoved = btnDragRef.current.moved;
2213
+ btnDragRef.current.active = false;
2214
+ if (wasMoved) {
2215
+ const newCorner = xyToCorner(btnPos.x, btnPos.y);
2216
+ setActiveCorner(newCorner);
2217
+ savePosition(newCorner);
2218
+ setBtnTransition(true);
2219
+ setBtnPos(cornerToXY(newCorner));
2220
+ setTimeout(() => {
2221
+ setBtnTransition(false);
2222
+ setBtnDragging(false);
2223
+ }, 300);
2224
+ } else {
2225
+ setBtnDragging(false);
2226
+ setIsOpen((prev) => !prev);
2227
+ }
2228
+ }, [btnPos]);
2108
2229
  const dragRef = react.useRef({
2109
2230
  dragging: false,
2110
2231
  startX: 0,
@@ -2133,18 +2254,28 @@ function BlockTreeDevTools({
2133
2254
  const targetW = showDiff ? DEFAULT_WIDTH + DIFF_EXTRA_WIDTH : DEFAULT_WIDTH;
2134
2255
  const wasDefault = Math.abs(prev.w - DEFAULT_WIDTH) < 20;
2135
2256
  const wasExpanded = Math.abs(prev.w - (DEFAULT_WIDTH + DIFF_EXTRA_WIDTH)) < 20;
2257
+ let newW = prev.w;
2136
2258
  if (showDiff && (wasDefault || prev.w < targetW)) {
2137
- return { ...prev, w: targetW };
2259
+ newW = targetW;
2260
+ } else if (!showDiff && wasExpanded) {
2261
+ newW = DEFAULT_WIDTH;
2138
2262
  }
2139
- if (!showDiff && wasExpanded) {
2140
- return { ...prev, w: DEFAULT_WIDTH };
2263
+ if (newW !== prev.w) {
2264
+ setCardPos((cp) => {
2265
+ if (!cp) return cp;
2266
+ const vw = typeof window !== "undefined" ? window.innerWidth : 1024;
2267
+ const maxX = Math.max(0, vw - newW);
2268
+ if (cp.x > maxX) return { ...cp, x: maxX };
2269
+ return cp;
2270
+ });
2271
+ return { ...prev, w: newW };
2141
2272
  }
2142
2273
  return prev;
2143
2274
  });
2144
2275
  }, [showDiff]);
2145
2276
  const getDefaultCardPos = react.useCallback(() => {
2146
- return computeCardOrigin(position, cardSize.w, cardSize.h);
2147
- }, [position, cardSize.w, cardSize.h]);
2277
+ return computeCardOrigin(activeCorner, cardSize.w, cardSize.h);
2278
+ }, [activeCorner, cardSize.w, cardSize.h]);
2148
2279
  react.useEffect(() => {
2149
2280
  if (isOpen && !cardPos) {
2150
2281
  setCardPos(getDefaultCardPos());
@@ -2253,23 +2384,27 @@ function BlockTreeDevTools({
2253
2384
  const d = new Date(ts);
2254
2385
  return `${d.getHours().toString().padStart(2, "0")}:${d.getMinutes().toString().padStart(2, "0")}:${d.getSeconds().toString().padStart(2, "0")}`;
2255
2386
  };
2256
- const anchor = getAnchorCSS(position);
2387
+ const isBottom = activeCorner.startsWith("bottom");
2388
+ const isLeft = activeCorner.endsWith("left");
2257
2389
  const triggerBtnStyle = {
2258
2390
  position: "fixed",
2259
- ...anchor,
2391
+ left: btnPos.x,
2392
+ top: btnPos.y,
2260
2393
  zIndex: 99998,
2261
- width: 40,
2262
- height: 40,
2394
+ width: BTN_SIZE,
2395
+ height: BTN_SIZE,
2263
2396
  borderRadius: "50%",
2264
2397
  border: "none",
2265
2398
  background: isOpen ? "rgba(59, 130, 246, 0.9)" : "rgba(30, 30, 30, 0.85)",
2266
2399
  color: "#fff",
2267
- cursor: "pointer",
2400
+ cursor: btnDragging ? "grabbing" : "pointer",
2268
2401
  display: "flex",
2269
2402
  alignItems: "center",
2270
2403
  justifyContent: "center",
2271
2404
  boxShadow: "0 2px 8px rgba(0,0,0,0.3)",
2272
- transition: "background 0.15s, transform 0.15s",
2405
+ 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",
2406
+ touchAction: "none",
2407
+ userSelect: "none",
2273
2408
  ...buttonStyle
2274
2409
  };
2275
2410
  const tooltipStyle = {
@@ -2283,9 +2418,8 @@ function BlockTreeDevTools({
2283
2418
  color: "#ccc",
2284
2419
  boxShadow: "0 2px 8px rgba(0,0,0,0.3)",
2285
2420
  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 }
2421
+ ...isBottom ? { bottom: "100%", marginBottom: 8 } : { top: "100%", marginTop: 8 },
2422
+ ...isLeft ? { left: 0 } : { right: 0 }
2289
2423
  };
2290
2424
  const cardStyle = {
2291
2425
  position: "fixed",
@@ -2476,11 +2610,13 @@ function BlockTreeDevTools({
2476
2610
  return { color: "rgba(200,200,200,0.5)" };
2477
2611
  };
2478
2612
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2479
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "fixed", ...anchor, zIndex: 99998 }, children: [
2613
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "fixed", left: btnPos.x, top: btnPos.y, zIndex: 99998 }, children: [
2480
2614
  /* @__PURE__ */ jsxRuntime.jsx(
2481
2615
  "button",
2482
2616
  {
2483
- onClick: toggle,
2617
+ onPointerDown: handleBtnPointerDown,
2618
+ onPointerMove: handleBtnPointerMove,
2619
+ onPointerUp: handleBtnPointerUp,
2484
2620
  onMouseEnter: () => setShowTooltip(true),
2485
2621
  onMouseLeave: () => setShowTooltip(false),
2486
2622
  style: triggerBtnStyle,
@@ -2488,7 +2624,7 @@ function BlockTreeDevTools({
2488
2624
  children: /* @__PURE__ */ jsxRuntime.jsx(DevToolsLogo, { size: 20 })
2489
2625
  }
2490
2626
  ),
2491
- showTooltip && !isOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { style: tooltipStyle, children: "dnd-block-tree DevTools" })
2627
+ showTooltip && !isOpen && !btnDragging && /* @__PURE__ */ jsxRuntime.jsx("div", { style: tooltipStyle, children: "dnd-block-tree DevTools" })
2492
2628
  ] }),
2493
2629
  isOpen && cardPos && /* @__PURE__ */ jsxRuntime.jsxs(
2494
2630
  "div",