Merge pull request #3830 from xyflow/next
React Flow 12.0.0-next.8, Svelte Flow 0.0.35
This commit is contained in:
@@ -10,9 +10,10 @@ import {
|
|||||||
Edge,
|
Edge,
|
||||||
useReactFlow,
|
useReactFlow,
|
||||||
Panel,
|
Panel,
|
||||||
|
OnNodeDrag,
|
||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
|
|
||||||
const onNodeDrag = (_: MouseEvent, node: Node) => console.log('drag', node);
|
const onNodeDrag: OnNodeDrag = (_, node) => console.log('drag', node);
|
||||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import React, { memo, FC, CSSProperties, useCallback, useEffect } from 'react';
|
import React, { memo, FC, CSSProperties, useCallback } from 'react';
|
||||||
import { Handle, Position, NodeProps, Connection, Edge, useOnViewportChange, Viewport } from '@xyflow/react';
|
import { Handle, Position, NodeProps, Connection, Edge, useOnViewportChange, Viewport } from '@xyflow/react';
|
||||||
|
|
||||||
|
import type { ColorSelectorNode } from '.';
|
||||||
|
|
||||||
const targetHandleStyle: CSSProperties = { background: '#555' };
|
const targetHandleStyle: CSSProperties = { background: '#555' };
|
||||||
const sourceHandleStyleA: CSSProperties = { ...targetHandleStyle, top: 10 };
|
const sourceHandleStyleA: CSSProperties = { ...targetHandleStyle, top: 10 };
|
||||||
const sourceHandleStyleB: CSSProperties = {
|
const sourceHandleStyleB: CSSProperties = {
|
||||||
@@ -11,7 +13,7 @@ const sourceHandleStyleB: CSSProperties = {
|
|||||||
|
|
||||||
const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params);
|
const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params);
|
||||||
|
|
||||||
const ColorSelectorNode: FC<NodeProps> = ({ data, isConnectable }) => {
|
const ColorSelectorNode: FC<NodeProps<ColorSelectorNode['data']>> = ({ data, isConnectable }) => {
|
||||||
const onStart = useCallback((viewport: Viewport) => console.log('onStart', viewport), []);
|
const onStart = useCallback((viewport: Viewport) => console.log('onStart', viewport), []);
|
||||||
const onChange = useCallback((viewport: Viewport) => console.log('onChange', viewport), []);
|
const onChange = useCallback((viewport: Viewport) => console.log('onChange', viewport), []);
|
||||||
const onEnd = useCallback((viewport: Viewport) => console.log('onEnd', viewport), []);
|
const onEnd = useCallback((viewport: Viewport) => console.log('onEnd', viewport), []);
|
||||||
|
|||||||
@@ -5,24 +5,33 @@ import {
|
|||||||
Controls,
|
Controls,
|
||||||
addEdge,
|
addEdge,
|
||||||
Node,
|
Node,
|
||||||
ReactFlowInstance,
|
|
||||||
Position,
|
Position,
|
||||||
SnapGrid,
|
SnapGrid,
|
||||||
Connection,
|
|
||||||
useNodesState,
|
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
Background,
|
Background,
|
||||||
Edge,
|
Edge,
|
||||||
|
OnNodeDrag,
|
||||||
|
OnInit,
|
||||||
|
applyNodeChanges,
|
||||||
|
OnNodesChange,
|
||||||
|
OnConnect,
|
||||||
|
OnBeforeDelete,
|
||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
|
|
||||||
import ColorSelectorNode from './ColorSelectorNode';
|
import ColorSelectorNode from './ColorSelectorNode';
|
||||||
|
|
||||||
const onInit = (reactFlowInstance: ReactFlowInstance) => {
|
export type ColorSelectorNode = Node<
|
||||||
|
{ color: string; onChange: (event: ChangeEvent<HTMLInputElement>) => void },
|
||||||
|
'selectorNode'
|
||||||
|
>;
|
||||||
|
export type MyNode = Node | ColorSelectorNode;
|
||||||
|
|
||||||
|
const onInit: OnInit<MyNode> = (reactFlowInstance) => {
|
||||||
console.log('flow loaded:', reactFlowInstance);
|
console.log('flow loaded:', reactFlowInstance);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
const onNodeDragStop: OnNodeDrag<MyNode> = (_, node) => console.log('drag stop', node);
|
||||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
const onNodeClick = (_: MouseEvent, node: MyNode) => console.log('click', node);
|
||||||
|
|
||||||
const initBgColor = '#1A192B';
|
const initBgColor = '#1A192B';
|
||||||
|
|
||||||
@@ -34,7 +43,16 @@ const nodeTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CustomNodeFlow = () => {
|
const CustomNodeFlow = () => {
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]);
|
const [nodes, setNodes] = useState<MyNode[]>([]);
|
||||||
|
const onNodesChange: OnNodesChange = useCallback(
|
||||||
|
(changes) =>
|
||||||
|
setNodes((nds) => {
|
||||||
|
const nextNodes = applyNodeChanges(changes, nds);
|
||||||
|
return nextNodes;
|
||||||
|
}),
|
||||||
|
[setNodes]
|
||||||
|
);
|
||||||
|
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
|
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
|
||||||
|
|
||||||
const [bgColor, setBgColor] = useState<string>(initBgColor);
|
const [bgColor, setBgColor] = useState<string>(initBgColor);
|
||||||
@@ -120,12 +138,13 @@ const CustomNodeFlow = () => {
|
|||||||
]);
|
]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onConnect = useCallback(
|
const onConnect: OnConnect = useCallback(
|
||||||
(connection: Connection) =>
|
(connection) => setEdges((eds) => addEdge({ ...connection, animated: true, style: { stroke: '#fff' } }, eds)),
|
||||||
setEdges((eds) => addEdge({ ...connection, animated: true, style: { stroke: '#fff' } }, eds)),
|
|
||||||
[setEdges]
|
[setEdges]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onBeforeDelete: OnBeforeDelete<MyNode> = useCallback(async (params) => true, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
@@ -143,16 +162,17 @@ const CustomNodeFlow = () => {
|
|||||||
fitView
|
fitView
|
||||||
minZoom={0.3}
|
minZoom={0.3}
|
||||||
maxZoom={2}
|
maxZoom={2}
|
||||||
|
onBeforeDelete={onBeforeDelete}
|
||||||
>
|
>
|
||||||
<MiniMap
|
<MiniMap
|
||||||
nodeStrokeColor={(n: Node): string => {
|
nodeStrokeColor={(n: MyNode): string => {
|
||||||
if (n.type === 'input') return '#0041d0';
|
if (n.type === 'input') return '#0041d0';
|
||||||
if (n.type === 'selectorNode') return bgColor;
|
if (n.type === 'selectorNode') return bgColor;
|
||||||
if (n.type === 'output') return '#ff0072';
|
if (n.type === 'output') return '#ff0072';
|
||||||
|
|
||||||
return '#eee';
|
return '#eee';
|
||||||
}}
|
}}
|
||||||
nodeColor={(n: Node): string => {
|
nodeColor={(n: MyNode): string => {
|
||||||
if (n.type === 'selectorNode') return bgColor;
|
if (n.type === 'selectorNode') return bgColor;
|
||||||
|
|
||||||
return '#fff';
|
return '#fff';
|
||||||
|
|||||||
@@ -175,6 +175,15 @@ const edgeTypes: EdgeTypes = {
|
|||||||
custom2: CustomEdge2,
|
custom2: CustomEdge2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultEdgeOptions = {
|
||||||
|
markerEnd: {
|
||||||
|
type: MarkerType.ArrowClosed,
|
||||||
|
color: 'red',
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const EdgesFlow = () => {
|
const EdgesFlow = () => {
|
||||||
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
const [nodes, , onNodesChange] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||||
@@ -197,6 +206,7 @@ const EdgesFlow = () => {
|
|||||||
onEdgeMouseMove={onEdgeMouseMove}
|
onEdgeMouseMove={onEdgeMouseMove}
|
||||||
onEdgeMouseLeave={onEdgeMouseLeave}
|
onEdgeMouseLeave={onEdgeMouseLeave}
|
||||||
onDelete={console.log}
|
onDelete={console.log}
|
||||||
|
defaultEdgeOptions={defaultEdgeOptions}
|
||||||
>
|
>
|
||||||
<MiniMap />
|
<MiniMap />
|
||||||
<Controls />
|
<Controls />
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
MiniMap,
|
MiniMap,
|
||||||
Background,
|
Background,
|
||||||
Panel,
|
Panel,
|
||||||
|
NodeOrigin,
|
||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
|
|
||||||
import DebugNode from './DebugNode';
|
import DebugNode from './DebugNode';
|
||||||
@@ -119,6 +120,7 @@ const initialNodes: Node[] = [
|
|||||||
data: { label: 'Node 3' },
|
data: { label: 'Node 3' },
|
||||||
position: { x: 400, y: 100 },
|
position: { x: 400, y: 100 },
|
||||||
className: 'light',
|
className: 'light',
|
||||||
|
extent: 'parent',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import UppercaseNode from './UppercaseNode';
|
|||||||
export type TextNode = Node<{ text: string }, 'text'>;
|
export type TextNode = Node<{ text: string }, 'text'>;
|
||||||
export type ResultNode = Node<{}, 'result'>;
|
export type ResultNode = Node<{}, 'result'>;
|
||||||
export type UppercaseNode = Node<{}, 'uppercase'>;
|
export type UppercaseNode = Node<{}, 'uppercase'>;
|
||||||
export type MyNode = Node<{ text: string }, 'text'> | Node<{}, 'result'> | Node<{}, 'uppercase'>;
|
export type MyNode = Node | TextNode | ResultNode | UppercaseNode;
|
||||||
|
|
||||||
const nodeTypes = {
|
const nodeTypes = {
|
||||||
text: TextNode,
|
text: TextNode,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
// See of connection landed inside the flow pane
|
// See of connection landed inside the flow pane
|
||||||
const targetIsPane = (event.target as HTMLDivElement)?.classList.contains('svelte-flow__pane');
|
const targetIsPane = (event.target as HTMLDivElement)?.classList.contains('svelte-flow__pane');
|
||||||
if (targetIsPane) {
|
if (targetIsPane && 'clientX' in event && 'clientY' in event) {
|
||||||
const id = getId();
|
const id = getId();
|
||||||
const position = {
|
const position = {
|
||||||
x: event.clientX,
|
x: event.clientX,
|
||||||
|
|||||||
@@ -43,10 +43,8 @@
|
|||||||
export let zIndex: $$Props['zIndex'] = undefined;
|
export let zIndex: $$Props['zIndex'] = undefined;
|
||||||
export let dragging: $$Props['dragging'] = false;
|
export let dragging: $$Props['dragging'] = false;
|
||||||
export let dragHandle: $$Props['dragHandle'] = undefined;
|
export let dragHandle: $$Props['dragHandle'] = undefined;
|
||||||
export let positionAbsolute: $$Props['positionAbsolute'] = {
|
export let positionAbsoluteX: $$Props['positionAbsoluteX'] = 0;
|
||||||
x: 0,
|
export let positionAbsoluteY: $$Props['positionAbsoluteY'] = 0;
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
export let isConnectable: $$Props['isConnectable'] = undefined;
|
export let isConnectable: $$Props['isConnectable'] = undefined;
|
||||||
|
|
||||||
data;
|
data;
|
||||||
@@ -59,7 +57,8 @@
|
|||||||
zIndex;
|
zIndex;
|
||||||
dragging;
|
dragging;
|
||||||
dragHandle;
|
dragHandle;
|
||||||
positionAbsolute;
|
positionAbsoluteX;
|
||||||
|
positionAbsoluteY;
|
||||||
isConnectable;
|
isConnectable;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
import CustomEdge from './CustomEdge.svelte';
|
import CustomEdge from './CustomEdge.svelte';
|
||||||
|
|
||||||
import '@xyflow/svelte/dist/style.css';
|
import '@xyflow/svelte/dist/style.css';
|
||||||
|
import InitTracker from './InitTracker.svelte';
|
||||||
|
|
||||||
const nodeTypes: NodeTypes = {
|
const nodeTypes: NodeTypes = {
|
||||||
custom: CustomNode,
|
custom: CustomNode,
|
||||||
@@ -148,6 +149,7 @@
|
|||||||
selectionMode={SelectionMode.Full}
|
selectionMode={SelectionMode.Full}
|
||||||
initialViewport={{ x: 100, y: 100, zoom: 2 }}
|
initialViewport={{ x: 100, y: 100, zoom: 2 }}
|
||||||
snapGrid={[25, 25]}
|
snapGrid={[25, 25]}
|
||||||
|
oninit={() => console.log('on init')}
|
||||||
on:nodeclick={(event) => console.log('on node click', event)}
|
on:nodeclick={(event) => console.log('on node click', event)}
|
||||||
on:nodemouseenter={(event) => console.log('on node enter', event)}
|
on:nodemouseenter={(event) => console.log('on node enter', event)}
|
||||||
on:nodemouseleave={(event) => console.log('on node leave', event)}
|
on:nodemouseleave={(event) => console.log('on node leave', event)}
|
||||||
@@ -207,6 +209,8 @@
|
|||||||
}}>hide/unhide</button
|
}}>hide/unhide</button
|
||||||
>
|
>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
|
<InitTracker />
|
||||||
</SvelteFlow>
|
</SvelteFlow>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { useNodesInitialized, useInitialized } from '@xyflow/svelte';
|
||||||
|
|
||||||
|
const nodesInitialized = useNodesInitialized();
|
||||||
|
const initialized = useInitialized();
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (nodesInitialized) {
|
||||||
|
console.log('nodes initialized');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (initialized) {
|
||||||
|
console.log('initialized');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,19 +1,33 @@
|
|||||||
# @xyflow/react
|
# @xyflow/react
|
||||||
|
|
||||||
|
## 12.0.0-next.8
|
||||||
|
|
||||||
|
### Patch changes
|
||||||
|
|
||||||
|
- selection box is not interrupted by selectionKey being let go
|
||||||
|
- fix `OnNodeDrag` type
|
||||||
|
- do not use fallback handle if a specific id is being used
|
||||||
|
- fix `defaultEdgeOptions` markers not being applied
|
||||||
|
- fix `getNodesBounds` and add second param for passing options
|
||||||
|
- fix `expandParent` for child nodes
|
||||||
|
|
||||||
## 12.0.0-next.7
|
## 12.0.0-next.7
|
||||||
|
|
||||||
## Minor changes
|
## Minor changes
|
||||||
|
|
||||||
- pass Node/Edge types to changes thanks @FelipeEmos
|
|
||||||
- use position instead of positionAbsolute for `getNodesBounds`
|
|
||||||
- add second option param to `screenToFlowPosition` for configuring if `snapToGrid` should be used
|
- add second option param to `screenToFlowPosition` for configuring if `snapToGrid` should be used
|
||||||
|
|
||||||
|
### Patch changes
|
||||||
|
|
||||||
|
- pass `Node`/ `Edge` types to changes thanks @FelipeEmos
|
||||||
|
- use position instead of positionAbsolute for `getNodesBounds`
|
||||||
- infer types for `getIncomers`, `getOutgoers`, `updateEdge`, `addEdge` and `getConnectedEdges` thanks @joeyballentine
|
- infer types for `getIncomers`, `getOutgoers`, `updateEdge`, `addEdge` and `getConnectedEdges` thanks @joeyballentine
|
||||||
- refactor handles: prefix with flow id for handling nested flows
|
- refactor handles: prefix with flow id for handling nested flows
|
||||||
- add comments for types like `ReactFlowProps` or `Node` for a better developer experience
|
- add comments for types like `ReactFlowProps` or `Node` for a better developer experience
|
||||||
|
|
||||||
## 12.0.0-next.6
|
## 12.0.0-next.6
|
||||||
|
|
||||||
### Minor changes
|
### Patch changes
|
||||||
|
|
||||||
- fix `deleteElements`
|
- fix `deleteElements`
|
||||||
- refactor internal `applyChanges`
|
- refactor internal `applyChanges`
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ const selector = (s: ReactFlowState) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
viewBB,
|
viewBB,
|
||||||
boundingRect: s.nodes.length > 0 ? getBoundsOfRects(getNodesBounds(s.nodes, s.nodeOrigin), viewBB) : viewBB,
|
boundingRect:
|
||||||
|
s.nodes.length > 0 ? getBoundsOfRects(getNodesBounds(s.nodes, { nodeOrigin: s.nodeOrigin }), viewBB) : viewBB,
|
||||||
rfId: s.rfId,
|
rfId: s.rfId,
|
||||||
nodeOrigin: s.nodeOrigin,
|
nodeOrigin: s.nodeOrigin,
|
||||||
panZoom: s.panZoom,
|
panZoom: s.panZoom,
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export function NodeToolbar({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeRect: Rect = getNodesBounds(nodes, nodeOrigin);
|
const nodeRect: Rect = getNodesBounds(nodes, { nodeOrigin });
|
||||||
const zIndex: number = Math.max(...nodes.map((node) => (node[internalsSymbol]?.z || 1) + 1));
|
const zIndex: number = Math.max(...nodes.map((node) => (node[internalsSymbol]?.z || 1) + 1));
|
||||||
|
|
||||||
const wrapperStyle: CSSProperties = {
|
const wrapperStyle: CSSProperties = {
|
||||||
|
|||||||
@@ -186,6 +186,7 @@ export function EdgeWrapper({
|
|||||||
animated: edge.animated,
|
animated: edge.animated,
|
||||||
inactive: !isSelectable && !onClick,
|
inactive: !isSelectable && !onClick,
|
||||||
updating: updateHover,
|
updating: updateHover,
|
||||||
|
selectable: isSelectable,
|
||||||
},
|
},
|
||||||
])}
|
])}
|
||||||
onClick={onEdgeClick}
|
onClick={onEdgeClick}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { useStore, useStoreApi } from '../../hooks/useStore';
|
|||||||
import { useNodeId } from '../../contexts/NodeIdContext';
|
import { useNodeId } from '../../contexts/NodeIdContext';
|
||||||
import { type ReactFlowState } from '../../types';
|
import { type ReactFlowState } from '../../types';
|
||||||
|
|
||||||
export type HandleComponentProps = HandleProps & Omit<HTMLAttributes<HTMLDivElement>, 'id'>;
|
export interface HandleComponentProps extends HandleProps, Omit<HTMLAttributes<HTMLDivElement>, 'id'> {}
|
||||||
|
|
||||||
const selector = (s: ReactFlowState) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
connectOnClick: s.connectOnClick,
|
connectOnClick: s.connectOnClick,
|
||||||
@@ -221,4 +221,7 @@ const HandleComponent = forwardRef<HTMLDivElement, HandleComponentProps>(
|
|||||||
|
|
||||||
HandleComponent.displayName = 'Handle';
|
HandleComponent.displayName = 'Handle';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Handle component is the part of a node that can be used to connect nodes.
|
||||||
|
*/
|
||||||
export const Handle = memo(HandleComponent);
|
export const Handle = memo(HandleComponent);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export type NodesSelectionProps = {
|
|||||||
|
|
||||||
const selector = (s: ReactFlowState) => {
|
const selector = (s: ReactFlowState) => {
|
||||||
const selectedNodes = s.nodes.filter((n) => n.selected);
|
const selectedNodes = s.nodes.filter((n) => n.selected);
|
||||||
const { width, height, x, y } = getNodesBounds(selectedNodes, s.nodeOrigin);
|
const { width, height, x, y } = getNodesBounds(selectedNodes, { nodeOrigin: s.nodeOrigin });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
width,
|
width,
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { memo, useCallback } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { type MarkerProps, createMarkerIds } from '@xyflow/system';
|
import { type MarkerProps, createMarkerIds } from '@xyflow/system';
|
||||||
|
|
||||||
import { useStore } from '../../hooks/useStore';
|
import { useStore } from '../../hooks/useStore';
|
||||||
import { useMarkerSymbol } from './MarkerSymbols';
|
import { useMarkerSymbol } from './MarkerSymbols';
|
||||||
import type { ReactFlowState } from '../../types';
|
|
||||||
|
|
||||||
type MarkerDefinitionsProps = {
|
type MarkerDefinitionsProps = {
|
||||||
defaultColor: string;
|
defaultColor: string;
|
||||||
@@ -43,23 +42,23 @@ const Marker = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const markerSelector =
|
|
||||||
({ defaultColor, rfId }: { defaultColor: string; rfId?: string }) =>
|
|
||||||
(s: ReactFlowState) => {
|
|
||||||
const markers = createMarkerIds(s.edges, { id: rfId, defaultColor });
|
|
||||||
|
|
||||||
return markers;
|
|
||||||
};
|
|
||||||
|
|
||||||
const markersEqual = (a: MarkerProps[], b: MarkerProps[]) =>
|
|
||||||
// the id includes all marker options, so we just need to look at that part of the marker
|
|
||||||
!(a.length !== b.length || a.some((m, i) => m.id !== b[i].id));
|
|
||||||
|
|
||||||
// when you have multiple flows on a page and you hide the first one, the other ones have no markers anymore
|
// when you have multiple flows on a page and you hide the first one, the other ones have no markers anymore
|
||||||
// when they do have markers with the same ids. To prevent this the user can pass a unique id to the react flow wrapper
|
// when they do have markers with the same ids. To prevent this the user can pass a unique id to the react flow wrapper
|
||||||
// that we can then use for creating our unique marker ids
|
// that we can then use for creating our unique marker ids
|
||||||
const MarkerDefinitions = ({ defaultColor, rfId }: MarkerDefinitionsProps) => {
|
const MarkerDefinitions = ({ defaultColor, rfId }: MarkerDefinitionsProps) => {
|
||||||
const markers = useStore(useCallback(markerSelector({ defaultColor, rfId }), [defaultColor, rfId]), markersEqual);
|
const edges = useStore((s) => s.edges);
|
||||||
|
const defaultEdgeOptions = useStore((s) => s.defaultEdgeOptions);
|
||||||
|
|
||||||
|
const markers = useMemo(() => {
|
||||||
|
const markers = createMarkerIds(edges, {
|
||||||
|
id: rfId,
|
||||||
|
defaultColor,
|
||||||
|
defaultMarkerStart: defaultEdgeOptions?.markerStart,
|
||||||
|
defaultMarkerEnd: defaultEdgeOptions?.markerEnd,
|
||||||
|
});
|
||||||
|
|
||||||
|
return markers;
|
||||||
|
}, [edges, defaultEdgeOptions, rfId, defaultColor]);
|
||||||
|
|
||||||
if (!markers.length) {
|
if (!markers.length) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ export type FlowRendererProps = Omit<
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const selector = (s: ReactFlowState) => s.nodesSelectionActive;
|
const selector = (s: ReactFlowState) => {
|
||||||
|
return { nodesSelectionActive: s.nodesSelectionActive, userSelectionActive: s.userSelectionActive };
|
||||||
|
};
|
||||||
|
|
||||||
const FlowRendererComponent = ({
|
const FlowRendererComponent = ({
|
||||||
children,
|
children,
|
||||||
@@ -67,13 +69,13 @@ const FlowRendererComponent = ({
|
|||||||
onViewportChange,
|
onViewportChange,
|
||||||
isControlledViewport,
|
isControlledViewport,
|
||||||
}: FlowRendererProps) => {
|
}: FlowRendererProps) => {
|
||||||
const nodesSelectionActive = useStore(selector);
|
const { nodesSelectionActive, userSelectionActive } = useStore(selector);
|
||||||
const selectionKeyPressed = useKeyPress(selectionKeyCode);
|
const selectionKeyPressed = useKeyPress(selectionKeyCode);
|
||||||
const panActivationKeyPressed = useKeyPress(panActivationKeyCode);
|
const panActivationKeyPressed = useKeyPress(panActivationKeyCode);
|
||||||
|
|
||||||
const panOnDrag = panActivationKeyPressed || _panOnDrag;
|
const panOnDrag = panActivationKeyPressed || _panOnDrag;
|
||||||
const panOnScroll = panActivationKeyPressed || _panOnScroll;
|
const panOnScroll = panActivationKeyPressed || _panOnScroll;
|
||||||
const isSelecting = selectionKeyPressed || (selectionOnDrag && panOnDrag !== true);
|
const isSelecting = selectionKeyPressed || userSelectionActive || (selectionOnDrag && panOnDrag !== true);
|
||||||
|
|
||||||
useGlobalKeyHandler({ deleteKeyCode, multiSelectionKeyCode });
|
useGlobalKeyHandler({ deleteKeyCode, multiSelectionKeyCode });
|
||||||
|
|
||||||
|
|||||||
@@ -10,18 +10,24 @@ const selector = (s: ReactFlowStore) => ({
|
|||||||
position: s.connectionStartHandle ? s.connectionPosition : null,
|
position: s.connectionStartHandle ? s.connectionPosition : null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type UseConnectionResult = {
|
||||||
|
/** The start handle where the user interaction started or null */
|
||||||
|
startHandle: ReactFlowStore['connectionStartHandle'];
|
||||||
|
/** The target handle that's inside the connection radius or null */
|
||||||
|
endHandle: ReactFlowStore['connectionEndHandle'];
|
||||||
|
/** The current connection status 'valid', 'invalid' or null*/
|
||||||
|
status: ReactFlowStore['connectionStatus'];
|
||||||
|
/** The current connection position or null */
|
||||||
|
position: ReactFlowStore['connectionPosition'] | null;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook for accessing the ongoing connection.
|
* Hook for accessing the ongoing connection.
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
* @returns ongoing connection: startHandle, endHandle, status, position
|
* @returns ongoing connection
|
||||||
*/
|
*/
|
||||||
export function useConnection(): {
|
export function useConnection(): UseConnectionResult {
|
||||||
startHandle: ReactFlowStore['connectionStartHandle'];
|
|
||||||
endHandle: ReactFlowStore['connectionEndHandle'];
|
|
||||||
status: ReactFlowStore['connectionStatus'];
|
|
||||||
position: ReactFlowStore['connectionPosition'] | null;
|
|
||||||
} {
|
|
||||||
const ongoingConnection = useStore(selector, shallow);
|
const ongoingConnection = useStore(selector, shallow);
|
||||||
|
|
||||||
return ongoingConnection;
|
return ongoingConnection;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { handleNodeClick } from '../components/Nodes/utils';
|
|||||||
import { useStoreApi } from './useStore';
|
import { useStoreApi } from './useStore';
|
||||||
|
|
||||||
type UseDragParams = {
|
type UseDragParams = {
|
||||||
nodeRef: RefObject<Element>;
|
nodeRef: RefObject<HTMLDivElement>;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
noDragClassName?: string;
|
noDragClassName?: string;
|
||||||
handleSelector?: string;
|
handleSelector?: string;
|
||||||
@@ -39,7 +39,7 @@ export function useDrag({
|
|||||||
handleNodeClick({
|
handleNodeClick({
|
||||||
id,
|
id,
|
||||||
store,
|
store,
|
||||||
nodeRef: nodeRef as RefObject<HTMLDivElement>,
|
nodeRef,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onDragStart: () => {
|
onDragStart: () => {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type useHandleConnectionsParams = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook to check if a <Handle /> is connected to another <Handle /> and get the connections.
|
* Hook to check if a <Handle /> is connected to another <Handle /> and get the connections.
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
* @param param.type - handle type 'source' or 'target'
|
* @param param.type - handle type 'source' or 'target'
|
||||||
|
|||||||
@@ -12,9 +12,15 @@ const selector = (options: UseNodesInitializedOptions) => (s: ReactFlowState) =>
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.nodes
|
for (const node of s.nodes) {
|
||||||
.filter((n) => (options.includeHiddenNodes ? true : !n.hidden))
|
if (options.includeHiddenNodes || !node.hidden) {
|
||||||
.every((n) => n[internalsSymbol]?.handleBounds !== undefined);
|
if (node[internalsSymbol]?.handleBounds === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export type UseOnSelectionChangeOptions = {
|
|||||||
* Hook for registering an onSelectionChange handler.
|
* Hook for registering an onSelectionChange handler.
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
* @params params.onChange - The handler to register
|
* @param params.onChange - The handler to register
|
||||||
*/
|
*/
|
||||||
export function useOnSelectionChange({ onChange }: UseOnSelectionChangeOptions) {
|
export function useOnSelectionChange({ onChange }: UseOnSelectionChangeOptions) {
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { calcNextPosition, snapPosition } from '@xyflow/system';
|
import { calculateNodePosition, snapPosition } from '@xyflow/system';
|
||||||
|
|
||||||
import { Node } from '../types';
|
import { Node } from '../types';
|
||||||
import { useStoreApi } from '../hooks/useStore';
|
import { useStoreApi } from '../hooks/useStore';
|
||||||
@@ -17,7 +17,17 @@ export function useUpdateNodePositions() {
|
|||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
|
|
||||||
const updatePositions = useCallback((params: { x: number; y: number; isShiftPressed: boolean }) => {
|
const updatePositions = useCallback((params: { x: number; y: number; isShiftPressed: boolean }) => {
|
||||||
const { nodeExtent, nodes, snapToGrid, snapGrid, nodesDraggable, onError, updateNodePositions } = store.getState();
|
const {
|
||||||
|
nodeExtent,
|
||||||
|
nodes,
|
||||||
|
snapToGrid,
|
||||||
|
snapGrid,
|
||||||
|
nodesDraggable,
|
||||||
|
onError,
|
||||||
|
updateNodePositions,
|
||||||
|
nodeLookup,
|
||||||
|
nodeOrigin,
|
||||||
|
} = store.getState();
|
||||||
const selectedNodes = nodes.filter(selectedAndDraggable(nodesDraggable));
|
const selectedNodes = nodes.filter(selectedAndDraggable(nodesDraggable));
|
||||||
// by default a node moves 5px on each key press, or 20px if shift is pressed
|
// by default a node moves 5px on each key press, or 20px if shift is pressed
|
||||||
// if snap grid is enabled, we use that for the velocity.
|
// if snap grid is enabled, we use that for the velocity.
|
||||||
@@ -31,27 +41,24 @@ export function useUpdateNodePositions() {
|
|||||||
const nodeUpdates = selectedNodes.map((node) => {
|
const nodeUpdates = selectedNodes.map((node) => {
|
||||||
if (node.computed?.positionAbsolute) {
|
if (node.computed?.positionAbsolute) {
|
||||||
let nextPosition = {
|
let nextPosition = {
|
||||||
x: node.computed?.positionAbsolute.x + xDiff,
|
x: node.computed.positionAbsolute.x + xDiff,
|
||||||
y: node.computed?.positionAbsolute.y + yDiff,
|
y: node.computed.positionAbsolute.y + yDiff,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (snapToGrid) {
|
if (snapToGrid) {
|
||||||
nextPosition = snapPosition(nextPosition, snapGrid);
|
nextPosition = snapPosition(nextPosition, snapGrid);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { positionAbsolute, position } = calcNextPosition(
|
const { position, positionAbsolute } = calculateNodePosition({
|
||||||
node,
|
nodeId: node.id,
|
||||||
nextPosition,
|
nextPosition,
|
||||||
nodes,
|
nodeLookup,
|
||||||
nodeExtent,
|
nodeExtent,
|
||||||
undefined,
|
nodeOrigin,
|
||||||
onError
|
onError,
|
||||||
);
|
});
|
||||||
|
|
||||||
node.position = position;
|
node.position = position;
|
||||||
if (!node.computed) {
|
|
||||||
node.computed = {};
|
|
||||||
}
|
|
||||||
node.computed.positionAbsolute = positionAbsolute;
|
node.computed.positionAbsolute = positionAbsolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,31 +86,31 @@ const useViewportHelper = (): ViewportHelperFunctions => {
|
|||||||
|
|
||||||
panZoom?.setViewport(viewport, { duration: options?.duration });
|
panZoom?.setViewport(viewport, { duration: options?.duration });
|
||||||
},
|
},
|
||||||
screenToFlowPosition: (position: XYPosition, options: { snapToGrid: boolean } = { snapToGrid: true }) => {
|
screenToFlowPosition: (clientPosition: XYPosition, options: { snapToGrid: boolean } = { snapToGrid: true }) => {
|
||||||
const { transform, snapGrid, domNode } = store.getState();
|
const { transform, snapGrid, domNode } = store.getState();
|
||||||
|
|
||||||
if (!domNode) {
|
if (!domNode) {
|
||||||
return position;
|
return clientPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { x: domX, y: domY } = domNode.getBoundingClientRect();
|
const { x: domX, y: domY } = domNode.getBoundingClientRect();
|
||||||
|
|
||||||
const correctedPosition = {
|
const correctedPosition = {
|
||||||
x: position.x - domX,
|
x: clientPosition.x - domX,
|
||||||
y: position.y - domY,
|
y: clientPosition.y - domY,
|
||||||
};
|
};
|
||||||
|
|
||||||
return pointToRendererPoint(correctedPosition, transform, options.snapToGrid, snapGrid);
|
return pointToRendererPoint(correctedPosition, transform, options.snapToGrid, snapGrid);
|
||||||
},
|
},
|
||||||
flowToScreenPosition: (position: XYPosition) => {
|
flowToScreenPosition: (flowPosition: XYPosition) => {
|
||||||
const { transform, domNode } = store.getState();
|
const { transform, domNode } = store.getState();
|
||||||
|
|
||||||
if (!domNode) {
|
if (!domNode) {
|
||||||
return position;
|
return flowPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { x: domX, y: domY } = domNode.getBoundingClientRect();
|
const { x: domX, y: domY } = domNode.getBoundingClientRect();
|
||||||
const rendererPosition = rendererPointToPoint(position, transform);
|
const rendererPosition = rendererPointToPoint(flowPosition, transform);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
x: rendererPosition.x + domX,
|
x: rendererPosition.x + domX,
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ export {
|
|||||||
type OnError,
|
type OnError,
|
||||||
type NodeProps,
|
type NodeProps,
|
||||||
type NodeOrigin,
|
type NodeOrigin,
|
||||||
type OnNodeDrag,
|
|
||||||
type OnSelectionDrag,
|
type OnSelectionDrag,
|
||||||
Position,
|
Position,
|
||||||
type XYPosition,
|
type XYPosition,
|
||||||
@@ -80,7 +79,6 @@ export {
|
|||||||
type ColorMode,
|
type ColorMode,
|
||||||
type ColorModeClass,
|
type ColorModeClass,
|
||||||
type HandleType,
|
type HandleType,
|
||||||
type OnBeforeDelete,
|
|
||||||
type ShouldResize,
|
type ShouldResize,
|
||||||
type OnResizeStart,
|
type OnResizeStart,
|
||||||
type OnResize,
|
type OnResize,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
getViewportForBounds,
|
getViewportForBounds,
|
||||||
Transform,
|
Transform,
|
||||||
updateConnectionLookup,
|
updateConnectionLookup,
|
||||||
|
devWarn,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { Edge, Node, ReactFlowStore } from '../types';
|
import type { Edge, Node, ReactFlowStore } from '../types';
|
||||||
@@ -37,7 +38,8 @@ const getInitialState = ({
|
|||||||
|
|
||||||
if (fitView && width && height) {
|
if (fitView && width && height) {
|
||||||
const nodesWithDimensions = nextNodes.filter((node) => node.width && node.height);
|
const nodesWithDimensions = nextNodes.filter((node) => node.width && node.height);
|
||||||
const bounds = getNodesBounds(nodesWithDimensions, [0, 0]);
|
// @todo users nodeOrigin should be used here
|
||||||
|
const bounds = getNodesBounds(nodesWithDimensions, { nodeOrigin: [0, 0] });
|
||||||
const { x, y, zoom } = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1);
|
const { x, y, zoom } = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1);
|
||||||
transform = [x, y, zoom];
|
transform = [x, y, zoom];
|
||||||
}
|
}
|
||||||
@@ -100,7 +102,7 @@ const getInitialState = ({
|
|||||||
autoPanOnConnect: true,
|
autoPanOnConnect: true,
|
||||||
autoPanOnNodeDrag: true,
|
autoPanOnNodeDrag: true,
|
||||||
connectionRadius: 20,
|
connectionRadius: 20,
|
||||||
onError: () => null,
|
onError: devWarn,
|
||||||
isValidConnection: undefined,
|
isValidConnection: undefined,
|
||||||
onSelectionChangeHandlers: [],
|
onSelectionChangeHandlers: [],
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,3 @@
|
|||||||
/* this will be exported as base.css and can be used for a basic styling */
|
/* this will be exported as base.css and can be used for a basic styling */
|
||||||
@import '../../../system/src/styles/init.css';
|
@import '../../../system/src/styles/init.css';
|
||||||
@import '../../../system/src/styles/base.css';
|
@import '../../../system/src/styles/base.css';
|
||||||
|
|
||||||
.react-flow {
|
|
||||||
--edge-label-background-color-default: #ffffff;
|
|
||||||
--edge-label-color-default: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-flow.dark {
|
|
||||||
--edge-label-background-color-default: #141414;
|
|
||||||
--edge-label-color-default: #f8f8f8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-flow__edge-textbg {
|
|
||||||
fill: var(--edge-label-background-color, var(--edge-label-background-color-default));
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-flow__edge-text {
|
|
||||||
fill: var(--edge-label-color, var(--edge-label-color-default));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,20 +3,10 @@
|
|||||||
@import '../../../system/src/styles/style.css';
|
@import '../../../system/src/styles/style.css';
|
||||||
@import '../../../system/src/styles/node-resizer.css';
|
@import '../../../system/src/styles/node-resizer.css';
|
||||||
|
|
||||||
.react-flow {
|
|
||||||
--edge-label-background-color-default: #ffffff;
|
|
||||||
--edge-label-color-default: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-flow.dark {
|
|
||||||
--edge-label-background-color-default: #141414;
|
|
||||||
--edge-label-color-default: #f8f8f8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-flow__edge-textbg {
|
.react-flow__edge-textbg {
|
||||||
fill: var(--edge-label-background-color, var(--edge-label-background-color-default));
|
fill: var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default));
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-flow__edge-text {
|
.react-flow__edge-text {
|
||||||
fill: var(--edge-label-color, var(--edge-label-color-default));
|
fill: var(--xy-edge-label-color, var(--xy-edge-label-color-default));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import type {
|
|||||||
IsValidConnection,
|
IsValidConnection,
|
||||||
ColorMode,
|
ColorMode,
|
||||||
SnapGrid,
|
SnapGrid,
|
||||||
OnBeforeDelete,
|
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -40,17 +39,18 @@ import type {
|
|||||||
OnDelete,
|
OnDelete,
|
||||||
OnNodesChange,
|
OnNodesChange,
|
||||||
OnEdgesChange,
|
OnEdgesChange,
|
||||||
NodeDragHandler,
|
|
||||||
NodeMouseHandler,
|
NodeMouseHandler,
|
||||||
SelectionDragHandler,
|
SelectionDragHandler,
|
||||||
EdgeMouseHandler,
|
EdgeMouseHandler,
|
||||||
|
OnNodeDrag,
|
||||||
|
OnBeforeDelete,
|
||||||
} from '.';
|
} from '.';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ReactFlow component props.
|
* ReactFlow component props.
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type ReactFlowProps = Omit<HTMLAttributes<HTMLDivElement>, 'onError'> & {
|
export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onError'> {
|
||||||
/** An array of nodes to render in a controlled flow.
|
/** An array of nodes to render in a controlled flow.
|
||||||
* @example
|
* @example
|
||||||
* const nodes = [
|
* const nodes = [
|
||||||
@@ -111,11 +111,11 @@ export type ReactFlowProps = Omit<HTMLAttributes<HTMLDivElement>, 'onError'> & {
|
|||||||
/** This event handler is called when a user right clicks on a node */
|
/** This event handler is called when a user right clicks on a node */
|
||||||
onNodeContextMenu?: NodeMouseHandler;
|
onNodeContextMenu?: NodeMouseHandler;
|
||||||
/** This event handler is called when a user starts to drag a node */
|
/** This event handler is called when a user starts to drag a node */
|
||||||
onNodeDragStart?: NodeDragHandler;
|
onNodeDragStart?: OnNodeDrag;
|
||||||
/** This event handler is called when a user drags a node */
|
/** This event handler is called when a user drags a node */
|
||||||
onNodeDrag?: NodeDragHandler;
|
onNodeDrag?: OnNodeDrag;
|
||||||
/** This event handler is called when a user stops dragging a node */
|
/** This event handler is called when a user stops dragging a node */
|
||||||
onNodeDragStop?: NodeDragHandler;
|
onNodeDragStop?: OnNodeDrag;
|
||||||
/** This event handler is called when a user clicks on an edge */
|
/** This event handler is called when a user clicks on an edge */
|
||||||
onEdgeClick?: (event: ReactMouseEvent, edge: Edge) => void;
|
onEdgeClick?: (event: ReactMouseEvent, edge: Edge) => void;
|
||||||
/** This event handler is called when a user right clicks on an edge */
|
/** This event handler is called when a user right clicks on an edge */
|
||||||
@@ -502,6 +502,6 @@ export type ReactFlowProps = Omit<HTMLAttributes<HTMLDivElement>, 'onError'> & {
|
|||||||
* @example 'system' | 'light' | 'dark'
|
* @example 'system' | 'light' | 'dark'
|
||||||
*/
|
*/
|
||||||
colorMode?: ColorMode;
|
colorMode?: ColorMode;
|
||||||
};
|
}
|
||||||
|
|
||||||
export type ReactFlowRefType = HTMLDivElement;
|
export type ReactFlowRefType = HTMLDivElement;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
FitBounds,
|
FitBounds,
|
||||||
XYPosition,
|
XYPosition,
|
||||||
NodeProps,
|
NodeProps,
|
||||||
|
OnBeforeDeleteBase,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { NodeChange, EdgeChange, Node, Edge, ReactFlowInstance, EdgeProps } from '.';
|
import type { NodeChange, EdgeChange, Node, Edge, ReactFlowInstance, EdgeProps } from '.';
|
||||||
@@ -19,7 +20,7 @@ import { ComponentType } from 'react';
|
|||||||
export type OnNodesChange<NodeType extends Node = Node> = (changes: NodeChange<NodeType>[]) => void;
|
export type OnNodesChange<NodeType extends Node = Node> = (changes: NodeChange<NodeType>[]) => void;
|
||||||
export type OnEdgesChange<EdgeType extends Edge = Edge> = (changes: EdgeChange<EdgeType>[]) => void;
|
export type OnEdgesChange<EdgeType extends Edge = Edge> = (changes: EdgeChange<EdgeType>[]) => void;
|
||||||
|
|
||||||
export type OnNodesDelete = (nodes: Node[]) => void;
|
export type OnNodesDelete<NodeType extends Node = Node> = (nodes: NodeType[]) => void;
|
||||||
export type OnEdgesDelete = (edges: Edge[]) => void;
|
export type OnEdgesDelete = (edges: Edge[]) => void;
|
||||||
export type OnDelete = (params: { nodes: Node[]; edges: Edge[] }) => void;
|
export type OnDelete = (params: { nodes: Node[]; edges: Edge[] }) => void;
|
||||||
|
|
||||||
@@ -46,16 +47,95 @@ export type OnInit<NodeType extends Node = Node, EdgeType extends Edge = Edge> =
|
|||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
export type ViewportHelperFunctions = {
|
export type ViewportHelperFunctions = {
|
||||||
|
/**
|
||||||
|
* Zooms viewport in by 1.2.
|
||||||
|
*
|
||||||
|
* @param options.duration - optional duration. If set, a transition will be applied
|
||||||
|
*/
|
||||||
zoomIn: ZoomInOut;
|
zoomIn: ZoomInOut;
|
||||||
|
/**
|
||||||
|
* Zooms viewport out by 1 / 1.2.
|
||||||
|
*
|
||||||
|
* @param options.duration - optional duration. If set, a transition will be applied
|
||||||
|
*/
|
||||||
zoomOut: ZoomInOut;
|
zoomOut: ZoomInOut;
|
||||||
|
/**
|
||||||
|
* Sets the current zoom level.
|
||||||
|
*
|
||||||
|
* @param zoomLevel - the zoom level to set
|
||||||
|
* @param options.duration - optional duration. If set, a transition will be applied
|
||||||
|
*/
|
||||||
zoomTo: ZoomTo;
|
zoomTo: ZoomTo;
|
||||||
|
/**
|
||||||
|
* Returns the current zoom level.
|
||||||
|
*
|
||||||
|
* @returns current zoom as a number
|
||||||
|
*/
|
||||||
getZoom: GetZoom;
|
getZoom: GetZoom;
|
||||||
|
/**
|
||||||
|
* Sets the current viewport.
|
||||||
|
*
|
||||||
|
* @param viewport - the viewport to set
|
||||||
|
* @param options.duration - optional duration. If set, a transition will be applied
|
||||||
|
*/
|
||||||
setViewport: SetViewport;
|
setViewport: SetViewport;
|
||||||
|
/**
|
||||||
|
* Returns the current viewport.
|
||||||
|
*
|
||||||
|
* @returns Viewport
|
||||||
|
*/
|
||||||
getViewport: GetViewport;
|
getViewport: GetViewport;
|
||||||
|
/**
|
||||||
|
* Fits the view.
|
||||||
|
*
|
||||||
|
* @param options.padding - optional padding
|
||||||
|
* @param options.includeHiddenNodes - optional includeHiddenNodes
|
||||||
|
* @param options.minZoom - optional minZoom
|
||||||
|
* @param options.maxZoom - optional maxZoom
|
||||||
|
* @param options.duration - optional duration. If set, a transition will be applied
|
||||||
|
* @param options.nodes - optional nodes to fit the view to
|
||||||
|
*/
|
||||||
fitView: FitView;
|
fitView: FitView;
|
||||||
|
/**
|
||||||
|
* Sets the center of the view to the given position.
|
||||||
|
*
|
||||||
|
* @param x - x position
|
||||||
|
* @param y - y position
|
||||||
|
* @param options.zoom - optional zoom
|
||||||
|
*/
|
||||||
setCenter: SetCenter;
|
setCenter: SetCenter;
|
||||||
|
/**
|
||||||
|
* Fits the view to the given bounds .
|
||||||
|
*
|
||||||
|
* @param bounds - the bounds ({ x: number, y: number, width: number, height: number }) to fit the view to
|
||||||
|
* @param options.padding - optional padding
|
||||||
|
*/
|
||||||
fitBounds: FitBounds;
|
fitBounds: FitBounds;
|
||||||
screenToFlowPosition: (position: XYPosition, options?: { snapToGrid: boolean }) => XYPosition;
|
/**
|
||||||
flowToScreenPosition: (position: XYPosition) => XYPosition;
|
* Converts a screen / client position to a flow position.
|
||||||
|
*
|
||||||
|
* @param clientPosition - the screen / client position. When you are working with events you can use event.clientX and event.clientY
|
||||||
|
* @param options.snapToGrid - if true, the converted position will be snapped to the grid
|
||||||
|
* @returns position as { x: number, y: number }
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const flowPosition = screenToFlowPosition({ x: event.clientX, y: event.clientY })
|
||||||
|
*/
|
||||||
|
screenToFlowPosition: (clientPosition: XYPosition, options?: { snapToGrid: boolean }) => XYPosition;
|
||||||
|
/**
|
||||||
|
* Converts a flow position to a screen / client position.
|
||||||
|
*
|
||||||
|
* @param flowPosition - the screen / client position. When you are working with events you can use event.clientX and event.clientY
|
||||||
|
* @returns position as { x: number, y: number }
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const clientPosition = flowToScreenPosition({ x: node.position.x, y: node.position.y })
|
||||||
|
*/
|
||||||
|
flowToScreenPosition: (flowPosition: XYPosition) => XYPosition;
|
||||||
viewportInitialized: boolean;
|
viewportInitialized: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type OnBeforeDelete<NodeType extends Node = Node, EdgeType extends Edge = Edge> = OnBeforeDeleteBase<
|
||||||
|
NodeType,
|
||||||
|
EdgeType
|
||||||
|
>;
|
||||||
|
|||||||
@@ -59,19 +59,112 @@ export namespace Instance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ReactFlowInstance<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
|
export type ReactFlowInstance<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
|
||||||
|
/**
|
||||||
|
* Returns nodes.
|
||||||
|
*
|
||||||
|
* @returns nodes array
|
||||||
|
*/
|
||||||
getNodes: Instance.GetNodes<NodeType>;
|
getNodes: Instance.GetNodes<NodeType>;
|
||||||
|
/**
|
||||||
|
* Sets nodes.
|
||||||
|
*
|
||||||
|
* @param payload - the nodes to set or a function that receives the current nodes and returns the new nodes
|
||||||
|
*/
|
||||||
setNodes: Instance.SetNodes<NodeType>;
|
setNodes: Instance.SetNodes<NodeType>;
|
||||||
|
/**
|
||||||
|
* Adds nodes.
|
||||||
|
*
|
||||||
|
* @param payload - the nodes to add
|
||||||
|
*/
|
||||||
addNodes: Instance.AddNodes<NodeType>;
|
addNodes: Instance.AddNodes<NodeType>;
|
||||||
|
/**
|
||||||
|
* Returns a node by id.
|
||||||
|
*
|
||||||
|
* @param id - the node id
|
||||||
|
* @returns the node or undefined if no node was found
|
||||||
|
*/
|
||||||
getNode: Instance.GetNode<NodeType>;
|
getNode: Instance.GetNode<NodeType>;
|
||||||
|
/**
|
||||||
|
* Returns edges.
|
||||||
|
*
|
||||||
|
* @returns edges array
|
||||||
|
*/
|
||||||
getEdges: Instance.GetEdges<EdgeType>;
|
getEdges: Instance.GetEdges<EdgeType>;
|
||||||
|
/**
|
||||||
|
* Sets edges.
|
||||||
|
*
|
||||||
|
* @param payload - the edges to set or a function that receives the current edges and returns the new edges
|
||||||
|
*/
|
||||||
setEdges: Instance.SetEdges<EdgeType>;
|
setEdges: Instance.SetEdges<EdgeType>;
|
||||||
|
/**
|
||||||
|
* Adds edges.
|
||||||
|
*
|
||||||
|
* @param payload - the edges to add
|
||||||
|
*/
|
||||||
addEdges: Instance.AddEdges<EdgeType>;
|
addEdges: Instance.AddEdges<EdgeType>;
|
||||||
|
/**
|
||||||
|
* Returns an edge by id.
|
||||||
|
*
|
||||||
|
* @param id - the edge id
|
||||||
|
* @returns the edge or undefined if no edge was found
|
||||||
|
*/
|
||||||
getEdge: Instance.GetEdge<EdgeType>;
|
getEdge: Instance.GetEdge<EdgeType>;
|
||||||
|
/**
|
||||||
|
* Returns the nodes, edges and the viewport as a JSON object.
|
||||||
|
*
|
||||||
|
* @returns the nodes, edges and the viewport as a JSON object
|
||||||
|
*/
|
||||||
toObject: Instance.ToObject<NodeType, EdgeType>;
|
toObject: Instance.ToObject<NodeType, EdgeType>;
|
||||||
|
/**
|
||||||
|
* Deletes nodes and edges.
|
||||||
|
*
|
||||||
|
* @param params.nodes - optional nodes array to delete
|
||||||
|
* @param params.edges - optional edges array to delete
|
||||||
|
*
|
||||||
|
* @returns a promise that resolves with the deleted nodes and edges
|
||||||
|
*/
|
||||||
deleteElements: Instance.DeleteElements;
|
deleteElements: Instance.DeleteElements;
|
||||||
|
/**
|
||||||
|
* Returns all nodes that intersect with the given node or rect.
|
||||||
|
*
|
||||||
|
* @param node - the node or rect to check for intersections
|
||||||
|
* @param partially - if true, the node is considered to be intersecting if it partially overlaps with the passed node or rect
|
||||||
|
* @param nodes - optional nodes array to check for intersections
|
||||||
|
*
|
||||||
|
* @returns an array of intersecting nodes
|
||||||
|
*/
|
||||||
getIntersectingNodes: Instance.GetIntersectingNodes<NodeType>;
|
getIntersectingNodes: Instance.GetIntersectingNodes<NodeType>;
|
||||||
|
/**
|
||||||
|
* Checks if the given node or rect intersects with the passed rect.
|
||||||
|
*
|
||||||
|
* @param node - the node or rect to check for intersections
|
||||||
|
* @param area - the rect to check for intersections
|
||||||
|
* @param partially - if true, the node is considered to be intersecting if it partially overlaps with the passed react
|
||||||
|
*
|
||||||
|
* @returns true if the node or rect intersects with the given area
|
||||||
|
*/
|
||||||
isNodeIntersecting: Instance.IsNodeIntersecting<NodeType>;
|
isNodeIntersecting: Instance.IsNodeIntersecting<NodeType>;
|
||||||
|
/**
|
||||||
|
* Updates a node.
|
||||||
|
*
|
||||||
|
* @param id - id of the node to update
|
||||||
|
* @param nodeUpdate - the node update as an object or a function that receives the current node and returns the node update
|
||||||
|
* @param options.replace - if true, the node is replaced with the node update, otherwise the changes get merged
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* updateNode('node-1', (node) => ({ position: { x: node.position.x + 10, y: node.position.y } }));
|
||||||
|
*/
|
||||||
updateNode: Instance.UpdateNode<NodeType>;
|
updateNode: Instance.UpdateNode<NodeType>;
|
||||||
|
/**
|
||||||
|
* Updates the data attribute of a node.
|
||||||
|
*
|
||||||
|
* @param id - id of the node to update
|
||||||
|
* @param dataUpdate - the data update as an object or a function that receives the current data and returns the data update
|
||||||
|
* @param options.replace - if true, the data is replaced with the data update, otherwise the changes get merged
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* updateNodeData('node-1', { label: 'A new label' });
|
||||||
|
*/
|
||||||
updateNodeData: Instance.UpdateNodeData<NodeType>;
|
updateNodeData: Instance.UpdateNodeData<NodeType>;
|
||||||
viewportInitialized: boolean;
|
viewportInitialized: boolean;
|
||||||
} & Omit<ViewportHelperFunctions, 'initialized'>;
|
} & Omit<ViewportHelperFunctions, 'initialized'>;
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import type { CoordinateExtent, NodeBase, NodeOrigin, OnError } from '@xyflow/sy
|
|||||||
|
|
||||||
import { NodeTypes } from './general';
|
import { NodeTypes } from './general';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
/**
|
/**
|
||||||
* The node data structure that gets used for the nodes prop.
|
* The node data structure that gets used for the nodes prop.
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export type Node<NodeData = any, NodeType extends string | undefined = string | undefined> = NodeBase<
|
export type Node<NodeData = any, NodeType extends string | undefined = string | undefined> = NodeBase<
|
||||||
NodeData,
|
NodeData,
|
||||||
NodeType
|
NodeType
|
||||||
@@ -18,9 +18,13 @@ export type Node<NodeData = any, NodeType extends string | undefined = string |
|
|||||||
focusable?: boolean;
|
focusable?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NodeMouseHandler = (event: ReactMouseEvent, node: Node) => void;
|
export type NodeMouseHandler<NodeType extends Node = Node> = (event: ReactMouseEvent, node: NodeType) => void;
|
||||||
export type NodeDragHandler = (event: ReactMouseEvent, node: Node, nodes: Node[]) => void;
|
export type SelectionDragHandler<NodeType extends Node = Node> = (event: ReactMouseEvent, nodes: NodeType[]) => void;
|
||||||
export type SelectionDragHandler = (event: ReactMouseEvent, nodes: Node[]) => void;
|
export type OnNodeDrag<NodeType extends Node = Node> = (
|
||||||
|
event: ReactMouseEvent,
|
||||||
|
node: NodeType,
|
||||||
|
nodes: NodeType[]
|
||||||
|
) => void;
|
||||||
|
|
||||||
export type NodeWrapperProps = {
|
export type NodeWrapperProps = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
type PanBy,
|
type PanBy,
|
||||||
type OnConnectStart,
|
type OnConnectStart,
|
||||||
type OnConnectEnd,
|
type OnConnectEnd,
|
||||||
type OnNodeDrag,
|
|
||||||
type OnSelectionDrag,
|
type OnSelectionDrag,
|
||||||
type OnMoveStart,
|
type OnMoveStart,
|
||||||
type OnMove,
|
type OnMove,
|
||||||
@@ -27,7 +26,6 @@ import {
|
|||||||
type EdgeLookup,
|
type EdgeLookup,
|
||||||
type ConnectionLookup,
|
type ConnectionLookup,
|
||||||
type NodeLookup,
|
type NodeLookup,
|
||||||
OnBeforeDelete,
|
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -43,6 +41,8 @@ import type {
|
|||||||
OnSelectionChangeFunc,
|
OnSelectionChangeFunc,
|
||||||
UnselectNodesAndEdgesParams,
|
UnselectNodesAndEdgesParams,
|
||||||
OnDelete,
|
OnDelete,
|
||||||
|
OnNodeDrag,
|
||||||
|
OnBeforeDelete,
|
||||||
} from '.';
|
} from '.';
|
||||||
|
|
||||||
export type ReactFlowStore = {
|
export type ReactFlowStore = {
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ export function handleParentExpand(updatedElements: any[], updateItem: any) {
|
|||||||
for (const [index, item] of updatedElements.entries()) {
|
for (const [index, item] of updatedElements.entries()) {
|
||||||
if (item.id === updateItem.parentNode) {
|
if (item.id === updateItem.parentNode) {
|
||||||
const parent = { ...item };
|
const parent = { ...item };
|
||||||
|
parent.computed ??= {};
|
||||||
if (!parent.computed) {
|
|
||||||
parent.computed = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const extendWidth = updateItem.position.x + updateItem.computed.width - parent.computed.width;
|
const extendWidth = updateItem.position.x + updateItem.computed.width - parent.computed.width;
|
||||||
const extendHeight = updateItem.position.y + updateItem.computed.height - parent.computed.height;
|
const extendHeight = updateItem.position.y + updateItem.computed.height - parent.computed.height;
|
||||||
@@ -106,7 +103,7 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
|||||||
const updatedElement = { ...element };
|
const updatedElement = { ...element };
|
||||||
|
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
applyChange(change, updatedElement, elements);
|
applyChange(change, updatedElement, updatedElements);
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedElements.push(updatedElement);
|
updatedElements.push(updatedElement);
|
||||||
|
|||||||
@@ -1,11 +1,29 @@
|
|||||||
# @xyflow/svelte
|
# @xyflow/svelte
|
||||||
|
|
||||||
|
## 0.0.35
|
||||||
|
|
||||||
|
## Minor changes
|
||||||
|
|
||||||
|
- add `getNode`, `getNodes`, `getEdge` and `getEdges` to `useSvelteFlow`
|
||||||
|
- add `useInitialized` / `useNNodesInitialized` hooks and `oninit` handler
|
||||||
|
|
||||||
|
## Patch changes
|
||||||
|
|
||||||
|
- selection box is not interrupted by selectionKey being let go
|
||||||
|
- Edge label has a default background and is clickable
|
||||||
|
- do not use fallback handle if a specific id is being used
|
||||||
|
- use correct id for `<Handle />` data-id attribute
|
||||||
|
- fix `getNodesBounds` and add second param for passing options
|
||||||
|
|
||||||
## 0.0.34
|
## 0.0.34
|
||||||
|
|
||||||
## Minor changes
|
## Minor changes
|
||||||
|
|
||||||
- add second option param to `screenToFlowPosition` for configuring if `snapToGrid` should be used
|
- add second option param to `screenToFlowPosition` for configuring if `snapToGrid` should be used
|
||||||
- add slot to `Controls`
|
- add slot to `Controls`
|
||||||
|
|
||||||
|
## Patch changes
|
||||||
|
|
||||||
- cleanup `ControlButton` types
|
- cleanup `ControlButton` types
|
||||||
- infer types for `getIncomers`, `getOutgoers`, `updateEdge`, `addEdge` and `getConnectedEdges` thanks @joeyballentine
|
- infer types for `getIncomers`, `getOutgoers`, `updateEdge`, `addEdge` and `getConnectedEdges` thanks @joeyballentine
|
||||||
- refactor handles: prefix with flow id for handling nested flows
|
- refactor handles: prefix with flow id for handling nested flows
|
||||||
@@ -42,7 +60,7 @@
|
|||||||
- add `onbeforedelete` handler to prevent/ manage deletions
|
- add `onbeforedelete` handler to prevent/ manage deletions
|
||||||
- TSDocs for hooks and some types
|
- TSDocs for hooks and some types
|
||||||
|
|
||||||
### Minor changes
|
### Patch changes
|
||||||
|
|
||||||
- new nodeDragThreshold default is 1
|
- new nodeDragThreshold default is 1
|
||||||
- refactor/simplify edge rendering
|
- refactor/simplify edge rendering
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import { EdgeLabelRenderer } from '$lib/components/EdgeLabelRenderer';
|
|
||||||
import type { BaseEdgeProps } from './types';
|
import type { BaseEdgeProps } from './types';
|
||||||
|
import EdgeLabel from '../EdgeLabel/EdgeLabel.svelte';
|
||||||
|
|
||||||
type $$Props = BaseEdgeProps;
|
type $$Props = BaseEdgeProps;
|
||||||
|
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
let className: $$Props['class'] = undefined;
|
let className: $$Props['class'] = undefined;
|
||||||
export { className as class };
|
export { className as class };
|
||||||
|
|
||||||
// @todo, why is interactionWidth undefined after first re-render?
|
|
||||||
let interactionWidthValue = interactionWidth === undefined ? 20 : interactionWidth;
|
let interactionWidthValue = interactionWidth === undefined ? 20 : interactionWidth;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -43,13 +42,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if label}
|
{#if label}
|
||||||
<EdgeLabelRenderer>
|
<EdgeLabel x={labelX} y={labelY} style={labelStyle}>
|
||||||
<div
|
{label}
|
||||||
class="svelte-flow__edge-label"
|
</EdgeLabel>
|
||||||
style:transform="translate(-50%, -50%) translate({labelX}px,{labelY}px)"
|
|
||||||
style={labelStyle}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</div>
|
|
||||||
</EdgeLabelRenderer>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
let _onMount: (() => void) | undefined = undefined;
|
||||||
|
export { _onMount as onMount };
|
||||||
|
|
||||||
|
let _onDestroy: (() => void) | undefined = undefined;
|
||||||
|
export { _onDestroy as onDestroy };
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
_onMount?.();
|
||||||
|
return _onDestroy;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as CallOnMount } from './CallOnMount.svelte';
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { getContext } from 'svelte';
|
||||||
|
|
||||||
|
import { EdgeLabelRenderer } from '$lib/components/EdgeLabelRenderer';
|
||||||
|
import { useHandleEdgeSelect } from '$lib/hooks/useHandleEdgeSelect';
|
||||||
|
import type { BaseEdgeProps } from '$lib/components/BaseEdge/types';
|
||||||
|
|
||||||
|
export let style: BaseEdgeProps['labelStyle'] = undefined;
|
||||||
|
export let x: BaseEdgeProps['labelX'] = undefined;
|
||||||
|
export let y: BaseEdgeProps['labelY'] = undefined;
|
||||||
|
|
||||||
|
const handleEdgeSelect = useHandleEdgeSelect();
|
||||||
|
|
||||||
|
const id = getContext<string>('svelteflow__edge_id');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<EdgeLabelRenderer>
|
||||||
|
<div
|
||||||
|
class="svelte-flow__edge-label"
|
||||||
|
style:transform="translate(-50%, -50%) translate({x}px,{y}px)"
|
||||||
|
style={'pointer-events: all;' + style}
|
||||||
|
on:click={() => {
|
||||||
|
if (id) handleEdgeSelect(id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</EdgeLabelRenderer>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as EdgeLabel } from './EdgeLabel.svelte';
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
<svelte:options immutable />
|
<svelte:options immutable />
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher, setContext } from 'svelte';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { getMarkerId } from '@xyflow/system';
|
||||||
import { errorMessages, getMarkerId } from '@xyflow/system';
|
|
||||||
|
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import { BezierEdgeInternal } from '$lib/components/edges';
|
import { BezierEdgeInternal } from '$lib/components/edges';
|
||||||
import type { EdgeLayouted, Edge } from '$lib/types';
|
import type { EdgeLayouted, Edge } from '$lib/types';
|
||||||
import { get } from 'svelte/store';
|
import { useHandleEdgeSelect } from '$lib/hooks/useHandleEdgeSelect';
|
||||||
|
|
||||||
type $$Props = EdgeLayouted;
|
type $$Props = EdgeLayouted;
|
||||||
|
|
||||||
@@ -22,7 +22,6 @@
|
|||||||
|
|
||||||
export let animated: $$Props['animated'] = false;
|
export let animated: $$Props['animated'] = false;
|
||||||
export let selected: $$Props['selected'] = false;
|
export let selected: $$Props['selected'] = false;
|
||||||
export let selectable: $$Props['selectable'] = true;
|
|
||||||
export let hidden: $$Props['hidden'] = false;
|
export let hidden: $$Props['hidden'] = false;
|
||||||
export let label: $$Props['label'] = undefined;
|
export let label: $$Props['label'] = undefined;
|
||||||
export let labelStyle: $$Props['labelStyle'] = undefined;
|
export let labelStyle: $$Props['labelStyle'] = undefined;
|
||||||
@@ -43,16 +42,9 @@
|
|||||||
let className: string = '';
|
let className: string = '';
|
||||||
export { className as class };
|
export { className as class };
|
||||||
|
|
||||||
const {
|
setContext('svelteflow__edge_id', id);
|
||||||
edges,
|
|
||||||
edgeTypes,
|
const { edgeLookup, edgeTypes, flowId } = useStore();
|
||||||
flowId,
|
|
||||||
selectionRect,
|
|
||||||
selectionRectMode,
|
|
||||||
multiselectionKeyPressed,
|
|
||||||
addSelectedEdges,
|
|
||||||
unselectNodesAndEdges
|
|
||||||
} = useStore();
|
|
||||||
const dispatch = createEventDispatcher<{
|
const dispatch = createEventDispatcher<{
|
||||||
edgeclick: { edge: Edge; event: MouseEvent | TouchEvent };
|
edgeclick: { edge: Edge; event: MouseEvent | TouchEvent };
|
||||||
edgecontextmenu: { edge: Edge; event: MouseEvent };
|
edgecontextmenu: { edge: Edge; event: MouseEvent };
|
||||||
@@ -62,30 +54,19 @@
|
|||||||
$: markerStartUrl = markerStart ? `url(#${getMarkerId(markerStart, $flowId)})` : undefined;
|
$: markerStartUrl = markerStart ? `url(#${getMarkerId(markerStart, $flowId)})` : undefined;
|
||||||
$: markerEndUrl = markerEnd ? `url(#${getMarkerId(markerEnd, $flowId)})` : undefined;
|
$: markerEndUrl = markerEnd ? `url(#${getMarkerId(markerEnd, $flowId)})` : undefined;
|
||||||
|
|
||||||
|
const handleEdgeSelect = useHandleEdgeSelect();
|
||||||
|
|
||||||
function onClick(event: MouseEvent | TouchEvent) {
|
function onClick(event: MouseEvent | TouchEvent) {
|
||||||
const edge = $edges.find((e) => e.id === id);
|
const edge = $edgeLookup.get(id);
|
||||||
|
|
||||||
if (!edge) {
|
if (edge) {
|
||||||
console.warn('012', errorMessages['error012'](id));
|
handleEdgeSelect(id);
|
||||||
return;
|
dispatch('edgeclick', { event, edge });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectable) {
|
|
||||||
selectionRect.set(null);
|
|
||||||
selectionRectMode.set(null);
|
|
||||||
|
|
||||||
if (!edge.selected) {
|
|
||||||
addSelectedEdges([id]);
|
|
||||||
} else if (edge.selected && get(multiselectionKeyPressed)) {
|
|
||||||
unselectNodesAndEdges({ nodes: [], edges: [edge] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch('edgeclick', { event, edge });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onContextMenu(event: MouseEvent) {
|
function onContextMenu(event: MouseEvent) {
|
||||||
const edge = $edges.find((e) => e.id === id);
|
const edge = $edgeLookup.get(id);
|
||||||
|
|
||||||
if (edge) {
|
if (edge) {
|
||||||
dispatch('edgecontextmenu', { event, edge });
|
dispatch('edgecontextmenu', { event, edge });
|
||||||
|
|||||||
@@ -126,11 +126,15 @@
|
|||||||
// @todo implement connectablestart, connectableend
|
// @todo implement connectablestart, connectableend
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@component
|
||||||
|
The Handle component is the part of a node that can be used to connect nodes.
|
||||||
|
-->
|
||||||
<div
|
<div
|
||||||
data-handleid={handleId}
|
data-handleid={handleId}
|
||||||
data-nodeid={nodeId}
|
data-nodeid={nodeId}
|
||||||
data-handlepos={position}
|
data-handlepos={position}
|
||||||
data-id="{flowId}-{nodeId}-{id || null}-{type}"
|
data-id="{$flowId}-{nodeId}-{id || null}-{type}"
|
||||||
class={cc([
|
class={cc([
|
||||||
'svelte-flow__handle',
|
'svelte-flow__handle',
|
||||||
`svelte-flow__handle-${position}`,
|
`svelte-flow__handle-${position}`,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { EdgeWrapper } from '$lib/components/EdgeWrapper';
|
import { EdgeWrapper } from '$lib/components/EdgeWrapper';
|
||||||
|
import { CallOnMount } from '$lib/components/CallOnMount';
|
||||||
import { MarkerDefinition } from '$lib/container/EdgeRenderer/MarkerDefinition';
|
import { MarkerDefinition } from '$lib/container/EdgeRenderer/MarkerDefinition';
|
||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import type { DefaultEdgeOptions } from '$lib/types';
|
import type { DefaultEdgeOptions } from '$lib/types';
|
||||||
@@ -8,8 +9,8 @@
|
|||||||
export let defaultEdgeOptions: DefaultEdgeOptions | undefined;
|
export let defaultEdgeOptions: DefaultEdgeOptions | undefined;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
elementsSelectable,
|
|
||||||
visibleEdges,
|
visibleEdges,
|
||||||
|
edgesInitialized,
|
||||||
edges: { setDefaultOptions }
|
edges: { setDefaultOptions }
|
||||||
} = useStore();
|
} = useStore();
|
||||||
|
|
||||||
@@ -24,12 +25,6 @@
|
|||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
{#each $visibleEdges as edge (edge.id)}
|
{#each $visibleEdges as edge (edge.id)}
|
||||||
{@const edgeType = edge.type || 'default'}
|
|
||||||
{@const selectable = !!(
|
|
||||||
edge.selectable ||
|
|
||||||
($elementsSelectable && typeof edge.selectable === 'undefined')
|
|
||||||
)}
|
|
||||||
|
|
||||||
<EdgeWrapper
|
<EdgeWrapper
|
||||||
id={edge.id}
|
id={edge.id}
|
||||||
source={edge.source}
|
source={edge.source}
|
||||||
@@ -54,11 +49,21 @@
|
|||||||
ariaLabel={edge.ariaLabel}
|
ariaLabel={edge.ariaLabel}
|
||||||
interactionWidth={edge.interactionWidth}
|
interactionWidth={edge.interactionWidth}
|
||||||
class={edge.class}
|
class={edge.class}
|
||||||
type={edgeType}
|
type={edge.type || 'default'}
|
||||||
zIndex={edge.zIndex}
|
zIndex={edge.zIndex}
|
||||||
{selectable}
|
|
||||||
on:edgeclick
|
on:edgeclick
|
||||||
on:edgecontextmenu
|
on:edgecontextmenu
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
{#if $visibleEdges.length > 0}
|
||||||
|
<CallOnMount
|
||||||
|
onMount={() => {
|
||||||
|
$edgesInitialized = true;
|
||||||
|
}}
|
||||||
|
onDestroy={() => {
|
||||||
|
$edgesInitialized = false;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -73,7 +73,8 @@
|
|||||||
let selectedNodes: Node[] = [];
|
let selectedNodes: Node[] = [];
|
||||||
|
|
||||||
$: _panOnDrag = $panActivationKeyPressed || panOnDrag;
|
$: _panOnDrag = $panActivationKeyPressed || panOnDrag;
|
||||||
$: isSelecting = $selectionKeyPressed || (selectionOnDrag && _panOnDrag !== true);
|
$: isSelecting =
|
||||||
|
$selectionKeyPressed || $selectionRect || (selectionOnDrag && _panOnDrag !== true);
|
||||||
$: hasActiveSelection = $elementsSelectable && (isSelecting || $selectionRectMode === 'user');
|
$: hasActiveSelection = $elementsSelectable && (isSelecting || $selectionRectMode === 'user');
|
||||||
|
|
||||||
function onClick(event: MouseEvent | TouchEvent) {
|
function onClick(event: MouseEvent | TouchEvent) {
|
||||||
|
|||||||
@@ -77,6 +77,7 @@
|
|||||||
export let onconnectstart: $$Props['onconnectstart'] = undefined;
|
export let onconnectstart: $$Props['onconnectstart'] = undefined;
|
||||||
export let onconnectend: $$Props['onconnectend'] = undefined;
|
export let onconnectend: $$Props['onconnectend'] = undefined;
|
||||||
export let onbeforedelete: $$Props['onbeforedelete'] = undefined;
|
export let onbeforedelete: $$Props['onbeforedelete'] = undefined;
|
||||||
|
export let oninit: $$Props['oninit'] = undefined;
|
||||||
|
|
||||||
export let defaultMarkerColor = '#b1b1b7';
|
export let defaultMarkerColor = '#b1b1b7';
|
||||||
|
|
||||||
@@ -130,6 +131,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call oninit once when flow is intialized
|
||||||
|
const { initialized } = store;
|
||||||
|
let onInitCalled = false;
|
||||||
|
$: {
|
||||||
|
if (!onInitCalled && $initialized) {
|
||||||
|
oninit?.();
|
||||||
|
onInitCalled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// this updates the store for simple changes
|
// this updates the store for simple changes
|
||||||
// where the prop names equals the store name
|
// where the prop names equals the store name
|
||||||
$: {
|
$: {
|
||||||
|
|||||||
@@ -333,4 +333,6 @@ export type SvelteFlowProps = DOMAttributes<HTMLDivElement> & {
|
|||||||
onconnectstart?: OnConnectStart;
|
onconnectstart?: OnConnectStart;
|
||||||
/** When a user stops dragging a connection line, this event gets fired. */
|
/** When a user stops dragging a connection line, this event gets fired. */
|
||||||
onconnectend?: OnConnectEnd;
|
onconnectend?: OnConnectEnd;
|
||||||
|
/** This handler gets called when the flow is finished initializing */
|
||||||
|
oninit?: () => void;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import { useStore } from '$lib/store';
|
import { useStore } from '$lib/store';
|
||||||
import zoom from '$lib/actions/zoom';
|
import zoom from '$lib/actions/zoom';
|
||||||
import type { ZoomProps } from './types';
|
import type { ZoomProps } from './types';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
type $$Props = ZoomProps;
|
type $$Props = ZoomProps;
|
||||||
|
|
||||||
@@ -29,12 +30,17 @@
|
|||||||
translateExtent,
|
translateExtent,
|
||||||
lib,
|
lib,
|
||||||
panActivationKeyPressed,
|
panActivationKeyPressed,
|
||||||
zoomActivationKeyPressed
|
zoomActivationKeyPressed,
|
||||||
|
viewportInitialized
|
||||||
} = useStore();
|
} = useStore();
|
||||||
|
|
||||||
$: viewPort = initialViewport || { x: 0, y: 0, zoom: 1 };
|
$: viewPort = initialViewport || { x: 0, y: 0, zoom: 1 };
|
||||||
$: _panOnDrag = $panActivationKeyPressed || panOnDrag;
|
$: _panOnDrag = $panActivationKeyPressed || panOnDrag;
|
||||||
$: _panOnScroll = $panActivationKeyPressed || panOnScroll;
|
$: _panOnScroll = $panActivationKeyPressed || panOnScroll;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
$viewportInitialized = true;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { get } from 'svelte/store';
|
||||||
|
import { errorMessages } from '@xyflow/system';
|
||||||
|
|
||||||
|
import { useStore } from '$lib/store';
|
||||||
|
|
||||||
|
export function useHandleEdgeSelect() {
|
||||||
|
const {
|
||||||
|
edgeLookup,
|
||||||
|
selectionRect,
|
||||||
|
selectionRectMode,
|
||||||
|
multiselectionKeyPressed,
|
||||||
|
addSelectedEdges,
|
||||||
|
unselectNodesAndEdges,
|
||||||
|
elementsSelectable
|
||||||
|
} = useStore();
|
||||||
|
|
||||||
|
return (id: string) => {
|
||||||
|
const edge = get(edgeLookup).get(id);
|
||||||
|
|
||||||
|
if (!edge) {
|
||||||
|
console.warn('012', errorMessages['error012'](id));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectable =
|
||||||
|
edge.selectable || (get(elementsSelectable) && typeof edge.selectable === 'undefined');
|
||||||
|
|
||||||
|
if (selectable) {
|
||||||
|
selectionRect.set(null);
|
||||||
|
selectionRectMode.set(null);
|
||||||
|
|
||||||
|
if (!edge.selected) {
|
||||||
|
addSelectedEdges([id]);
|
||||||
|
} else if (edge.selected && get(multiselectionKeyPressed)) {
|
||||||
|
unselectNodesAndEdges({ nodes: [], edges: [edge] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { useStore } from '$lib/store';
|
||||||
|
import type { Readable } from 'svelte/store';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for seeing if nodes are initialized
|
||||||
|
* @returns - nodesInitialized Writable
|
||||||
|
*/
|
||||||
|
export function useNodesInitialized() {
|
||||||
|
const { nodesInitialized } = useStore();
|
||||||
|
return {
|
||||||
|
subscribe: nodesInitialized.subscribe
|
||||||
|
} as Readable<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for seeing if the flow is initialized
|
||||||
|
* @returns - initialized Writable
|
||||||
|
*/
|
||||||
|
export function useInitialized() {
|
||||||
|
const { initialized } = useStore();
|
||||||
|
return {
|
||||||
|
subscribe: initialized.subscribe
|
||||||
|
} as Readable<boolean>;
|
||||||
|
}
|
||||||
@@ -24,28 +24,136 @@ import { isNode } from '$lib/utils';
|
|||||||
* Hook for accessing the ReactFlow instance.
|
* Hook for accessing the ReactFlow instance.
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
|
*
|
||||||
* @returns helper functions
|
* @returns helper functions
|
||||||
*/
|
*/
|
||||||
export function useSvelteFlow(): {
|
export function useSvelteFlow(): {
|
||||||
|
/**
|
||||||
|
* Zooms viewport in by 1.2.
|
||||||
|
*
|
||||||
|
* @param options.duration - optional duration. If set, a transition will be applied
|
||||||
|
*/
|
||||||
zoomIn: ZoomInOut;
|
zoomIn: ZoomInOut;
|
||||||
|
/**
|
||||||
|
* Zooms viewport out by 1 / 1.2.
|
||||||
|
*
|
||||||
|
* @param options.duration - optional duration. If set, a transition will be applied
|
||||||
|
*/
|
||||||
zoomOut: ZoomInOut;
|
zoomOut: ZoomInOut;
|
||||||
|
/**
|
||||||
|
* Returns a node by id.
|
||||||
|
*
|
||||||
|
* @param id - the node id
|
||||||
|
* @returns the node or undefined if no node was found
|
||||||
|
*/
|
||||||
|
getNode: (id: string) => Node | undefined;
|
||||||
|
/**
|
||||||
|
* Returns nodes.
|
||||||
|
*
|
||||||
|
* @returns nodes array
|
||||||
|
*/
|
||||||
|
getNodes: (ids?: string[]) => Node[];
|
||||||
|
/**
|
||||||
|
* Returns an edge by id.
|
||||||
|
*
|
||||||
|
* @param id - the edge id
|
||||||
|
* @returns the edge or undefined if no edge was found
|
||||||
|
*/
|
||||||
|
getEdge: (id: string) => Edge | undefined;
|
||||||
|
/**
|
||||||
|
* Returns edges.
|
||||||
|
*
|
||||||
|
* @returns edges array
|
||||||
|
*/
|
||||||
|
getEdges: (ids?: string[]) => Edge[];
|
||||||
|
/**
|
||||||
|
* Sets the current zoom level.
|
||||||
|
*
|
||||||
|
* @param zoomLevel - the zoom level to set
|
||||||
|
* @param options.duration - optional duration. If set, a transition will be applied
|
||||||
|
*/
|
||||||
setZoom: (zoomLevel: number, options?: ViewportHelperFunctionOptions) => void;
|
setZoom: (zoomLevel: number, options?: ViewportHelperFunctionOptions) => void;
|
||||||
|
/**
|
||||||
|
* Returns the current zoom level.
|
||||||
|
*
|
||||||
|
* @returns current zoom as a number
|
||||||
|
*/
|
||||||
getZoom: () => number;
|
getZoom: () => number;
|
||||||
|
/**
|
||||||
|
* Sets the center of the view to the given position.
|
||||||
|
*
|
||||||
|
* @param x - x position
|
||||||
|
* @param y - y position
|
||||||
|
* @param options.zoom - optional zoom
|
||||||
|
*/
|
||||||
setCenter: (x: number, y: number, options?: SetCenterOptions) => void;
|
setCenter: (x: number, y: number, options?: SetCenterOptions) => void;
|
||||||
|
/**
|
||||||
|
* Sets the current viewport.
|
||||||
|
*
|
||||||
|
* @param viewport - the viewport to set
|
||||||
|
* @param options.duration - optional duration. If set, a transition will be applied
|
||||||
|
*/
|
||||||
setViewport: (viewport: Viewport, options?: ViewportHelperFunctionOptions) => void;
|
setViewport: (viewport: Viewport, options?: ViewportHelperFunctionOptions) => void;
|
||||||
|
/**
|
||||||
|
* Returns the current viewport.
|
||||||
|
*
|
||||||
|
* @returns Viewport
|
||||||
|
*/
|
||||||
getViewport: () => Viewport;
|
getViewport: () => Viewport;
|
||||||
|
/**
|
||||||
|
* Fits the view.
|
||||||
|
*
|
||||||
|
* @param options.padding - optional padding
|
||||||
|
* @param options.includeHiddenNodes - optional includeHiddenNodes
|
||||||
|
* @param options.minZoom - optional minZoom
|
||||||
|
* @param options.maxZoom - optional maxZoom
|
||||||
|
* @param options.duration - optional duration. If set, a transition will be applied
|
||||||
|
* @param options.nodes - optional nodes to fit the view to
|
||||||
|
*/
|
||||||
fitView: (options?: FitViewOptions) => void;
|
fitView: (options?: FitViewOptions) => void;
|
||||||
|
/**
|
||||||
|
* Returns all nodes that intersect with the given node or rect.
|
||||||
|
*
|
||||||
|
* @param node - the node or rect to check for intersections
|
||||||
|
* @param partially - if true, the node is considered to be intersecting if it partially overlaps with the passed node or rect
|
||||||
|
* @param nodes - optional nodes array to check for intersections
|
||||||
|
*
|
||||||
|
* @returns an array of intersecting nodes
|
||||||
|
*/
|
||||||
getIntersectingNodes: (
|
getIntersectingNodes: (
|
||||||
nodeOrRect: Node | { id: Node['id'] } | Rect,
|
nodeOrRect: Node | { id: Node['id'] } | Rect,
|
||||||
partially?: boolean,
|
partially?: boolean,
|
||||||
nodesToIntersect?: Node[]
|
nodesToIntersect?: Node[]
|
||||||
) => Node[];
|
) => Node[];
|
||||||
|
/**
|
||||||
|
* Checks if the given node or rect intersects with the passed rect.
|
||||||
|
*
|
||||||
|
* @param node - the node or rect to check for intersections
|
||||||
|
* @param area - the rect to check for intersections
|
||||||
|
* @param partially - if true, the node is considered to be intersecting if it partially overlaps with the passed react
|
||||||
|
*
|
||||||
|
* @returns true if the node or rect intersects with the given area
|
||||||
|
*/
|
||||||
isNodeIntersecting: (
|
isNodeIntersecting: (
|
||||||
nodeOrRect: Node | { id: Node['id'] } | Rect,
|
nodeOrRect: Node | { id: Node['id'] } | Rect,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
partially?: boolean
|
partially?: boolean
|
||||||
) => boolean;
|
) => boolean;
|
||||||
|
/**
|
||||||
|
* Fits the view to the given bounds .
|
||||||
|
*
|
||||||
|
* @param bounds - the bounds ({ x: number, y: number, width: number, height: number }) to fit the view to
|
||||||
|
* @param options.padding - optional padding
|
||||||
|
*/
|
||||||
fitBounds: (bounds: Rect, options?: FitBoundsOptions) => void;
|
fitBounds: (bounds: Rect, options?: FitBoundsOptions) => void;
|
||||||
|
/**
|
||||||
|
* Deletes nodes and edges.
|
||||||
|
*
|
||||||
|
* @param params.nodes - optional nodes array to delete
|
||||||
|
* @param params.edges - optional edges array to delete
|
||||||
|
*
|
||||||
|
* @returns a promise that resolves with the deleted nodes and edges
|
||||||
|
*/
|
||||||
deleteElements: ({
|
deleteElements: ({
|
||||||
nodes,
|
nodes,
|
||||||
edges
|
edges
|
||||||
@@ -53,19 +161,66 @@ export function useSvelteFlow(): {
|
|||||||
nodes?: (Node | { id: Node['id'] })[];
|
nodes?: (Node | { id: Node['id'] })[];
|
||||||
edges?: (Edge | { id: Edge['id'] })[];
|
edges?: (Edge | { id: Edge['id'] })[];
|
||||||
}) => Promise<{ deletedNodes: Node[]; deletedEdges: Edge[] }>;
|
}) => Promise<{ deletedNodes: Node[]; deletedEdges: Edge[] }>;
|
||||||
screenToFlowPosition: (position: XYPosition, options?: { snapToGrid: boolean }) => XYPosition;
|
/**
|
||||||
flowToScreenPosition: (position: XYPosition) => XYPosition;
|
* Converts a screen / client position to a flow position.
|
||||||
|
*
|
||||||
|
* @param clientPosition - the screen / client position. When you are working with events you can use event.clientX and event.clientY
|
||||||
|
* @param options.snapToGrid - if true, the converted position will be snapped to the grid
|
||||||
|
* @returns position as { x: number, y: number }
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const flowPosition = screenToFlowPosition({ x: event.clientX, y: event.clientY })
|
||||||
|
*/
|
||||||
|
screenToFlowPosition: (
|
||||||
|
clientPosition: XYPosition,
|
||||||
|
options?: { snapToGrid: boolean }
|
||||||
|
) => XYPosition;
|
||||||
|
/**
|
||||||
|
* Converts a flow position to a screen / client position.
|
||||||
|
*
|
||||||
|
* @param flowPosition - the screen / client position. When you are working with events you can use event.clientX and event.clientY
|
||||||
|
* @returns position as { x: number, y: number }
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const clientPosition = flowToScreenPosition({ x: node.position.x, y: node.position.y })
|
||||||
|
*/
|
||||||
|
flowToScreenPosition: (flowPosition: XYPosition) => XYPosition;
|
||||||
viewport: Writable<Viewport>;
|
viewport: Writable<Viewport>;
|
||||||
|
/**
|
||||||
|
* Updates a node.
|
||||||
|
*
|
||||||
|
* @param id - id of the node to update
|
||||||
|
* @param nodeUpdate - the node update as an object or a function that receives the current node and returns the node update
|
||||||
|
* @param options.replace - if true, the node is replaced with the node update, otherwise the changes get merged
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* updateNode('node-1', (node) => ({ position: { x: node.position.x + 10, y: node.position.y } }));
|
||||||
|
*/
|
||||||
updateNode: (
|
updateNode: (
|
||||||
id: string,
|
id: string,
|
||||||
nodeUpdate: Partial<Node> | ((node: Node) => Partial<Node>),
|
nodeUpdate: Partial<Node> | ((node: Node) => Partial<Node>),
|
||||||
options?: { replace: boolean }
|
options?: { replace: boolean }
|
||||||
) => void;
|
) => void;
|
||||||
|
/**
|
||||||
|
* Updates the data attribute of a node.
|
||||||
|
*
|
||||||
|
* @param id - id of the node to update
|
||||||
|
* @param dataUpdate - the data update as an object or a function that receives the current data and returns the data update
|
||||||
|
* @param options.replace - if true, the data is replaced with the data update, otherwise the changes get merged
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* updateNodeData('node-1', { label: 'A new label' });
|
||||||
|
*/
|
||||||
updateNodeData: (
|
updateNodeData: (
|
||||||
id: string,
|
id: string,
|
||||||
dataUpdate: object | ((node: Node) => object),
|
dataUpdate: object | ((node: Node) => object),
|
||||||
options?: { replace: boolean }
|
options?: { replace: boolean }
|
||||||
) => void;
|
) => void;
|
||||||
|
/**
|
||||||
|
* Returns the nodes, edges and the viewport as a JSON object.
|
||||||
|
*
|
||||||
|
* @returns the nodes, edges and the viewport as a JSON object
|
||||||
|
*/
|
||||||
toObject: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport };
|
toObject: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport };
|
||||||
} {
|
} {
|
||||||
const {
|
const {
|
||||||
@@ -82,7 +237,9 @@ export function useSvelteFlow(): {
|
|||||||
panZoom,
|
panZoom,
|
||||||
nodes,
|
nodes,
|
||||||
edges,
|
edges,
|
||||||
domNode
|
domNode,
|
||||||
|
nodeLookup,
|
||||||
|
edgeLookup
|
||||||
} = useStore();
|
} = useStore();
|
||||||
|
|
||||||
const getNodeRect = (
|
const getNodeRect = (
|
||||||
@@ -121,6 +278,10 @@ export function useSvelteFlow(): {
|
|||||||
return {
|
return {
|
||||||
zoomIn,
|
zoomIn,
|
||||||
zoomOut,
|
zoomOut,
|
||||||
|
getNode: (id) => get(nodeLookup).get(id),
|
||||||
|
getNodes: (ids) => (ids === undefined ? get(nodes) : getElements(get(nodeLookup), ids)),
|
||||||
|
getEdge: (id) => get(edgeLookup).get(id),
|
||||||
|
getEdges: (ids) => (ids === undefined ? get(edges) : getElements(get(edgeLookup), ids)),
|
||||||
setZoom: (zoomLevel, options) => {
|
setZoom: (zoomLevel, options) => {
|
||||||
get(panZoom)?.scaleTo(zoomLevel, { duration: options?.duration });
|
get(panZoom)?.scaleTo(zoomLevel, { duration: options?.duration });
|
||||||
},
|
},
|
||||||
@@ -253,6 +414,11 @@ export function useSvelteFlow(): {
|
|||||||
_snapGrid || [1, 1]
|
_snapGrid || [1, 1]
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param position
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
flowToScreenPosition: (position: XYPosition) => {
|
flowToScreenPosition: (position: XYPosition) => {
|
||||||
const _domNode = get(domNode);
|
const _domNode = get(domNode);
|
||||||
|
|
||||||
@@ -269,6 +435,7 @@ export function useSvelteFlow(): {
|
|||||||
y: rendererPosition.y + domY
|
y: rendererPosition.y + domY
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
toObject: () => {
|
toObject: () => {
|
||||||
return {
|
return {
|
||||||
nodes: get(nodes).map((node) => ({
|
nodes: get(nodes).map((node) => ({
|
||||||
@@ -295,3 +462,17 @@ export function useSvelteFlow(): {
|
|||||||
viewport
|
viewport
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getElements<EdgeOrNode>(lookup: Map<string, EdgeOrNode>, ids: string[]) {
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
const element = lookup.get(id);
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
result.push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export * from '$lib/hooks/useConnection';
|
|||||||
export * from '$lib/hooks/useNodesEdges';
|
export * from '$lib/hooks/useNodesEdges';
|
||||||
export * from '$lib/hooks/useHandleConnections';
|
export * from '$lib/hooks/useHandleConnections';
|
||||||
export * from '$lib/hooks/useNodesData';
|
export * from '$lib/hooks/useNodesData';
|
||||||
|
export { useInitialized, useNodesInitialized } from '$lib/hooks/useInitialized';
|
||||||
|
|
||||||
// types
|
// types
|
||||||
export type {
|
export type {
|
||||||
@@ -79,7 +80,6 @@ export {
|
|||||||
type OnError,
|
type OnError,
|
||||||
type NodeProps,
|
type NodeProps,
|
||||||
type NodeOrigin,
|
type NodeOrigin,
|
||||||
type OnNodeDrag,
|
|
||||||
type OnSelectionDrag,
|
type OnSelectionDrag,
|
||||||
Position,
|
Position,
|
||||||
type XYPosition,
|
type XYPosition,
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
height: toolbarNode.computed?.height ?? toolbarNode.height ?? 0
|
height: toolbarNode.computed?.height ?? toolbarNode.height ?? 0
|
||||||
};
|
};
|
||||||
} else if (toolbarNodes.length > 1) {
|
} else if (toolbarNodes.length > 1) {
|
||||||
nodeRect = getNodesBounds(toolbarNodes, $nodeOrigin);
|
nodeRect = getNodesBounds(toolbarNodes, { nodeOrigin: $nodeOrigin });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeRect) {
|
if (nodeRect) {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
type XYPosition,
|
type XYPosition,
|
||||||
type CoordinateExtent,
|
type CoordinateExtent,
|
||||||
type UpdateConnection,
|
type UpdateConnection,
|
||||||
type NodeBase,
|
|
||||||
type NodeDragItem,
|
type NodeDragItem,
|
||||||
errorMessages
|
errorMessages
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
@@ -67,7 +66,7 @@ export function createStore({
|
|||||||
const updateNodePositions: UpdateNodePositions = (nodeDragItems, dragging = false) => {
|
const updateNodePositions: UpdateNodePositions = (nodeDragItems, dragging = false) => {
|
||||||
store.nodes.update((nds) => {
|
store.nodes.update((nds) => {
|
||||||
return nds.map((node) => {
|
return nds.map((node) => {
|
||||||
const nodeDragItem = (nodeDragItems as Array<NodeBase | NodeDragItem>).find(
|
const nodeDragItem = (nodeDragItems as Array<Node | NodeDragItem>).find(
|
||||||
(ndi) => ndi.id === node.id
|
(ndi) => ndi.id === node.id
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -112,6 +111,10 @@ export function createStore({
|
|||||||
}
|
}
|
||||||
|
|
||||||
store.nodes.set(nextNodes);
|
store.nodes.set(nextNodes);
|
||||||
|
|
||||||
|
if (!get(store.nodesInitialized)) {
|
||||||
|
store.nodesInitialized.set(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fitView(nodes: Node[], options?: FitViewOptions) {
|
function fitView(nodes: Node[], options?: FitViewOptions) {
|
||||||
@@ -354,6 +357,29 @@ export function createStore({
|
|||||||
[store.edges, store.defaultMarkerColor, store.flowId],
|
[store.edges, store.defaultMarkerColor, store.flowId],
|
||||||
([edges, defaultColor, id]) => createMarkerIds(edges, { defaultColor, id })
|
([edges, defaultColor, id]) => createMarkerIds(edges, { defaultColor, id })
|
||||||
),
|
),
|
||||||
|
initialized: (() => {
|
||||||
|
let initialized = false;
|
||||||
|
const initialNodesLength = get(store.nodes).length;
|
||||||
|
const initialEdgesLength = get(store.edges).length;
|
||||||
|
return derived(
|
||||||
|
[store.nodesInitialized, store.edgesInitialized, store.viewportInitialized],
|
||||||
|
([nodesInitialized, edgesInitialized, viewportInitialized]) => {
|
||||||
|
// If it was already initialized, return true from then on
|
||||||
|
if (initialized) return initialized;
|
||||||
|
|
||||||
|
// if it hasn't been initialised check if it's now
|
||||||
|
if (initialNodesLength === 0) {
|
||||||
|
initialized = viewportInitialized;
|
||||||
|
} else if (initialEdgesLength === 0) {
|
||||||
|
initialized = viewportInitialized && nodesInitialized;
|
||||||
|
} else {
|
||||||
|
initialized = viewportInitialized && nodesInitialized && edgesInitialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialized;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})(),
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
syncNodeStores: (nodes) => syncNodeStores(store.nodes, nodes),
|
syncNodeStores: (nodes) => syncNodeStores(store.nodes, nodes),
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import {
|
|||||||
type OnConnectStart,
|
type OnConnectStart,
|
||||||
type OnConnectEnd,
|
type OnConnectEnd,
|
||||||
type NodeLookup,
|
type NodeLookup,
|
||||||
type OnBeforeDelete,
|
|
||||||
type EdgeLookup
|
type EdgeLookup
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
@@ -47,7 +46,8 @@ import type {
|
|||||||
Edge,
|
Edge,
|
||||||
FitViewOptions,
|
FitViewOptions,
|
||||||
OnDelete,
|
OnDelete,
|
||||||
OnEdgeCreate
|
OnEdgeCreate,
|
||||||
|
OnBeforeDelete
|
||||||
} from '$lib/types';
|
} from '$lib/types';
|
||||||
import { createNodesStore, createEdgesStore } from './utils';
|
import { createNodesStore, createEdgesStore } from './utils';
|
||||||
import { initConnectionProps, type ConnectionProps } from './derived-connection-props';
|
import { initConnectionProps, type ConnectionProps } from './derived-connection-props';
|
||||||
@@ -92,7 +92,8 @@ export const getInitialStore = ({
|
|||||||
|
|
||||||
if (fitView && width && height) {
|
if (fitView && width && height) {
|
||||||
const nodesWithDimensions = nextNodes.filter((node) => node.width && node.height);
|
const nodesWithDimensions = nextNodes.filter((node) => node.width && node.height);
|
||||||
const bounds = getNodesBounds(nodesWithDimensions, [0, 0]);
|
// @todo users nodeOrigin should be used here
|
||||||
|
const bounds = getNodesBounds(nodesWithDimensions, { nodeOrigin: [0, 0] });
|
||||||
viewport = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1);
|
viewport = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +153,10 @@ export const getInitialStore = ({
|
|||||||
onconnect: writable<OnConnect>(undefined),
|
onconnect: writable<OnConnect>(undefined),
|
||||||
onconnectstart: writable<OnConnectStart>(undefined),
|
onconnectstart: writable<OnConnectStart>(undefined),
|
||||||
onconnectend: writable<OnConnectEnd>(undefined),
|
onconnectend: writable<OnConnectEnd>(undefined),
|
||||||
onbeforedelete: writable<OnBeforeDelete>(undefined)
|
onbeforedelete: writable<OnBeforeDelete>(undefined),
|
||||||
|
nodesInitialized: writable<boolean>(false),
|
||||||
|
edgesInitialized: writable<boolean>(false),
|
||||||
|
viewportInitialized: writable<boolean>(false),
|
||||||
|
initialized: readable<boolean>(false)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import type {
|
|||||||
Position,
|
Position,
|
||||||
XYPosition,
|
XYPosition,
|
||||||
ConnectingHandle,
|
ConnectingHandle,
|
||||||
Connection
|
Connection,
|
||||||
|
OnBeforeDeleteBase
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import type { Node } from './nodes';
|
import type { Node } from './nodes';
|
||||||
@@ -52,3 +53,7 @@ export type FitViewOptions = FitViewOptionsBase<Node>;
|
|||||||
|
|
||||||
export type OnDelete = (params: { nodes: Node[]; edges: Edge[] }) => void;
|
export type OnDelete = (params: { nodes: Node[]; edges: Edge[] }) => void;
|
||||||
export type OnEdgeCreate = (connection: Connection) => Edge | Connection | void;
|
export type OnEdgeCreate = (connection: Connection) => Edge | Connection | void;
|
||||||
|
export type OnBeforeDelete<
|
||||||
|
NodeType extends Node = Node,
|
||||||
|
EdgeType extends Edge = Edge
|
||||||
|
> = OnBeforeDeleteBase<NodeType, EdgeType>;
|
||||||
|
|||||||
@@ -2,16 +2,7 @@
|
|||||||
@import '../../../system/src/styles/init.css';
|
@import '../../../system/src/styles/init.css';
|
||||||
@import '../../../system/src/styles/base.css';
|
@import '../../../system/src/styles/base.css';
|
||||||
|
|
||||||
.svelte-flow {
|
|
||||||
--edge-label-color-default: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.svelte-flow.dark {
|
|
||||||
--edge-label-color-default: #f8f8f8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.svelte-flow__edge-label {
|
.svelte-flow__edge-label {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
color: var(--edge-label-color, var(--edge-label-color-default));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,19 +3,14 @@
|
|||||||
@import '../../../system/src/styles/style.css';
|
@import '../../../system/src/styles/style.css';
|
||||||
@import '../../../system/src/styles/node-resizer.css';
|
@import '../../../system/src/styles/node-resizer.css';
|
||||||
|
|
||||||
.svelte-flow {
|
|
||||||
--edge-label-color-default: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.svelte-flow.dark {
|
|
||||||
--edge-label-color-default: #f8f8f8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.svelte-flow__edge-label {
|
.svelte-flow__edge-label {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
padding: 2px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
color: var(--edge-label-color, var(--edge-label-color-default));
|
cursor: pointer;
|
||||||
|
color: var(--xy-edge-label-color, var(--xy-edge-label-color-default));
|
||||||
|
background: var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default));
|
||||||
}
|
}
|
||||||
|
|
||||||
.svelte-flow__nodes {
|
.svelte-flow__nodes {
|
||||||
|
|||||||
@@ -139,8 +139,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.selected .xy-flow__edge-path,
|
&.selected .xy-flow__edge-path,
|
||||||
&:focus .xy-flow__edge-path,
|
&.selectable:focus .xy-flow__edge-path,
|
||||||
&:focus-visible .xy-flow__edge-path {
|
&.selectable:focus-visible .xy-flow__edge-path {
|
||||||
stroke: var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default));
|
stroke: var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
--xy-controls-button-color-hover-default: inherit;
|
--xy-controls-button-color-hover-default: inherit;
|
||||||
--xy-controls-button-border-color-default: #eee;
|
--xy-controls-button-border-color-default: #eee;
|
||||||
--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08);
|
--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08);
|
||||||
|
|
||||||
|
--xy-edge-label-background-color-default: #ffffff;
|
||||||
|
--xy-edge-label-color-default: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.xy-flow.dark {
|
.xy-flow.dark {
|
||||||
@@ -41,6 +44,9 @@
|
|||||||
--xy-controls-button-color-hover-default: #fff;
|
--xy-controls-button-color-hover-default: #fff;
|
||||||
--xy-controls-button-border-color-default: #5b5b5b;
|
--xy-controls-button-border-color-default: #5b5b5b;
|
||||||
--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08);
|
--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08);
|
||||||
|
|
||||||
|
--xy-edge-label-background-color-default: #141414;
|
||||||
|
--xy-edge-label-color-default: #f8f8f8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.xy-flow__edge {
|
.xy-flow__edge {
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export type ColorMode = ColorModeClass | 'system';
|
|||||||
|
|
||||||
export type ConnectionLookup = Map<string, Map<string, Connection>>;
|
export type ConnectionLookup = Map<string, Map<string, Connection>>;
|
||||||
|
|
||||||
export type OnBeforeDelete = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>({
|
export type OnBeforeDeleteBase<NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase> = ({
|
||||||
nodes,
|
nodes,
|
||||||
edges,
|
edges,
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@@ -94,9 +94,9 @@ export type NodeProps<T = any> = {
|
|||||||
positionAbsoluteY: number;
|
positionAbsoluteY: number;
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
dragging: boolean;
|
dragging: NodeBase['dragging'];
|
||||||
targetPosition?: Position;
|
sourcePosition?: NodeBase['sourcePosition'];
|
||||||
sourcePosition?: Position;
|
targetPosition?: NodeBase['targetPosition'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NodeHandleBounds = {
|
export type NodeHandleBounds = {
|
||||||
@@ -134,8 +134,6 @@ export type NodeDragItem = {
|
|||||||
|
|
||||||
export type NodeOrigin = [number, number];
|
export type NodeOrigin = [number, number];
|
||||||
|
|
||||||
export type OnNodeDrag = (event: MouseEvent, node: NodeBase, nodes: NodeBase[]) => void;
|
|
||||||
|
|
||||||
export type OnSelectionDrag = (event: MouseEvent, nodes: NodeBase[]) => void;
|
export type OnSelectionDrag = (event: MouseEvent, nodes: NodeBase[]) => void;
|
||||||
|
|
||||||
export type NodeHandle = Optional<HandleElement, 'width' | 'height'>;
|
export type NodeHandle = Optional<HandleElement, 'width' | 'height'>;
|
||||||
|
|||||||
@@ -114,11 +114,6 @@ function getHandle(bounds: HandleElement[], handleId?: string | null): HandleEle
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bounds.length === 1 || !handleId) {
|
// if no handleId is given, we use the first handle, otherwise we check for the id
|
||||||
return bounds[0];
|
return (!handleId ? bounds[0] : bounds.find((d) => d.id === handleId)) || null;
|
||||||
} else if (handleId) {
|
|
||||||
return bounds.find((d) => d.id === handleId) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,3 +198,7 @@ export const getViewportForBounds = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const isMacOs = () => typeof navigator !== 'undefined' && navigator?.userAgent?.indexOf('Mac') >= 0;
|
export const isMacOs = () => typeof navigator !== 'undefined' && navigator?.userAgent?.indexOf('Mac') >= 0;
|
||||||
|
|
||||||
|
export function isCoordinateExtent(extent?: CoordinateExtent | 'parent'): extent is CoordinateExtent {
|
||||||
|
return extent !== undefined && extent !== 'parent';
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import {
|
|||||||
clampPosition,
|
clampPosition,
|
||||||
getBoundsOfBoxes,
|
getBoundsOfBoxes,
|
||||||
getOverlappingArea,
|
getOverlappingArea,
|
||||||
isNumeric,
|
|
||||||
rectToBox,
|
rectToBox,
|
||||||
nodeToRect,
|
nodeToRect,
|
||||||
pointToRendererPoint,
|
pointToRendererPoint,
|
||||||
getViewportForBounds,
|
getViewportForBounds,
|
||||||
|
isCoordinateExtent,
|
||||||
} from './general';
|
} from './general';
|
||||||
import {
|
import {
|
||||||
type Transform,
|
type Transform,
|
||||||
@@ -19,10 +19,10 @@ import {
|
|||||||
type EdgeBase,
|
type EdgeBase,
|
||||||
type FitViewParamsBase,
|
type FitViewParamsBase,
|
||||||
type FitViewOptionsBase,
|
type FitViewOptionsBase,
|
||||||
NodeDragItem,
|
|
||||||
CoordinateExtent,
|
CoordinateExtent,
|
||||||
OnError,
|
OnError,
|
||||||
OnBeforeDelete,
|
OnBeforeDeleteBase,
|
||||||
|
NodeLookup,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { errorMessages } from '../constants';
|
import { errorMessages } from '../constants';
|
||||||
|
|
||||||
@@ -102,11 +102,13 @@ export const getIncomers = <NodeType extends NodeBase = NodeBase, EdgeType exten
|
|||||||
export const getNodePositionWithOrigin = (
|
export const getNodePositionWithOrigin = (
|
||||||
node: NodeBase | undefined,
|
node: NodeBase | undefined,
|
||||||
nodeOrigin: NodeOrigin = [0, 0]
|
nodeOrigin: NodeOrigin = [0, 0]
|
||||||
): XYPosition & { positionAbsolute: XYPosition } => {
|
): { position: XYPosition; positionAbsolute: XYPosition } => {
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return {
|
return {
|
||||||
x: 0,
|
position: {
|
||||||
y: 0,
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
positionAbsolute: {
|
positionAbsolute: {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
@@ -123,7 +125,7 @@ export const getNodePositionWithOrigin = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...position,
|
position,
|
||||||
positionAbsolute: node.computed?.positionAbsolute
|
positionAbsolute: node.computed?.positionAbsolute
|
||||||
? {
|
? {
|
||||||
x: node.computed.positionAbsolute.x - offsetX,
|
x: node.computed.positionAbsolute.x - offsetX,
|
||||||
@@ -133,27 +135,35 @@ export const getNodePositionWithOrigin = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GetNodesBoundsParams = {
|
||||||
|
nodeOrigin?: NodeOrigin;
|
||||||
|
useRelativePosition?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines a bounding box that contains all given nodes in an array
|
* Determines a bounding box that contains all given nodes in an array
|
||||||
* @public
|
* @public
|
||||||
* @remarks Useful when combined with {@link getViewportForBounds} to calculate the correct transform to fit the given nodes in a viewport.
|
* @remarks Useful when combined with {@link getViewportForBounds} to calculate the correct transform to fit the given nodes in a viewport.
|
||||||
* @param nodes - Nodes to calculate the bounds for
|
* @param nodes - Nodes to calculate the bounds for
|
||||||
* @param nodeOrigin - Origin of the nodes: [0, 0] - top left, [0.5, 0.5] - center
|
* @param params.nodeOrigin - Origin of the nodes: [0, 0] - top left, [0.5, 0.5] - center
|
||||||
|
* @param params.useRelativePosition - Whether to use the relative or absolute node positions
|
||||||
* @returns Bounding box enclosing all nodes
|
* @returns Bounding box enclosing all nodes
|
||||||
*/
|
*/
|
||||||
export const getNodesBounds = (nodes: NodeBase[], nodeOrigin: NodeOrigin = [0, 0]): Rect => {
|
export const getNodesBounds = (
|
||||||
|
nodes: NodeBase[],
|
||||||
|
params: GetNodesBoundsParams = { nodeOrigin: [0, 0], useRelativePosition: false }
|
||||||
|
): Rect => {
|
||||||
if (nodes.length === 0) {
|
if (nodes.length === 0) {
|
||||||
return { x: 0, y: 0, width: 0, height: 0 };
|
return { x: 0, y: 0, width: 0, height: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const box = nodes.reduce(
|
const box = nodes.reduce(
|
||||||
(currBox, node) => {
|
(currBox, node) => {
|
||||||
const { x, y } = getNodePositionWithOrigin(node, node.origin || nodeOrigin);
|
const nodePos = getNodePositionWithOrigin(node, node.origin || params.nodeOrigin);
|
||||||
return getBoundsOfBoxes(
|
return getBoundsOfBoxes(
|
||||||
currBox,
|
currBox,
|
||||||
rectToBox({
|
rectToBox({
|
||||||
x,
|
...nodePos[params.useRelativePosition ? 'position' : 'positionAbsolute'],
|
||||||
y,
|
|
||||||
width: node.computed?.width ?? node.width ?? 0,
|
width: node.computed?.width ?? node.width ?? 0,
|
||||||
height: node.computed?.height ?? node.height ?? 0,
|
height: node.computed?.height ?? node.height ?? 0,
|
||||||
})
|
})
|
||||||
@@ -239,7 +249,7 @@ export function fitView<Params extends FitViewParamsBase<NodeBase>, Options exte
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (filteredNodes.length > 0) {
|
if (filteredNodes.length > 0) {
|
||||||
const bounds = getNodesBounds(filteredNodes, nodeOrigin);
|
const bounds = getNodesBounds(filteredNodes, { nodeOrigin });
|
||||||
|
|
||||||
const viewport = getViewportForBounds(
|
const viewport = getViewportForBounds(
|
||||||
bounds,
|
bounds,
|
||||||
@@ -258,69 +268,87 @@ export function fitView<Params extends FitViewParamsBase<NodeBase>, Options exte
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clampNodeExtent(node: NodeDragItem | NodeBase, extent?: CoordinateExtent | 'parent') {
|
/**
|
||||||
|
* This function clamps the passed extend by the node's width and height.
|
||||||
|
* This is needed to prevent the node from being dragged outside of its extent.
|
||||||
|
*
|
||||||
|
* @param node
|
||||||
|
* @param extent
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function clampNodeExtent<NodeType extends NodeBase>(
|
||||||
|
node: NodeType,
|
||||||
|
extent?: CoordinateExtent | 'parent'
|
||||||
|
): CoordinateExtent | 'parent' | undefined {
|
||||||
if (!extent || extent === 'parent') {
|
if (!extent || extent === 'parent') {
|
||||||
return extent;
|
return extent;
|
||||||
}
|
}
|
||||||
return [extent[0], [extent[1][0] - (node.computed?.width ?? 0), extent[1][1] - (node.computed?.height ?? 0)]];
|
return [extent[0], [extent[1][0] - (node.computed?.width ?? 0), extent[1][1] - (node.computed?.height ?? 0)]];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calcNextPosition<NodeType extends NodeBase>(
|
/**
|
||||||
node: NodeDragItem | NodeType,
|
* This function calculates the next position of a node, taking into account the node's extent, parent node, and origin.
|
||||||
nextPosition: XYPosition,
|
*
|
||||||
nodes: NodeType[],
|
* @internal
|
||||||
nodeExtent?: CoordinateExtent,
|
* @returns position, positionAbsolute
|
||||||
nodeOrigin: NodeOrigin = [0, 0],
|
*/
|
||||||
onError?: OnError
|
export function calculateNodePosition<NodeType extends NodeBase>({
|
||||||
): { position: XYPosition; positionAbsolute: XYPosition } {
|
nodeId,
|
||||||
const clampedNodeExtent = clampNodeExtent(node, node.extent || nodeExtent);
|
nextPosition,
|
||||||
let currentExtent = clampedNodeExtent;
|
nodeLookup,
|
||||||
let parentNode: NodeType | null = null;
|
nodeOrigin = [0, 0],
|
||||||
let parentPos = { x: 0, y: 0 };
|
nodeExtent,
|
||||||
|
onError,
|
||||||
if (node.parentNode) {
|
}: {
|
||||||
parentNode = nodes.find((n) => n.id === node.parentNode) || null;
|
nodeId: string;
|
||||||
parentPos = parentNode
|
nextPosition: XYPosition;
|
||||||
? getNodePositionWithOrigin(parentNode, parentNode.origin || nodeOrigin).positionAbsolute
|
nodeLookup: NodeLookup<NodeType>;
|
||||||
: parentPos;
|
nodeOrigin?: NodeOrigin;
|
||||||
}
|
nodeExtent?: CoordinateExtent;
|
||||||
|
onError?: OnError;
|
||||||
|
}): { position: XYPosition; positionAbsolute: XYPosition } {
|
||||||
|
const node = nodeLookup.get(nodeId)!;
|
||||||
|
const parentNode = node.parentNode ? nodeLookup.get(node.parentNode) : undefined;
|
||||||
|
const { x: parentX, y: parentY } = parentNode
|
||||||
|
? getNodePositionWithOrigin(parentNode, parentNode.origin || nodeOrigin).positionAbsolute
|
||||||
|
: { x: 0, y: 0 };
|
||||||
|
let currentExtent = clampNodeExtent(node, node.extent || nodeExtent);
|
||||||
|
|
||||||
if (node.extent === 'parent' && !node.expandParent) {
|
if (node.extent === 'parent' && !node.expandParent) {
|
||||||
const nodeWidth = node.computed?.width;
|
if (!parentNode) {
|
||||||
const nodeHeight = node.computed?.height;
|
|
||||||
if (node.parentNode && nodeWidth && nodeHeight) {
|
|
||||||
const currNodeOrigin = node.origin || nodeOrigin;
|
|
||||||
|
|
||||||
currentExtent =
|
|
||||||
parentNode && isNumeric(parentNode.computed?.width) && isNumeric(parentNode.computed?.height)
|
|
||||||
? [
|
|
||||||
[parentPos.x + nodeWidth * currNodeOrigin[0], parentPos.y + nodeHeight * currNodeOrigin[1]],
|
|
||||||
[
|
|
||||||
parentPos.x + (parentNode.computed?.width ?? 0) - nodeWidth + nodeWidth * currNodeOrigin[0],
|
|
||||||
parentPos.y + (parentNode.computed?.height ?? 0) - nodeHeight + nodeHeight * currNodeOrigin[1],
|
|
||||||
],
|
|
||||||
]
|
|
||||||
: currentExtent;
|
|
||||||
} else {
|
|
||||||
onError?.('005', errorMessages['error005']());
|
onError?.('005', errorMessages['error005']());
|
||||||
currentExtent = clampedNodeExtent;
|
} else {
|
||||||
|
const nodeWidth = node.computed?.width;
|
||||||
|
const nodeHeight = node.computed?.height;
|
||||||
|
const parentWidth = parentNode?.computed?.width;
|
||||||
|
const parentHeight = parentNode?.computed?.height;
|
||||||
|
|
||||||
|
if (nodeWidth && nodeHeight && parentWidth && parentHeight) {
|
||||||
|
const currNodeOrigin = node.origin || nodeOrigin;
|
||||||
|
const extentX = parentX + nodeWidth * currNodeOrigin[0];
|
||||||
|
const extentY = parentY + nodeHeight * currNodeOrigin[1];
|
||||||
|
|
||||||
|
currentExtent = [
|
||||||
|
[extentX, extentY],
|
||||||
|
[extentX + parentWidth - nodeWidth, extentY + parentHeight - nodeHeight],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (node.extent && node.parentNode && node.extent !== 'parent') {
|
} else if (parentNode && isCoordinateExtent(node.extent)) {
|
||||||
currentExtent = [
|
currentExtent = [
|
||||||
[node.extent[0][0] + parentPos.x, node.extent[0][1] + parentPos.y],
|
[node.extent[0][0] + parentX, node.extent[0][1] + parentY],
|
||||||
[node.extent[1][0] + parentPos.x, node.extent[1][1] + parentPos.y],
|
[node.extent[1][0] + parentX, node.extent[1][1] + parentY],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const positionAbsolute =
|
const positionAbsolute = isCoordinateExtent(currentExtent)
|
||||||
currentExtent && currentExtent !== 'parent'
|
? clampPosition(nextPosition, currentExtent)
|
||||||
? clampPosition(nextPosition, currentExtent as CoordinateExtent)
|
: nextPosition;
|
||||||
: nextPosition;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
position: {
|
position: {
|
||||||
x: positionAbsolute.x - parentPos.x,
|
x: positionAbsolute.x - parentX,
|
||||||
y: positionAbsolute.y - parentPos.y,
|
y: positionAbsolute.y - parentY,
|
||||||
},
|
},
|
||||||
positionAbsolute,
|
positionAbsolute,
|
||||||
};
|
};
|
||||||
@@ -347,7 +375,7 @@ export async function getElementsToRemove<NodeType extends NodeBase = NodeBase,
|
|||||||
edgesToRemove: Partial<EdgeType>[];
|
edgesToRemove: Partial<EdgeType>[];
|
||||||
nodes: NodeType[];
|
nodes: NodeType[];
|
||||||
edges: EdgeType[];
|
edges: EdgeType[];
|
||||||
onBeforeDelete?: OnBeforeDelete;
|
onBeforeDelete?: OnBeforeDeleteBase<NodeType, EdgeType>;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
nodes: NodeType[];
|
nodes: NodeType[];
|
||||||
edges: EdgeType[];
|
edges: EdgeType[];
|
||||||
|
|||||||
@@ -19,21 +19,32 @@ export function getMarkerId(marker: EdgeMarkerType | undefined, id?: string | nu
|
|||||||
|
|
||||||
export function createMarkerIds(
|
export function createMarkerIds(
|
||||||
edges: EdgeBase[],
|
edges: EdgeBase[],
|
||||||
{ id, defaultColor }: { id?: string | null; defaultColor?: string }
|
{
|
||||||
|
id,
|
||||||
|
defaultColor,
|
||||||
|
defaultMarkerStart,
|
||||||
|
defaultMarkerEnd,
|
||||||
|
}: {
|
||||||
|
id?: string | null;
|
||||||
|
defaultColor?: string;
|
||||||
|
defaultMarkerStart?: EdgeMarkerType;
|
||||||
|
defaultMarkerEnd?: EdgeMarkerType;
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
const ids: string[] = [];
|
const ids = new Set<string>();
|
||||||
|
|
||||||
return edges
|
return edges
|
||||||
.reduce<MarkerProps[]>((markers, edge) => {
|
.reduce<MarkerProps[]>((markers, edge) => {
|
||||||
[edge.markerStart, edge.markerEnd].forEach((marker) => {
|
[edge.markerStart || defaultMarkerStart, edge.markerEnd || defaultMarkerEnd].forEach((marker) => {
|
||||||
if (marker && typeof marker === 'object') {
|
if (marker && typeof marker === 'object') {
|
||||||
const markerId = getMarkerId(marker, id);
|
const markerId = getMarkerId(marker, id);
|
||||||
if (!ids.includes(markerId)) {
|
if (!ids.has(markerId)) {
|
||||||
markers.push({ id: markerId, color: marker.color || defaultColor, ...marker });
|
markers.push({ id: markerId, color: marker.color || defaultColor, ...marker });
|
||||||
ids.push(markerId);
|
ids.add(markerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return markers;
|
return markers;
|
||||||
}, [])
|
}, [])
|
||||||
.sort((a, b) => a.id.localeCompare(b.id));
|
.sort((a, b) => a.id.localeCompare(b.id));
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ function calculateXYZPosition<NodeType extends NodeBase>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const parentNode = nodeLookup.get(node.parentNode)!;
|
const parentNode = nodeLookup.get(node.parentNode)!;
|
||||||
const parentNodePosition = getNodePositionWithOrigin(parentNode, parentNode?.origin || nodeOrigin);
|
const { position: parentNodePosition } = getNodePositionWithOrigin(parentNode, parentNode?.origin || nodeOrigin);
|
||||||
|
|
||||||
return calculateXYZPosition(
|
return calculateXYZPosition(
|
||||||
parentNode,
|
parentNode,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
calcAutoPan,
|
calcAutoPan,
|
||||||
getEventPosition,
|
getEventPosition,
|
||||||
getPointerPosition,
|
getPointerPosition,
|
||||||
calcNextPosition,
|
calculateNodePosition,
|
||||||
snapPosition,
|
snapPosition,
|
||||||
getNodesBounds,
|
getNodesBounds,
|
||||||
rectToBox,
|
rectToBox,
|
||||||
@@ -23,7 +23,6 @@ import type {
|
|||||||
SnapGrid,
|
SnapGrid,
|
||||||
Transform,
|
Transform,
|
||||||
PanBy,
|
PanBy,
|
||||||
OnNodeDrag,
|
|
||||||
OnSelectionDrag,
|
OnSelectionDrag,
|
||||||
UpdateNodePositions,
|
UpdateNodePositions,
|
||||||
Box,
|
Box,
|
||||||
@@ -31,7 +30,7 @@ import type {
|
|||||||
|
|
||||||
export type OnDrag = (event: MouseEvent, dragItems: NodeDragItem[], node: NodeBase, nodes: NodeBase[]) => void;
|
export type OnDrag = (event: MouseEvent, dragItems: NodeDragItem[], node: NodeBase, nodes: NodeBase[]) => void;
|
||||||
|
|
||||||
type StoreItems = {
|
type StoreItems<OnNodeDrag> = {
|
||||||
nodes: NodeBase[];
|
nodes: NodeBase[];
|
||||||
nodeLookup: Map<string, NodeBase>;
|
nodeLookup: Map<string, NodeBase>;
|
||||||
edges: EdgeBase[];
|
edges: EdgeBase[];
|
||||||
@@ -58,9 +57,9 @@ type StoreItems = {
|
|||||||
updateNodePositions: UpdateNodePositions;
|
updateNodePositions: UpdateNodePositions;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type XYDragParams = {
|
export type XYDragParams<OnNodeDrag> = {
|
||||||
domNode: Element;
|
domNode: Element;
|
||||||
getStoreItems: () => StoreItems;
|
getStoreItems: () => StoreItems<OnNodeDrag>;
|
||||||
onDragStart?: OnDrag;
|
onDragStart?: OnDrag;
|
||||||
onDrag?: OnDrag;
|
onDrag?: OnDrag;
|
||||||
onDragStop?: OnDrag;
|
onDragStop?: OnDrag;
|
||||||
@@ -80,14 +79,15 @@ export type DragUpdateParams = {
|
|||||||
domNode: Element;
|
domNode: Element;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function XYDrag({
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => void | undefined>({
|
||||||
domNode,
|
domNode,
|
||||||
onNodeMouseDown,
|
onNodeMouseDown,
|
||||||
getStoreItems,
|
getStoreItems,
|
||||||
onDragStart,
|
onDragStart,
|
||||||
onDrag,
|
onDrag,
|
||||||
onDragStop,
|
onDragStop,
|
||||||
}: XYDragParams): XYDragInstance {
|
}: XYDragParams<OnNodeDrag>): XYDragInstance {
|
||||||
let lastPos: { x: number | null; y: number | null } = { x: null, y: null };
|
let lastPos: { x: number | null; y: number | null } = { x: null, y: null };
|
||||||
let autoPanId = 0;
|
let autoPanId = 0;
|
||||||
let dragItems: NodeDragItem[] = [];
|
let dragItems: NodeDragItem[] = [];
|
||||||
@@ -103,7 +103,6 @@ export function XYDrag({
|
|||||||
function update({ noDragClassName, handleSelector, domNode, isSelectable, nodeId }: DragUpdateParams) {
|
function update({ noDragClassName, handleSelector, domNode, isSelectable, nodeId }: DragUpdateParams) {
|
||||||
function updateNodes({ x, y }: XYPosition) {
|
function updateNodes({ x, y }: XYPosition) {
|
||||||
const {
|
const {
|
||||||
nodes,
|
|
||||||
nodeLookup,
|
nodeLookup,
|
||||||
nodeExtent,
|
nodeExtent,
|
||||||
snapGrid,
|
snapGrid,
|
||||||
@@ -121,7 +120,7 @@ export function XYDrag({
|
|||||||
let nodesBox: Box = { x: 0, y: 0, x2: 0, y2: 0 };
|
let nodesBox: Box = { x: 0, y: 0, x2: 0, y2: 0 };
|
||||||
|
|
||||||
if (dragItems.length > 1 && nodeExtent) {
|
if (dragItems.length > 1 && nodeExtent) {
|
||||||
const rect = getNodesBounds(dragItems as unknown as NodeBase[], nodeOrigin);
|
const rect = getNodesBounds(dragItems as unknown as NodeBase[], { nodeOrigin });
|
||||||
nodesBox = rectToBox(rect);
|
nodesBox = rectToBox(rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,13 +148,20 @@ export function XYDrag({
|
|||||||
n.computed.positionAbsolute.y + (n.computed?.height ?? 0) - nodesBox.y2 + nodeExtent[1][1];
|
n.computed.positionAbsolute.y + (n.computed?.height ?? 0) - nodesBox.y2 + nodeExtent[1][1];
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedPos = calcNextPosition(n, nextPosition, nodes, adjustedNodeExtent, nodeOrigin, onError);
|
const { position, positionAbsolute } = calculateNodePosition({
|
||||||
|
nodeId: n.id,
|
||||||
|
nextPosition,
|
||||||
|
nodeLookup,
|
||||||
|
nodeExtent: adjustedNodeExtent,
|
||||||
|
nodeOrigin,
|
||||||
|
onError,
|
||||||
|
});
|
||||||
|
|
||||||
// we want to make sure that we only fire a change event when there is a change
|
// we want to make sure that we only fire a change event when there is a change
|
||||||
hasChange = hasChange || n.position.x !== updatedPos.position.x || n.position.y !== updatedPos.position.y;
|
hasChange = hasChange || n.position.x !== position.x || n.position.y !== position.y;
|
||||||
|
|
||||||
n.position = updatedPos.position;
|
n.position = position;
|
||||||
n.computed.positionAbsolute = updatedPos.positionAbsolute;
|
n.computed.positionAbsolute = positionAbsolute;
|
||||||
|
|
||||||
return n;
|
return n;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -260,9 +260,10 @@ function isValidHandle(
|
|||||||
}: IsValidParams
|
}: IsValidParams
|
||||||
) {
|
) {
|
||||||
const isTarget = fromType === 'target';
|
const isTarget = fromType === 'target';
|
||||||
const handleDomNode = doc.querySelector(
|
const handleDomNode = handle
|
||||||
`.${lib}-flow__handle[data-id="${flowId}-${handle?.nodeId}-${handle?.id}-${handle?.type}"]`
|
? doc.querySelector(`.${lib}-flow__handle[data-id="${flowId}-${handle?.nodeId}-${handle?.id}-${handle?.type}"]`)
|
||||||
);
|
: null;
|
||||||
|
|
||||||
const { x, y } = getEventPosition(event);
|
const { x, y } = getEventPosition(event);
|
||||||
const handleBelow = doc.elementFromPoint(x, y);
|
const handleBelow = doc.elementFromPoint(x, y);
|
||||||
// we always want to prioritize the handle below the mouse cursor over the closest distance handle,
|
// we always want to prioritize the handle below the mouse cursor over the closest distance handle,
|
||||||
|
|||||||
Reference in New Issue
Block a user