@bian-womp/spark-workbench 0.3.42 → 0.3.44

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/lib/cjs/index.cjs CHANGED
@@ -5846,6 +5846,13 @@ function useKeyboardShortcutToast() {
5846
5846
  const SelectionBoundOverlay = ({ selection, rfInstance, viewportTick }) => {
5847
5847
  const overlayRef = React.useRef(null);
5848
5848
  const [parentRect, setParentRect] = React.useState(null);
5849
+ const [isDragging, setIsDragging] = React.useState(false);
5850
+ const [bounds, setBounds] = React.useState(null);
5851
+ const dragStateRef = React.useRef(null);
5852
+ const moveListenerRef = React.useRef(null);
5853
+ const upListenerRef = React.useRef(null);
5854
+ React.useRef(null);
5855
+ const { wb } = useWorkbenchContext();
5849
5856
  React.useEffect(() => {
5850
5857
  if (!overlayRef.current)
5851
5858
  return;
@@ -5865,56 +5872,190 @@ const SelectionBoundOverlay = ({ selection, rfInstance, viewportTick }) => {
5865
5872
  window.removeEventListener("scroll", scrollHandler, true);
5866
5873
  };
5867
5874
  }, [viewportTick]);
5868
- const selectionBounds = React.useMemo(() => {
5875
+ const cleanupDragListeners = React.useCallback(() => {
5876
+ if (moveListenerRef.current) {
5877
+ window.removeEventListener("mousemove", moveListenerRef.current);
5878
+ moveListenerRef.current = null;
5879
+ }
5880
+ if (upListenerRef.current) {
5881
+ window.removeEventListener("mouseup", upListenerRef.current);
5882
+ upListenerRef.current = null;
5883
+ }
5884
+ dragStateRef.current = null;
5885
+ setIsDragging(false);
5886
+ }, []);
5887
+ React.useEffect(() => cleanupDragListeners, [cleanupDragListeners]);
5888
+ const handleMouseMove = React.useCallback((e) => {
5889
+ if (!rfInstance || !wb || !parentRect)
5890
+ return;
5891
+ const dragState = dragStateRef.current;
5892
+ if (!dragState)
5893
+ return;
5894
+ e.preventDefault();
5895
+ e.stopPropagation();
5896
+ const current = rfInstance.screenToFlowPosition({
5897
+ x: e.clientX,
5898
+ y: e.clientY,
5899
+ });
5900
+ const dx = current.x - dragState.startFlow.x;
5901
+ const dy = current.y - dragState.startFlow.y;
5902
+ // Update nodes directly via React Flow for immediate visual feedback
5903
+ const nodes = rfInstance.getNodes();
5904
+ const updatedNodes = nodes.map((node) => {
5905
+ if (dragState.initialPositions[node.id]) {
5906
+ const initialPos = dragState.initialPositions[node.id];
5907
+ return {
5908
+ ...node,
5909
+ position: {
5910
+ x: initialPos.x + dx,
5911
+ y: initialPos.y + dy,
5912
+ },
5913
+ };
5914
+ }
5915
+ return node;
5916
+ });
5917
+ rfInstance.setNodes(updatedNodes);
5918
+ // Also update workbench state
5919
+ const nextPositions = {};
5920
+ for (const [nodeId, pos] of Object.entries(dragState.initialPositions)) {
5921
+ nextPositions[nodeId] = { x: pos.x + dx, y: pos.y + dy };
5922
+ }
5923
+ if (Object.keys(nextPositions).length) {
5924
+ wb.setPositions(nextPositions, { commit: false });
5925
+ }
5926
+ }, [rfInstance, wb, parentRect]);
5927
+ const handleMouseUp = React.useCallback((e) => {
5928
+ if (!rfInstance || !wb) {
5929
+ cleanupDragListeners();
5930
+ return;
5931
+ }
5932
+ const dragState = dragStateRef.current;
5933
+ if (!dragState) {
5934
+ cleanupDragListeners();
5935
+ return;
5936
+ }
5937
+ e.preventDefault();
5938
+ e.stopPropagation();
5939
+ const current = rfInstance.screenToFlowPosition({
5940
+ x: e.clientX,
5941
+ y: e.clientY,
5942
+ });
5943
+ const dx = current.x - dragState.startFlow.x;
5944
+ const dy = current.y - dragState.startFlow.y;
5945
+ const nextPositions = {};
5946
+ for (const [nodeId, pos] of Object.entries(dragState.initialPositions)) {
5947
+ nextPositions[nodeId] = { x: pos.x + dx, y: pos.y + dy };
5948
+ }
5949
+ if (Object.keys(nextPositions).length) {
5950
+ wb.setPositions(nextPositions, { commit: true });
5951
+ }
5952
+ cleanupDragListeners();
5953
+ }, [cleanupDragListeners, rfInstance, wb]);
5954
+ const handleMouseDown = React.useCallback((e) => {
5955
+ if (e.button !== 0)
5956
+ return;
5957
+ if (!rfInstance || !wb)
5958
+ return;
5959
+ if (selection.nodes.length < 2)
5960
+ return;
5961
+ e.preventDefault();
5962
+ e.stopPropagation();
5963
+ const startFlow = rfInstance.screenToFlowPosition({
5964
+ x: e.clientX,
5965
+ y: e.clientY,
5966
+ });
5967
+ const positions = wb.getPositions();
5968
+ const initialPositions = {};
5969
+ for (const nodeId of selection.nodes) {
5970
+ const pos = positions[nodeId];
5971
+ if (pos) {
5972
+ initialPositions[nodeId] = pos;
5973
+ }
5974
+ }
5975
+ if (!Object.keys(initialPositions).length)
5976
+ return;
5977
+ dragStateRef.current = { startFlow, initialPositions };
5978
+ setIsDragging(true);
5979
+ moveListenerRef.current = handleMouseMove;
5980
+ upListenerRef.current = handleMouseUp;
5981
+ window.addEventListener("mousemove", handleMouseMove, { passive: false });
5982
+ window.addEventListener("mouseup", handleMouseUp, { passive: false });
5983
+ }, [handleMouseMove, handleMouseUp, rfInstance, selection.nodes, wb]);
5984
+ // Continuous bounds update loop
5985
+ React.useEffect(() => {
5869
5986
  if (typeof document === "undefined" ||
5870
5987
  !rfInstance ||
5871
5988
  !parentRect ||
5872
5989
  selection.nodes.length < 2) {
5873
- return null;
5990
+ setBounds(null);
5991
+ return;
5874
5992
  }
5875
- let bounds = null;
5876
- for (const nodeId of selection.nodes) {
5877
- const el = document.querySelector(`.react-flow__node[data-id="${nodeId}"]`);
5878
- if (!el)
5879
- continue;
5880
- const rect = el.getBoundingClientRect();
5881
- const relativeLeft = rect.left - parentRect.left;
5882
- const relativeTop = rect.top - parentRect.top;
5883
- const relativeRight = rect.right - parentRect.left;
5884
- const relativeBottom = rect.bottom - parentRect.top;
5885
- if (!bounds) {
5886
- bounds = {
5887
- left: relativeLeft,
5888
- top: relativeTop,
5889
- right: relativeRight,
5890
- bottom: relativeBottom,
5891
- };
5993
+ let animationFrameId = null;
5994
+ let isActive = true;
5995
+ const updateBounds = () => {
5996
+ if (!isActive)
5997
+ return;
5998
+ let calculatedBounds = null;
5999
+ for (const nodeId of selection.nodes) {
6000
+ const el = document.querySelector(`.react-flow__node[data-id="${nodeId}"]`);
6001
+ if (!el)
6002
+ continue;
6003
+ const rect = el.getBoundingClientRect();
6004
+ const relativeLeft = rect.left - parentRect.left;
6005
+ const relativeTop = rect.top - parentRect.top;
6006
+ const relativeRight = rect.right - parentRect.left;
6007
+ const relativeBottom = rect.bottom - parentRect.top;
6008
+ if (!calculatedBounds) {
6009
+ calculatedBounds = {
6010
+ left: relativeLeft,
6011
+ top: relativeTop,
6012
+ right: relativeRight,
6013
+ bottom: relativeBottom,
6014
+ };
6015
+ }
6016
+ else {
6017
+ calculatedBounds.left = Math.min(calculatedBounds.left, relativeLeft);
6018
+ calculatedBounds.top = Math.min(calculatedBounds.top, relativeTop);
6019
+ calculatedBounds.right = Math.max(calculatedBounds.right, relativeRight);
6020
+ calculatedBounds.bottom = Math.max(calculatedBounds.bottom, relativeBottom);
6021
+ }
5892
6022
  }
5893
- else {
5894
- bounds.left = Math.min(bounds.left, relativeLeft);
5895
- bounds.top = Math.min(bounds.top, relativeTop);
5896
- bounds.right = Math.max(bounds.right, relativeRight);
5897
- bounds.bottom = Math.max(bounds.bottom, relativeBottom);
6023
+ setBounds(calculatedBounds);
6024
+ // Continue the animation loop
6025
+ if (isActive) {
6026
+ animationFrameId = requestAnimationFrame(updateBounds);
5898
6027
  }
5899
- }
5900
- return bounds;
6028
+ };
6029
+ // Start the animation loop
6030
+ animationFrameId = requestAnimationFrame(updateBounds);
6031
+ return () => {
6032
+ isActive = false;
6033
+ if (animationFrameId !== null) {
6034
+ cancelAnimationFrame(animationFrameId);
6035
+ }
6036
+ };
5901
6037
  }, [selection.nodes, rfInstance, viewportTick, parentRect]);
5902
- if (!selectionBounds || selection.nodes.length < 2) {
6038
+ if (!bounds || selection.nodes.length < 2) {
5903
6039
  return jsxRuntime.jsx("div", { ref: overlayRef, style: { display: "none" } });
5904
6040
  }
5905
- const { left, top, right, bottom } = selectionBounds;
6041
+ const { left, top, right, bottom } = bounds;
5906
6042
  const width = right - left;
5907
6043
  const height = bottom - top;
5908
- return (jsxRuntime.jsx("div", { ref: overlayRef, style: {
6044
+ return (jsxRuntime.jsx("div", { ref: overlayRef, onMouseDown: handleMouseDown, style: {
5909
6045
  position: "absolute",
5910
6046
  left: `${left}px`,
5911
6047
  top: `${top}px`,
5912
6048
  width: `${width}px`,
5913
6049
  height: `${height}px`,
5914
- border: "1px dashed #0ea5e9",
5915
- pointerEvents: "none",
6050
+ border: isDragging ? "2px solid #0ea5e9" : "1px dashed #0ea5e9",
6051
+ backgroundColor: isDragging
6052
+ ? "rgba(14, 165, 233, 0.05)"
6053
+ : "transparent",
6054
+ pointerEvents: "auto",
6055
+ cursor: isDragging ? "grabbing" : "move",
5916
6056
  zIndex: 4,
5917
6057
  boxSizing: "border-box",
6058
+ transition: isDragging ? "none" : "border 0.1s ease",
5918
6059
  } }));
5919
6060
  };
5920
6061