@particle-academy/react-fancy 2.8.1 → 2.9.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.cjs CHANGED
@@ -11771,7 +11771,7 @@ function useCanvas() {
11771
11771
  return ctx;
11772
11772
  }
11773
11773
  function CanvasNode({ children, id, x, y, draggable, onPositionChange, className, style }) {
11774
- const { registerNode, unregisterNode, viewport } = useCanvas();
11774
+ const { registerNode, unregisterNode, viewport, gridSize, snapToGrid } = useCanvas();
11775
11775
  const nodeRef = react.useRef(null);
11776
11776
  const isDragging = react.useRef(false);
11777
11777
  const dragStart = react.useRef({ mouseX: 0, mouseY: 0, nodeX: 0, nodeY: 0 });
@@ -11804,9 +11804,15 @@ function CanvasNode({ children, id, x, y, draggable, onPositionChange, className
11804
11804
  if (!isDragging.current) return;
11805
11805
  const dx = (e.clientX - dragStart.current.mouseX) / viewport.zoom;
11806
11806
  const dy = (e.clientY - dragStart.current.mouseY) / viewport.zoom;
11807
- onPositionChange?.(dragStart.current.nodeX + dx, dragStart.current.nodeY + dy);
11807
+ let nx = dragStart.current.nodeX + dx;
11808
+ let ny = dragStart.current.nodeY + dy;
11809
+ if (snapToGrid && gridSize > 0) {
11810
+ nx = Math.round(nx / gridSize) * gridSize;
11811
+ ny = Math.round(ny / gridSize) * gridSize;
11812
+ }
11813
+ onPositionChange?.(nx, ny);
11808
11814
  },
11809
- [viewport.zoom, onPositionChange]
11815
+ [viewport.zoom, onPositionChange, snapToGrid, gridSize]
11810
11816
  );
11811
11817
  const handlePointerUp = react.useCallback(() => {
11812
11818
  isDragging.current = false;
@@ -12054,6 +12060,10 @@ function CanvasRoot({
12054
12060
  pannable = true,
12055
12061
  zoomable = true,
12056
12062
  showGrid = false,
12063
+ gridStyle = "dots",
12064
+ gridSize = 20,
12065
+ gridColor = "rgb(161 161 170 / 0.3)",
12066
+ snapToGrid = false,
12057
12067
  fitOnMount = false,
12058
12068
  className,
12059
12069
  style
@@ -12071,8 +12081,8 @@ function CanvasRoot({
12071
12081
  containerRef
12072
12082
  });
12073
12083
  const ctx = react.useMemo(
12074
- () => ({ viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, containerRef }),
12075
- [viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion]
12084
+ () => ({ viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, containerRef, gridSize, snapToGrid }),
12085
+ [viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, gridSize, snapToGrid]
12076
12086
  );
12077
12087
  const hasFitted = react.useRef(false);
12078
12088
  react.useEffect(() => {
@@ -12128,9 +12138,13 @@ function CanvasRoot({
12128
12138
  {
12129
12139
  "data-canvas-bg": "",
12130
12140
  className: "absolute inset-0",
12131
- style: showGrid ? {
12132
- backgroundImage: `radial-gradient(circle, rgb(161 161 170 / 0.3) 1px, transparent 1px)`,
12133
- backgroundSize: `${20 * viewport.zoom}px ${20 * viewport.zoom}px`,
12141
+ style: showGrid && gridStyle !== "none" ? gridStyle === "lines" ? {
12142
+ backgroundImage: `linear-gradient(to right, ${gridColor} 1px, transparent 1px), linear-gradient(to bottom, ${gridColor} 1px, transparent 1px)`,
12143
+ backgroundSize: `${gridSize * viewport.zoom}px ${gridSize * viewport.zoom}px`,
12144
+ backgroundPosition: `${viewport.panX}px ${viewport.panY}px`
12145
+ } : {
12146
+ backgroundImage: `radial-gradient(circle, ${gridColor} 1px, transparent 1px)`,
12147
+ backgroundSize: `${gridSize * viewport.zoom}px ${gridSize * viewport.zoom}px`,
12134
12148
  backgroundPosition: `${viewport.panX}px ${viewport.panY}px`
12135
12149
  } : void 0
12136
12150
  }
@@ -12847,6 +12861,8 @@ function TreeNode({ node, depth }) {
12847
12861
  dragState,
12848
12862
  setDragState,
12849
12863
  onNodeMove,
12864
+ acceptExternalDrops,
12865
+ onExternalDrop,
12850
12866
  nodes,
12851
12867
  expandNode
12852
12868
  } = useTreeNav();
@@ -12889,14 +12905,18 @@ function TreeNode({ node, depth }) {
12889
12905
  clearAutoExpand();
12890
12906
  setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });
12891
12907
  }, [clearAutoExpand, setDragState]);
12908
+ const isExternalDrag = !dragState.draggedNodeId;
12892
12909
  const handleDragOver = react.useCallback((e) => {
12893
- if (!dragState.draggedNodeId) return;
12894
- const sourceId = dragState.draggedNodeId;
12895
- if (sourceId === node.id) return;
12896
- if (isDescendantOf(nodes, sourceId, node.id)) return;
12910
+ if (isExternalDrag) {
12911
+ if (!acceptExternalDrops) return;
12912
+ } else {
12913
+ const sourceId = dragState.draggedNodeId;
12914
+ if (sourceId === node.id) return;
12915
+ if (isDescendantOf(nodes, sourceId, node.id)) return;
12916
+ }
12897
12917
  e.preventDefault();
12898
12918
  e.stopPropagation();
12899
- e.dataTransfer.dropEffect = "move";
12919
+ e.dataTransfer.dropEffect = isExternalDrag ? "copy" : "move";
12900
12920
  const position = computeDropPosition(e, !!isFolder);
12901
12921
  if (isFolder && !isExpanded && position === "inside") {
12902
12922
  if (!autoExpandTimer.current) {
@@ -12909,9 +12929,9 @@ function TreeNode({ node, depth }) {
12909
12929
  clearAutoExpand();
12910
12930
  }
12911
12931
  if (dragState.dropTargetId !== node.id || dragState.dropPosition !== position) {
12912
- setDragState({ draggedNodeId: sourceId, dropTargetId: node.id, dropPosition: position });
12932
+ setDragState({ draggedNodeId: dragState.draggedNodeId, dropTargetId: node.id, dropPosition: position });
12913
12933
  }
12914
- }, [dragState, node.id, isFolder, isExpanded, nodes, setDragState, expandNode, clearAutoExpand]);
12934
+ }, [dragState, isExternalDrag, acceptExternalDrops, node.id, isFolder, isExpanded, nodes, setDragState, expandNode, clearAutoExpand]);
12915
12935
  const handleDragLeave = react.useCallback((e) => {
12916
12936
  if (!e.currentTarget.contains(e.relatedTarget)) {
12917
12937
  clearAutoExpand();
@@ -12925,21 +12945,25 @@ function TreeNode({ node, depth }) {
12925
12945
  e.stopPropagation();
12926
12946
  clearAutoExpand();
12927
12947
  const sourceId = dragState.draggedNodeId;
12928
- const position = dragState.dropPosition;
12929
- if (!sourceId || !position) return;
12930
- if (sourceId === node.id) return;
12931
- if (isDescendantOf(nodes, sourceId, node.id)) return;
12932
- onNodeMove?.(sourceId, node.id, position);
12948
+ const position = dragState.dropPosition ?? computeDropPosition(e, !!isFolder);
12949
+ if (sourceId) {
12950
+ if (sourceId === node.id) return;
12951
+ if (isDescendantOf(nodes, sourceId, node.id)) return;
12952
+ onNodeMove?.(sourceId, node.id, position);
12953
+ } else if (acceptExternalDrops) {
12954
+ onExternalDrop?.(e, node, position);
12955
+ }
12933
12956
  setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });
12934
- }, [dragState, node.id, nodes, onNodeMove, setDragState, clearAutoExpand]);
12957
+ }, [dragState, node, isFolder, nodes, onNodeMove, acceptExternalDrops, onExternalDrop, setDragState, clearAutoExpand]);
12935
12958
  const canDrag = draggable && !node.disabled;
12959
+ const dropEnabled = draggable || acceptExternalDrops;
12936
12960
  return /* @__PURE__ */ jsxRuntime.jsxs(
12937
12961
  "div",
12938
12962
  {
12939
12963
  "data-react-fancy-tree-node": "",
12940
- onDragOver: draggable ? handleDragOver : void 0,
12941
- onDragLeave: draggable ? handleDragLeave : void 0,
12942
- onDrop: draggable ? handleDrop : void 0,
12964
+ onDragOver: dropEnabled ? handleDragOver : void 0,
12965
+ onDragLeave: dropEnabled ? handleDragLeave : void 0,
12966
+ onDrop: dropEnabled ? handleDrop : void 0,
12943
12967
  children: [
12944
12968
  isDropTarget && dropPosition === "before" && /* @__PURE__ */ jsxRuntime.jsx(
12945
12969
  "div",
@@ -13012,6 +13036,8 @@ function TreeNavRoot({
13012
13036
  onNodeContextMenu,
13013
13037
  draggable = false,
13014
13038
  onNodeMove,
13039
+ acceptExternalDrops = false,
13040
+ onExternalDrop,
13015
13041
  expandedIds: controlledExpanded,
13016
13042
  defaultExpandedIds,
13017
13043
  onExpandedChange,
@@ -13065,6 +13091,8 @@ function TreeNavRoot({
13065
13091
  dragState,
13066
13092
  setDragState,
13067
13093
  onNodeMove,
13094
+ acceptExternalDrops,
13095
+ onExternalDrop,
13068
13096
  nodes,
13069
13097
  expandNode
13070
13098
  }),
@@ -13079,16 +13107,19 @@ function TreeNavRoot({
13079
13107
  draggable,
13080
13108
  dragState,
13081
13109
  onNodeMove,
13110
+ acceptExternalDrops,
13111
+ onExternalDrop,
13082
13112
  nodes,
13083
13113
  expandNode
13084
13114
  ]
13085
13115
  );
13116
+ const dropEnabled = draggable || acceptExternalDrops;
13086
13117
  return /* @__PURE__ */ jsxRuntime.jsx(TreeNavContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsx(
13087
13118
  "nav",
13088
13119
  {
13089
13120
  "data-react-fancy-tree-nav": "",
13090
13121
  className: cn("flex flex-col gap-0.5 py-1 text-sm", className),
13091
- onDragEnd: draggable ? handleDragEnd : void 0,
13122
+ onDragEnd: dropEnabled ? handleDragEnd : void 0,
13092
13123
  children: nodes.map((node) => /* @__PURE__ */ jsxRuntime.jsx(TreeNode, { node, depth: 0 }, node.id))
13093
13124
  }
13094
13125
  ) });