diff --git a/examples/svelte/src/routes/examples/overview/+page.svelte b/examples/svelte/src/routes/examples/overview/+page.svelte
index 44d54544..586af3b9 100644
--- a/examples/svelte/src/routes/examples/overview/+page.svelte
+++ b/examples/svelte/src/routes/examples/overview/+page.svelte
@@ -13,7 +13,8 @@
type Node,
type Edge,
ConnectionMode,
- useSvelteFlow
+ useSvelteFlow,
+ ControlButton
} from '@xyflow/svelte';
import CustomNode from './CustomNode.svelte';
@@ -187,7 +188,12 @@
connectionMode={ConnectionMode.Strict}
attributionPosition={'top-center'}
>
-
+
+ xy
+ console.log('control button')}
+ >log
+
diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md
index 5828b7c4..2dd4e593 100644
--- a/packages/react/CHANGELOG.md
+++ b/packages/react/CHANGELOG.md
@@ -1,5 +1,16 @@
# @xyflow/react
+## 12.0.0-next.7
+
+## 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
+- infer types for `getIncomers`, `getOutgoers`, `updateEdge`, `addEdge` and `getConnectedEdges` thanks @joeyballentine
+- refactor handles: prefix with flow id for handling nested flows
+- add comments for types like `ReactFlowProps` or `Node` for a better developer experience
+
## 12.0.0-next.6
### Minor changes
diff --git a/packages/react/package.json b/packages/react/package.json
index 7560e0dc..a7eb71c3 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -1,6 +1,6 @@
{
"name": "@xyflow/react",
- "version": "12.0.0-next.6",
+ "version": "12.0.0-next.7",
"description": "React Flow - A highly customizable React library for building node-based editors and interactive flow charts.",
"keywords": [
"react",
diff --git a/packages/react/src/additional-components/Background/types.ts b/packages/react/src/additional-components/Background/types.ts
index 5d21b23f..409bbe31 100644
--- a/packages/react/src/additional-components/Background/types.ts
+++ b/packages/react/src/additional-components/Background/types.ts
@@ -8,14 +8,27 @@ export enum BackgroundVariant {
export type BackgroundProps = {
id?: string;
+ /** Color of the pattern */
color?: string;
+ /** Color of the background */
bgColor?: string;
+ /** Class applied to the container */
className?: string;
+ /** Class applied to the pattern */
patternClassName?: string;
+ /** Gap between repetitions of the pattern */
gap?: number | [number, number];
+ /** Size of a single pattern element */
size?: number;
+ /** Offset of the pattern */
offset?: number;
+ /** Line width of the Line pattern */
lineWidth?: number;
+ /** Variant of the pattern
+ * @example BackgroundVariant.Lines, BackgroundVariant.Dots, BackgroundVariant.Cross
+ * 'lines', 'dots', 'cross'
+ */
variant?: BackgroundVariant;
+ /** Style applied to the container */
style?: CSSProperties;
};
diff --git a/packages/react/src/additional-components/Controls/types.ts b/packages/react/src/additional-components/Controls/types.ts
index e687b09b..e9015dfb 100644
--- a/packages/react/src/additional-components/Controls/types.ts
+++ b/packages/react/src/additional-components/Controls/types.ts
@@ -4,17 +4,31 @@ import type { PanelPosition } from '@xyflow/system';
import type { FitViewOptions } from '../../types';
export type ControlProps = {
+ /** Show button for zoom in/out */
showZoom?: boolean;
+ /** Show button for fit view */
showFitView?: boolean;
+ /** Show button for toggling interactivity */
showInteractive?: boolean;
+ /** Options being used when fit view button is clicked */
fitViewOptions?: FitViewOptions;
+ /** Callback when zoom in button is clicked */
onZoomIn?: () => void;
+ /** Callback when zoom out button is clicked */
onZoomOut?: () => void;
+ /** Callback when fit view button is clicked */
onFitView?: () => void;
+ /** Callback when interactivity is toggled */
onInteractiveChange?: (interactiveStatus: boolean) => void;
+ /** Position of the controls on the pane
+ * @example PanelPosition.TopLeft, PanelPosition.TopRight,
+ * PanelPosition.BottomLeft, PanelPosition.BottomRight
+ */
position?: PanelPosition;
children?: ReactNode;
+ /** Style applied to container */
style?: React.CSSProperties;
+ /** ClassName applied to container */
className?: string;
'aria-label'?: string;
};
diff --git a/packages/react/src/additional-components/MiniMap/types.ts b/packages/react/src/additional-components/MiniMap/types.ts
index 3b24b08d..31e67428 100644
--- a/packages/react/src/additional-components/MiniMap/types.ts
+++ b/packages/react/src/additional-components/MiniMap/types.ts
@@ -7,23 +7,44 @@ import type { Node } from '../../types';
export type GetMiniMapNodeAttribute = (node: NodeType) => string;
export type MiniMapProps = Omit, 'onClick'> & {
+ /** Color of nodes on minimap */
nodeColor?: string | GetMiniMapNodeAttribute;
+ /** Stroke color of nodes on minimap */
nodeStrokeColor?: string | GetMiniMapNodeAttribute;
+ /** ClassName applied to nodes on minimap */
nodeClassName?: string | GetMiniMapNodeAttribute;
+ /** Border radius of nodes on minimap */
nodeBorderRadius?: number;
+ /** Stroke width of nodes on minimap */
nodeStrokeWidth?: number;
+ /** Component used to render nodes on minimap */
nodeComponent?: ComponentType;
+ /** Color of mask representing viewport */
maskColor?: string;
+ /** Stroke color of mask representing viewport */
maskStrokeColor?: string;
+ /** Stroke width of mask representing viewport */
maskStrokeWidth?: number;
+ /** Position of minimap on pane
+ * @example PanelPosition.TopLeft, PanelPosition.TopRight,
+ * PanelPosition.BottomLeft, PanelPosition.BottomRight
+ */
position?: PanelPosition;
+ /** Callback caled when minimap is clicked*/
onClick?: (event: MouseEvent, position: XYPosition) => void;
+ /** Callback called when node on minimap is clicked */
onNodeClick?: (event: MouseEvent, node: NodeType) => void;
+ /** If true, viewport is pannable via mini map component */
pannable?: boolean;
+ /** If true, viewport is zoomable via mini map component */
zoomable?: boolean;
+ /** The aria-label attribute */
ariaLabel?: string | null;
+ /** Invert direction when panning the minimap viewport */
inversePan?: boolean;
+ /** Step size for zooming in/out on minimap */
zoomStep?: number;
+ /** Offset the viewport on the minmap, acts like a padding */
offsetScale?: number;
};
diff --git a/packages/react/src/additional-components/NodeResizer/types.ts b/packages/react/src/additional-components/NodeResizer/types.ts
index 3a414517..5335c80d 100644
--- a/packages/react/src/additional-components/NodeResizer/types.ts
+++ b/packages/react/src/additional-components/NodeResizer/types.ts
@@ -10,21 +10,39 @@ import type {
} from '@xyflow/system';
export type NodeResizerProps = {
+ /** Id of the node it is resizing
+ * @remarks optional if used inside custom node
+ */
nodeId?: string;
+ /** Color of the resize handle */
color?: string;
+ /** ClassName applied to handle */
handleClassName?: string;
+ /** Style applied to handle */
handleStyle?: CSSProperties;
+ /** ClassName applied to line */
lineClassName?: string;
+ /** Style applied to line */
lineStyle?: CSSProperties;
+ /** Are the controls visible */
isVisible?: boolean;
+ /** Minimum width of node */
minWidth?: number;
+ /** Minimum height of node */
minHeight?: number;
+ /** Maximum width of node */
maxWidth?: number;
+ /** Maximum height of node */
maxHeight?: number;
+ /** Keep aspect ratio when resizing */
keepAspectRatio?: boolean;
+ /** Callback to determine if node should resize */
shouldResize?: ShouldResize;
+ /** Callback called when resizing starts */
onResizeStart?: OnResizeStart;
+ /** Callback called when resizing */
onResize?: OnResize;
+ /** Callback called when resizing ends */
onResizeEnd?: OnResizeEnd;
};
@@ -42,7 +60,14 @@ export type ResizeControlProps = Pick<
| 'onResize'
| 'onResizeEnd'
> & {
+ /** Position of the control
+ * @example ControlPosition.TopLeft, ControlPosition.TopRight,
+ * ControlPosition.BottomLeft, ControlPosition.BottomRight
+ */
position?: ControlPosition;
+ /** Variant of the control
+ * @example ResizeControlVariant.Handle, ResizeControlVariant.Line
+ */
variant?: ResizeControlVariant;
className?: string;
style?: CSSProperties;
diff --git a/packages/react/src/additional-components/NodeToolbar/types.ts b/packages/react/src/additional-components/NodeToolbar/types.ts
index f08859dc..4de679bd 100644
--- a/packages/react/src/additional-components/NodeToolbar/types.ts
+++ b/packages/react/src/additional-components/NodeToolbar/types.ts
@@ -2,9 +2,19 @@ import type { HTMLAttributes } from 'react';
import type { Position, Align } from '@xyflow/system';
export type NodeToolbarProps = HTMLAttributes & {
+ /** Id of the node, or array of ids the toolbar should be displayed at */
nodeId?: string | string[];
+ /** If true, node toolbar is visible even if node is not selected */
isVisible?: boolean;
+ /** Position of the toolbar relative to the node
+ * @example Position.TopLeft, Position.TopRight,
+ * Position.BottomLeft, Position.BottomRight
+ */
position?: Position;
+ /** Offset the toolbar from the node */
offset?: number;
+ /** Align the toolbar relative to the node
+ * @example Align.Start, Align.Center, Align.End
+ */
align?: Align;
};
diff --git a/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx b/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx
index 3e3f3bf7..db56017c 100644
--- a/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx
+++ b/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx
@@ -55,6 +55,7 @@ export function EdgeUpdateAnchors({
onConnectEnd,
cancelConnection,
nodes,
+ rfId: flowId,
panBy,
updateConnection,
} = store.getState();
@@ -85,6 +86,7 @@ export function EdgeUpdateAnchors({
isTarget,
edgeUpdaterType: handleType,
lib,
+ flowId,
cancelConnection,
panBy,
isValidConnection,
diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx
index 41bf492c..362027ab 100644
--- a/packages/react/src/components/Handle/index.tsx
+++ b/packages/react/src/components/Handle/index.tsx
@@ -12,6 +12,7 @@ import {
XYHandle,
getHostForElement,
isMouseEvent,
+ addEdge,
type HandleProps,
type Connection,
type HandleType,
@@ -19,15 +20,14 @@ import {
import { useStore, useStoreApi } from '../../hooks/useStore';
import { useNodeId } from '../../contexts/NodeIdContext';
-import { addEdge } from '../../utils/';
import { type ReactFlowState } from '../../types';
export type HandleComponentProps = HandleProps & Omit, 'id'>;
const selector = (s: ReactFlowState) => ({
- connectionStartHandle: s.connectionStartHandle,
connectOnClick: s.connectOnClick,
noPanClassName: s.noPanClassName,
+ rfId: s.rfId,
});
const connectingSelector =
@@ -70,7 +70,7 @@ const HandleComponent = forwardRef(
const isTarget = type === 'target';
const store = useStoreApi();
const nodeId = useNodeId();
- const { connectOnClick, noPanClassName } = useStore(selector, shallow);
+ const { connectOnClick, noPanClassName, rfId } = useStore(selector, shallow);
const { connecting, clickConnecting } = useStore(connectingSelector(nodeId, handleId, type), shallow);
if (!nodeId) {
@@ -116,6 +116,7 @@ const HandleComponent = forwardRef(
isTarget,
handleId,
nodeId,
+ flowId: currentStore.rfId,
panBy: currentStore.panBy,
cancelConnection: currentStore.cancelConnection,
onConnectStart: currentStore.onConnectStart,
@@ -142,6 +143,7 @@ const HandleComponent = forwardRef(
connectionMode,
isValidConnection: isValidConnectionStore,
lib,
+ rfId: flowId,
} = store.getState();
if (!nodeId || (!connectionClickStartHandle && !isConnectableStart)) {
@@ -167,6 +169,7 @@ const HandleComponent = forwardRef(
fromHandleId: connectionClickStartHandle.handleId || null,
fromType: connectionClickStartHandle.type,
isValidConnection: isValidConnectionHandler,
+ flowId,
doc,
lib,
});
@@ -185,7 +188,7 @@ const HandleComponent = forwardRef(
data-handleid={handleId}
data-nodeid={nodeId}
data-handlepos={position}
- data-id={`${nodeId}-${handleId}-${type}`}
+ data-id={`${rfId}-${nodeId}-${handleId}-${type}`}
className={cc([
'react-flow__handle',
`react-flow__handle-${position}`,
diff --git a/packages/react/src/components/Panel/index.tsx b/packages/react/src/components/Panel/index.tsx
index cde20716..eeb01a15 100644
--- a/packages/react/src/components/Panel/index.tsx
+++ b/packages/react/src/components/Panel/index.tsx
@@ -6,6 +6,9 @@ import { useStore } from '../../hooks/useStore';
import type { ReactFlowState } from '../../types';
export type PanelProps = HTMLAttributes & {
+ /** Set position of the panel
+ * @example 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
+ */
position?: PanelPosition;
children: ReactNode;
};
diff --git a/packages/react/src/hooks/useNodesEdgesState.ts b/packages/react/src/hooks/useNodesEdgesState.ts
index c134472b..766ee049 100644
--- a/packages/react/src/hooks/useNodesEdgesState.ts
+++ b/packages/react/src/hooks/useNodesEdgesState.ts
@@ -12,9 +12,12 @@ import type { Node, NodeChange, Edge, EdgeChange } from '../types';
*/
export function useNodesState(
initialNodes: NodeType[]
-): [NodeType[], Dispatch>, (changes: NodeChange[]) => void] {
+): [NodeType[], Dispatch>, (changes: NodeChange[]) => void] {
const [nodes, setNodes] = useState(initialNodes);
- const onNodesChange = useCallback((changes: NodeChange[]) => setNodes((nds) => applyNodeChanges(changes, nds)), []);
+ const onNodesChange = useCallback(
+ (changes: NodeChange[]) => setNodes((nds) => applyNodeChanges(changes, nds)),
+ []
+ );
return [nodes, setNodes, onNodesChange];
}
@@ -28,9 +31,12 @@ export function useNodesState(
*/
export function useEdgesState(
initialEdges: EdgeType[]
-): [EdgeType[], Dispatch>, (changes: EdgeChange[]) => void] {
+): [EdgeType[], Dispatch>, (changes: EdgeChange[]) => void] {
const [edges, setEdges] = useState(initialEdges);
- const onEdgesChange = useCallback((changes: EdgeChange[]) => setEdges((eds) => applyEdgeChanges(changes, eds)), []);
+ const onEdgesChange = useCallback(
+ (changes: EdgeChange[]) => setEdges((eds) => applyEdgeChanges(changes, eds)),
+ []
+ );
return [edges, setEdges, onEdgesChange];
}
diff --git a/packages/react/src/hooks/useViewportHelper.ts b/packages/react/src/hooks/useViewportHelper.ts
index 481bae15..39320459 100644
--- a/packages/react/src/hooks/useViewportHelper.ts
+++ b/packages/react/src/hooks/useViewportHelper.ts
@@ -86,8 +86,8 @@ const useViewportHelper = (): ViewportHelperFunctions => {
panZoom?.setViewport(viewport, { duration: options?.duration });
},
- screenToFlowPosition: (position: XYPosition) => {
- const { transform, snapToGrid, snapGrid, domNode } = store.getState();
+ screenToFlowPosition: (position: XYPosition, options: { snapToGrid: boolean } = { snapToGrid: true }) => {
+ const { transform, snapGrid, domNode } = store.getState();
if (!domNode) {
return position;
@@ -100,7 +100,7 @@ const useViewportHelper = (): ViewportHelperFunctions => {
y: position.y - domY,
};
- return pointToRendererPoint(correctedPosition, transform, snapToGrid, snapGrid || [1, 1]);
+ return pointToRendererPoint(correctedPosition, transform, options.snapToGrid, snapGrid);
},
flowToScreenPosition: (position: XYPosition) => {
const { transform, domNode } = store.getState();
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index 20573392..3cabb813 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -29,7 +29,7 @@ export { useConnection } from './hooks/useConnection';
export { useNodeId } from './contexts/NodeIdContext';
export { applyNodeChanges, applyEdgeChanges, handleParentExpand } from './utils/changes';
-export { isNode, isEdge, getIncomers, getOutgoers, addEdge, updateEdge, getConnectedEdges } from './utils/general';
+export { isNode, isEdge } from './utils/general';
export * from './additional-components';
@@ -102,5 +102,10 @@ export {
getStraightPath,
getViewportForBounds,
getNodesBounds,
+ getIncomers,
+ getOutgoers,
+ addEdge,
+ updateEdge,
+ getConnectedEdges,
internalsSymbol,
} from '@xyflow/system';
diff --git a/packages/react/src/types/changes.ts b/packages/react/src/types/changes.ts
index 3dfdffa2..9a5e8fe5 100644
--- a/packages/react/src/types/changes.ts
+++ b/packages/react/src/types/changes.ts
@@ -44,13 +44,13 @@ export type NodeReplaceChange = {
* Union type of all possible node changes.
* @public
*/
-export type NodeChange =
+export type NodeChange =
| NodeDimensionChange
| NodePositionChange
| NodeSelectionChange
| NodeRemoveChange
- | NodeAddChange
- | NodeReplaceChange;
+ | NodeAddChange
+ | NodeReplaceChange;
export type EdgeSelectionChange = NodeSelectionChange;
export type EdgeRemoveChange = NodeRemoveChange;
@@ -64,4 +64,9 @@ export type EdgeReplaceChange = {
item: EdgeType;
type: 'replace';
};
-export type EdgeChange = EdgeSelectionChange | EdgeRemoveChange | EdgeAddChange | EdgeReplaceChange;
+
+export type EdgeChange =
+ | EdgeSelectionChange
+ | EdgeRemoveChange
+ | EdgeAddChange
+ | EdgeReplaceChange;
diff --git a/packages/react/src/types/component-props.ts b/packages/react/src/types/component-props.ts
index bdb6e529..213b9d8c 100644
--- a/packages/react/src/types/component-props.ts
+++ b/packages/react/src/types/component-props.ts
@@ -51,118 +51,456 @@ import type {
* @public
*/
export type ReactFlowProps = Omit, 'onError'> & {
+ /** An array of nodes to render in a controlled flow.
+ * @example
+ * const nodes = [
+ * {
+ * id: 'node-1',
+ * type: 'input',
+ * data: { label: 'Node 1' },
+ * position: { x: 250, y: 50 }
+ * }
+ * ];
+ */
nodes?: Node[];
+ /** An array of edges to render in a controlled flow.
+ * @example
+ * const edges = [
+ * {
+ * id: 'edge-1-2',
+ * source: 'node-1',
+ * target: 'node-2',
+ * }
+ * ];
+ */
edges?: Edge[];
+ /** The initial nodes to render in an uncontrolled flow. */
defaultNodes?: Node[];
+ /** The initial edges to render in an uncontrolled flow. */
defaultEdges?: Edge[];
+ /** Defaults to be applied to all new edges that are added to the flow.
+ *
+ * Properties on a new edge will override these defaults if they exist.
+ * @example
+ * const defaultEdgeOptions = {
+ * type: 'customEdgeType',
+ * animated: true,
+ * interactionWidth: 10,
+ * data: { label: 'custom label' },
+ * hidden: false,
+ * deletable: true,
+ * selected: false,
+ * focusable: true,
+ * markerStart: EdgeMarker.ArrowClosed,
+ * markerEnd: EdgeMarker.ArrowClosed,
+ * zIndex: 12,
+ * ariaLabel: 'custom aria label'
+ * }
+ */
defaultEdgeOptions?: DefaultEdgeOptions;
+ /** This event handler is called when a user clicks on a node */
onNodeClick?: NodeMouseHandler;
+ /** This event handler is called when a user double clicks on a node */
onNodeDoubleClick?: NodeMouseHandler;
+ /** This event handler is called when mouse of a user enters a node */
onNodeMouseEnter?: NodeMouseHandler;
+ /** This event handler is called when mouse of a user moves over a node */
onNodeMouseMove?: NodeMouseHandler;
+ /** This event handler is called when mouse of a user leaves a node */
onNodeMouseLeave?: NodeMouseHandler;
+ /** This event handler is called when a user right clicks on a node */
onNodeContextMenu?: NodeMouseHandler;
+ /** This event handler is called when a user starts to drag a node */
onNodeDragStart?: NodeDragHandler;
+ /** This event handler is called when a user drags a node */
onNodeDrag?: NodeDragHandler;
+ /** This event handler is called when a user stops dragging a node */
onNodeDragStop?: NodeDragHandler;
+ /** This event handler is called when a user clicks on an edge */
onEdgeClick?: (event: ReactMouseEvent, edge: Edge) => void;
- onEdgeUpdate?: OnEdgeUpdateFunc;
+ /** This event handler is called when a user right clicks on an edge */
onEdgeContextMenu?: EdgeMouseHandler;
+ /** This event handler is called when mouse of a user enters an edge */
onEdgeMouseEnter?: EdgeMouseHandler;
+ /** This event handler is called when mouse of a user moves over an edge */
onEdgeMouseMove?: EdgeMouseHandler;
+ /** This event handler is called when mouse of a user leaves an edge */
onEdgeMouseLeave?: EdgeMouseHandler;
+ /** This event handler is called when a user double clicks on an edge */
onEdgeDoubleClick?: EdgeMouseHandler;
onEdgeUpdateStart?: (event: ReactMouseEvent, edge: Edge, handleType: HandleType) => void;
onEdgeUpdateEnd?: (event: MouseEvent | TouchEvent, edge: Edge, handleType: HandleType) => void;
+ onEdgeUpdate?: OnEdgeUpdateFunc;
+ /** This event handler is called when a Node is updated
+ * @example // Use NodesState hook to create edges and get onNodesChange handler
+ * import ReactFlow, { useNodesState } from '@xyflow/react';
+ * const [edges, setNodes, onNodesChange] = useNodesState(initialNodes);
+ *
+ * return ()
+ * @example // Use helper function to update edge
+ * import ReactFlow, { applyNodeChanges } from '@xyflow/react';
+ *
+ * const onNodeChange = useCallback(
+ * (changes) => setNode((eds) => applyNodeChanges(changes, eds)),
+ * [],
+ * );
+ *
+ * return ()
+ */
onNodesChange?: OnNodesChange;
+ /** This event handler is called when a Edge is updated
+ * @example // Use EdgesState hook to create edges and get onEdgesChange handler
+ * import ReactFlow, { useEdgesState } from '@xyflow/react';
+ * const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
+ *
+ * return ()
+ * @example // Use helper function to update edge
+ * import ReactFlow, { applyEdgeChanges } from '@xyflow/react';
+ *
+ * const onEdgesChange = useCallback(
+ * (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
+ * [],
+ * );
+ *
+ * return ()
+ */
onEdgesChange?: OnEdgesChange;
+ /** This event handler gets called when a Node is deleted */
onNodesDelete?: OnNodesDelete;
+ /** This event handler gets called when a Edge is deleted */
onEdgesDelete?: OnEdgesDelete;
+ /** This event handler gets called when a Node or Edge is deleted */
onDelete?: OnDelete;
+ /** This event handler gets called when a user starts to drag a selection box */
onSelectionDragStart?: SelectionDragHandler;
+ /** This event handler gets called when a user drags a selection box */
onSelectionDrag?: SelectionDragHandler;
+ /** This event handler gets called when a user stops dragging a selection box */
onSelectionDragStop?: SelectionDragHandler;
onSelectionStart?: (event: ReactMouseEvent) => void;
onSelectionEnd?: (event: ReactMouseEvent) => void;
onSelectionContextMenu?: (event: ReactMouseEvent, nodes: Node[]) => void;
+ /** When a connection line is completed and two nodes are connected by the user, this event fires with the new connection.
+ *
+ * You can use the addEdge utility to convert the connection to a complete edge.
+ * @example // Use helper function to update edges onConnect
+ * import ReactFlow, { addEdge } from '@xyflow/react';
+ *
+ * const onConnect = useCallback(
+ * (params) => setEdges((eds) => addEdge(params, eds)),
+ * [],
+ * );
+ *
+ * return ()
+ */
onConnect?: OnConnect;
+ /** This event handler gets called when a user starts to drag a connection line */
onConnectStart?: OnConnectStart;
+ /** This event handler gets called when a user stops dragging a connection line */
onConnectEnd?: OnConnectEnd;
onClickConnectStart?: OnConnectStart;
onClickConnectEnd?: OnConnectEnd;
+ /** This event handler gets called when a flow has finished initializing */
onInit?: OnInit;
+ /** This event handler is called while the user is either panning or zooming the viewport. */
onMove?: OnMove;
+ /** This event handler gets called when a user starts to pan or zoom the viewport */
onMoveStart?: OnMoveStart;
+ /** This event handler gets called when a user stops panning or zooming the viewport */
onMoveEnd?: OnMoveEnd;
+ /** This event handler gets called when a user changes group of selected elements in the flow */
onSelectionChange?: OnSelectionChangeFunc;
+ /** This event handler gets called when user scroll inside the pane */
onPaneScroll?: (event?: WheelEvent) => void;
+ /** This event handler gets called when user clicks inside the pane */
onPaneClick?: (event: ReactMouseEvent) => void;
+ /** This event handler gets called when user right clicks inside the pane */
onPaneContextMenu?: (event: ReactMouseEvent | MouseEvent) => void;
+ /** This event handler gets called when mouse enters the pane */
onPaneMouseEnter?: (event: ReactMouseEvent) => void;
+ /** This event handler gets called when mouse moves over the pane */
onPaneMouseMove?: (event: ReactMouseEvent) => void;
+ /** This event handler gets called when mouse leaves the pane */
onPaneMouseLeave?: (event: ReactMouseEvent) => void;
+ /** This handler gets called before the user deletes nodes or edges and provides a way to abort the deletion by returning false. */
onBeforeDelete?: OnBeforeDelete;
+ /** Custom node types to be available in a flow.
+ *
+ * React Flow matches a node's type to a component in the nodeTypes object.
+ * @example
+ * import CustomNode from './CustomNode';
+ *
+ * const nodeTypes = { nameOfNodeType: CustomNode };
+ */
nodeTypes?: NodeTypes;
+ /** Custom edge types to be available in a flow.
+ *
+ * React Flow matches an edge's type to a component in the edgeTypes object.
+ * @example
+ * import CustomEdge from './CustomEdge';
+ *
+ * const edgeTypes = { nameOfEdgeType: CustomEdge };
+ */
edgeTypes?: EdgeTypes;
+ /** The type of edge path to use for connection lines.
+ *
+ * Although created edges can be of any type, React Flow needs to know what type of path to render for the connection line before the edge is created!
+ */
connectionLineType?: ConnectionLineType;
+ /** Styles to be applied to the connection line */
connectionLineStyle?: CSSProperties;
+ /** React Component to be used as a connection line */
connectionLineComponent?: ConnectionLineComponent;
+ /** Styles to be applied to the container of the connection line */
connectionLineContainerStyle?: CSSProperties;
+ /** 'strict' connection mode will only allow you to connect source handles to target handles.
+ *
+ * 'loose' connection mode will allow you to connect handles of any type to one another.
+ * @default 'strict'
+ */
connectionMode?: ConnectionMode;
+ /** Pressing down this key deletes all selected nodes & edges.
+ * @default 'Backspace'
+ */
deleteKeyCode?: KeyCode | null;
+ /** If a key is set, you can pan the viewport while that key is held down even if panOnScroll is set to false.
+ *
+ * By setting this prop to null you can disable this functionality.
+ * @default 'Space'
+ */
selectionKeyCode?: KeyCode | null;
+ /** Select multiple elements with a selection box, without pressing down selectionKey */
selectionOnDrag?: boolean;
+ /** When set to "partial", when the user creates a selection box by click and dragging nodes that are only partially in the box are still selected.
+ * @default 'full'
+ */
selectionMode?: SelectionMode;
+ /** If a key is set, you can pan the viewport while that key is held down even if panOnScroll is set to false.
+ *
+ * By setting this prop to null you can disable this functionality.
+ * @default 'Space'
+ */
panActivationKeyCode?: KeyCode | null;
+ /** Pressing down this key you can select multiple elements by clicking.
+ * @default 'Meta' for macOS, "Ctrl" for other systems
+ */
multiSelectionKeyCode?: KeyCode | null;
+ /** If a key is set, you can zoom the viewport while that key is held down even if panOnScroll is set to false.
+ *
+ * By setting this prop to null you can disable this functionality.
+ * @default 'Meta' for macOS, "Ctrl" for other systems
+ * */
zoomActivationKeyCode?: KeyCode | null;
+ /** Set this prop to make the flow snap to the grid */
snapToGrid?: boolean;
+ /** Grid all nodes will snap to
+ * @example [20, 20]
+ */
snapGrid?: SnapGrid;
+ /** You can enable this optimisation to instruct Svelte Flow to only render nodes and edges that would be visible in the viewport.
+ *
+ * This might improve performance when you have a large number of nodes and edges but also adds an overhead.
+ * @default false
+ */
onlyRenderVisibleElements?: boolean;
+ /** Controls if all nodes should be draggable
+ * @default true
+ */
nodesDraggable?: boolean;
+ /** Controls if all nodes should be connectable to each other
+ * @default true
+ */
nodesConnectable?: boolean;
+ /** Controls if all nodes should be focusable
+ * @default true
+ */
nodesFocusable?: boolean;
+ /** Defines nodes relative position to its coordinates
+ * @example
+ * [0, 0] // default, top left
+ * [0.5, 0.5] // center
+ * [1, 1] // bottom right
+ */
nodeOrigin?: NodeOrigin;
+ /** Controls if all edges should be focusable
+ * @default true
+ */
edgesFocusable?: boolean;
+ /** Controls if all edges should be updateable
+ * @default true
+ */
edgesUpdatable?: boolean;
+ /** Controls if all elements should (nodes & edges) be selectable
+ * @default true
+ */
elementsSelectable?: boolean;
+ /** If true, nodes get selected on drag
+ * @default true
+ */
selectNodesOnDrag?: boolean;
+ /** Enableing this prop allows users to pan the viewport by clicking and dragging.
+ *
+ * You can also set this prop to an array of numbers to limit which mouse buttons can activate panning.
+ * @example [0, 2] // allows panning with the left and right mouse buttons
+ * [0, 1, 2, 3, 4] // allows panning with all mouse buttons
+ */
panOnDrag?: boolean | number[];
+ /** Minimum zoom level
+ * @default 0.1
+ */
minZoom?: number;
+ /** Maximum zoom level
+ * @default 1
+ */
maxZoom?: number;
+ /** Controlled viewport to be used instead of internal one */
viewport?: Viewport;
+ /** Sets the initial position and zoom of the viewport.
+ *
+ * If a default viewport is provided but fitView is enabled, the default viewport will be ignored.
+ * @example
+ * const initialViewport = {
+ * zoom: 0.5,
+ * position: { x: 0, y: 0 }
+ * };
+ */
defaultViewport?: Viewport;
+ /**
+ * Gets called when the viewport changes.
+ */
onViewportChange?: (viewport: Viewport) => void;
+ /** By default the viewport extends infinitely. You can use this prop to set a boundary.
+ *
+ * The first pair of coordinates is the top left boundary and the second pair is the bottom right.
+ * @example [[-1000, -10000], [1000, 1000]]
+ */
translateExtent?: CoordinateExtent;
+ /** Disabling this prop will allow the user to scroll the page even when their pointer is over the flow.
+ * @default true
+ */
preventScrolling?: boolean;
+ /** By default nodes can be placed on an infinite flow. You can use this prop to set a boundary.
+ *
+ * The first pair of coordinates is the top left boundary and the second pair is the bottom right.
+ * @example [[-1000, -10000], [1000, 1000]]
+ */
nodeExtent?: CoordinateExtent;
+ /** Color of edge markers
+ * @example "#b1b1b7"
+ */
defaultMarkerColor?: string;
+ /** Controls if the viewport should zoom by scrolling inside the container */
zoomOnScroll?: boolean;
+ /** Controls if the viewport should zoom by pinching on a touch screen */
zoomOnPinch?: boolean;
+ /** Controls if the viewport should pan by scrolling inside the container
+ *
+ * Can be limited to a specific direction with panOnScrollMode
+ */
panOnScroll?: boolean;
+ /** Controls how fast viewport should be panned on scroll.
+ *
+ * Use togther with panOnScroll prop.
+ */
panOnScrollSpeed?: number;
+ /** This prop is used to limit the direction of panning when panOnScroll is enabled.
+ *
+ * The "free" option allows panning in any direction.
+ * @default "free"
+ * @example "horizontal" | "vertical"
+ */
panOnScrollMode?: PanOnScrollMode;
+ /** Controls if the viewport should zoom by double clicking somewhere on the flow */
zoomOnDoubleClick?: boolean;
edgeUpdaterRadius?: number;
noDragClassName?: string;
noWheelClassName?: string;
noPanClassName?: string;
+ /** If set, initial viewport will show all nodes & edges */
fitView?: boolean;
+ /** Options to be used in combination with fitView
+ * @example
+ * const fitViewOptions = {
+ * padding: 0.1,
+ * includeHiddenNodes: false,
+ * minZoom: 0.1,
+ * maxZoom: 1,
+ * duration: 200,
+ * nodes: [{id: 'node-1'}, {id: 'node-2'}], // nodes to fit
+ * };
+ */
fitViewOptions?: FitViewOptions;
+ /**The connectOnClick option lets you click or tap on a source handle to start a connection
+ * and then click on a target handle to complete the connection.
+ *
+ * If you set this option to false, users will need to drag the connection line to the target
+ * handle to create a connection.
+ */
connectOnClick?: boolean;
+ /** Set position of the attribution
+ * @default 'bottom-right'
+ * @example 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
+ */
attributionPosition?: PanelPosition;
+ /** By default, we render a small attribution in the corner of your flows that links back to the project.
+ *
+ * Anyone is free to remove this attribution whether they're a Pro subscriber or not
+ * but we ask that you take a quick look at our {@link https://reactflow.dev/learn/troubleshooting/remove-attribution | removing attribution guide}
+ * before doing so.
+ */
proOptions?: ProOptions;
+ /** Enabling this option will raise the z-index of nodes when they are selected.
+ * @default true
+ */
elevateNodesOnSelect?: boolean;
+ /** Enabling this option will raise the z-index of edges when they are selected.
+ * @default true
+ */
elevateEdgesOnSelect?: boolean;
+ /**
+ * Can be set true if built-in keyboard controls should be disabled.
+ * @default false
+ */
disableKeyboardA11y?: boolean;
+ /** You can enable this prop to automatically pan the viewport while dragging a node.
+ * @default true
+ */
autoPanOnNodeDrag?: boolean;
+ /** You can enable this prop to automatically pan the viewport while dragging a node.
+ * @default true
+ */
autoPanOnConnect?: boolean;
+ /** You can enable this prop to automatically pan the viewport while making a new connection.
+ * @default true
+ */
connectionRadius?: number;
+ /** Ocassionally something may happen that causes Svelte Flow to throw an error.
+ *
+ * Instead of exploding your application, we log a message to the console and then call this event handler.
+ * You might use it for additional logging or to show a message to the user.
+ */
onError?: OnError;
+ /** This callback can be used to validate a new connection
+ *
+ * If you return false, the edge will not be added to your flow.
+ * If you have custom connection logic its preferred to use this callback over the isValidConnection prop on the handle component for performance reasons.
+ * @default (connection: Connection) => true
+ */
isValidConnection?: IsValidConnection;
+ /** With a threshold greater than zero you can control the distinction between node drag and click events.
+ *
+ * If threshold equals 1, you need to drag the node 1 pixel before a drag event is fired.
+ * @default 1
+ */
nodeDragThreshold?: number;
+ /** Sets a fixed width for the flow */
width?: number;
+ /** Sets a fixed height for the flow */
height?: number;
+ /** Controls color scheme used for styling the flow
+ * @default 'system'
+ * @example 'system' | 'light' | 'dark'
+ */
colorMode?: ColorMode;
};
diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts
index b65b78c3..2b514c96 100644
--- a/packages/react/src/types/edges.ts
+++ b/packages/react/src/types/edges.ts
@@ -114,13 +114,24 @@ export type EdgeProps = Pick<
* @public
*/
export type BaseEdgeProps = EdgeLabelOptions & {
+ /** Unique id of edge */
id?: string;
+ /** Additional padding where interacting with an edge is still possible */
interactionWidth?: number;
className?: string;
+ /** The x position of edge label */
labelX?: number;
+ /** The y position of edge label */
labelY?: number;
+ /** Marker at start of edge
+ * @example 'url(#arrow)'
+ */
markerStart?: string;
+ /** Marker at end of edge
+ * @example 'url(#arrow)'
+ */
markerEnd?: string;
+ /** SVG path of the edge */
path: string;
style?: CSSProperties;
};
diff --git a/packages/react/src/types/general.ts b/packages/react/src/types/general.ts
index a1d76983..9a36e41f 100644
--- a/packages/react/src/types/general.ts
+++ b/packages/react/src/types/general.ts
@@ -16,8 +16,8 @@ import {
import type { NodeChange, EdgeChange, Node, Edge, ReactFlowInstance, EdgeProps } from '.';
import { ComponentType } from 'react';
-export type OnNodesChange = (changes: NodeChange[]) => void;
-export type OnEdgesChange = (changes: EdgeChange[]) => void;
+export type OnNodesChange = (changes: NodeChange[]) => void;
+export type OnEdgesChange = (changes: EdgeChange[]) => void;
export type OnNodesDelete = (nodes: Node[]) => void;
export type OnEdgesDelete = (edges: Edge[]) => void;
@@ -55,7 +55,7 @@ export type ViewportHelperFunctions = {
fitView: FitView;
setCenter: SetCenter;
fitBounds: FitBounds;
- screenToFlowPosition: (position: XYPosition) => XYPosition;
+ screenToFlowPosition: (position: XYPosition, options?: { snapToGrid: boolean }) => XYPosition;
flowToScreenPosition: (position: XYPosition) => XYPosition;
viewportInitialized: boolean;
};
diff --git a/packages/react/src/types/instance.ts b/packages/react/src/types/instance.ts
index 0bf01b20..6b37fd14 100644
--- a/packages/react/src/types/instance.ts
+++ b/packages/react/src/types/instance.ts
@@ -45,9 +45,6 @@ export namespace Instance {
area: Rect,
partially?: boolean
) => boolean;
- export type getConnectedEdges = (id: string | (Node | { id: Node['id'] })[]) => Edge[];
- export type getIncomers = (node: string | Node | { id: Node['id'] }) => Node[];
- export type getOutgoers = (node: string | Node | { id: Node['id'] }) => Node[];
export type UpdateNode = (
id: string,
diff --git a/packages/react/src/utils/changes.ts b/packages/react/src/utils/changes.ts
index cd148a6e..6bcddabd 100644
--- a/packages/react/src/utils/changes.ts
+++ b/packages/react/src/utils/changes.ts
@@ -188,7 +188,10 @@ function applyChange(change: any, element: any, elements: any[] = []): any {
);
*/
-export function applyNodeChanges(changes: NodeChange[], nodes: NodeType[]): NodeType[] {
+export function applyNodeChanges(
+ changes: NodeChange[],
+ nodes: NodeType[]
+): NodeType[] {
return applyChanges(changes, nodes) as NodeType[];
}
@@ -212,7 +215,10 @@ export function applyNodeChanges(changes: NodeChan
);
*/
-export function applyEdgeChanges(changes: EdgeChange[], edges: EdgeType[]): EdgeType[] {
+export function applyEdgeChanges(
+ changes: EdgeChange[],
+ edges: EdgeType[]
+): EdgeType[] {
return applyChanges(changes, edges) as EdgeType[];
}
@@ -261,7 +267,7 @@ export function getElementsDiffChanges({
lookup,
}: {
items: Node[] | undefined;
- lookup: NodeLookup;
+ lookup: NodeLookup;
}): NodeChange[];
export function getElementsDiffChanges({
items,
diff --git a/packages/react/src/utils/general.ts b/packages/react/src/utils/general.ts
index 5de3c3b2..2c2387fc 100644
--- a/packages/react/src/utils/general.ts
+++ b/packages/react/src/utils/general.ts
@@ -1,12 +1,4 @@
-import {
- isNodeBase,
- isEdgeBase,
- addEdgeBase,
- getOutgoersBase,
- getIncomersBase,
- updateEdgeBase,
- getConnectedEdgesBase,
-} from '@xyflow/system';
+import { isNodeBase, isEdgeBase } from '@xyflow/system';
import type { Edge, Node } from '../types';
@@ -17,7 +9,8 @@ import type { Edge, Node } from '../types';
* @param element - The element to test
* @returns A boolean indicating whether the element is an Node
*/
-export const isNode = isNodeBase;
+export const isNode = (element: unknown): element is NodeType =>
+ isNodeBase(element);
/**
* Test whether an object is useable as an Edge
@@ -26,52 +19,5 @@ export const isNode = isNodeBase;
* @param element - The element to test
* @returns A boolean indicating whether the element is an Edge
*/
-export const isEdge = isEdgeBase;
-
-/**
- * Pass in a node, and get connected nodes where edge.source === node.id
- * @public
- * @param node - The node to get the connected nodes from
- * @param nodes - The array of all nodes
- * @param edges - The array of all edges
- * @returns An array of nodes that are connected over eges where the source is the given node
- */
-export const getOutgoers = getOutgoersBase;
-
-/**
- * Pass in a node, and get connected nodes where edge.target === node.id
- * @public
- * @param node - The node to get the connected nodes from
- * @param nodes - The array of all nodes
- * @param edges - The array of all edges
- * @returns An array of nodes that are connected over eges where the target is the given node
- */
-export const getIncomers = getIncomersBase;
-
-/**
- * This util is a convenience function to add a new Edge to an array of edges
- * @remarks It also performs some validation to make sure you don't add an invalid edge or duplicate an existing one.
- * @public
- * @param edgeParams - Either an Edge or a Connection you want to add
- * @param edges - The array of all current edges
- * @returns A new array of edges with the new edge added
- */
-export const addEdge = addEdgeBase;
-
-/**
- * A handy utility to update an existing Edge with new properties
- * @param oldEdge - The edge you want to update
- * @param newConnection - The new connection you want to update the edge with
- * @param edges - The array of all current edges
- * @param options.shouldReplaceId - should the id of the old edge be replaced with the new connection id
- * @returns the updated edges array
- */
-export const updateEdge = updateEdgeBase;
-
-/**
- * Get all connecting edges for a given set of nodes
- * @param nodes - Nodes you want to get the connected edges for
- * @param edges - All edges
- * @returns Array of edges that connect any of the given nodes with each other
- */
-export const getConnectedEdges = getConnectedEdgesBase;
+export const isEdge = (element: unknown): element is EdgeType =>
+ isEdgeBase(element);
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index b2fef4b0..b7557fff 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,16 @@
# @xyflow/svelte
+## 0.0.34
+
+## Minor changes
+
+- add second option param to `screenToFlowPosition` for configuring if `snapToGrid` should be used
+- add slot to `Controls`
+- cleanup `ControlButton` types
+- infer types for `getIncomers`, `getOutgoers`, `updateEdge`, `addEdge` and `getConnectedEdges` thanks @joeyballentine
+- refactor handles: prefix with flow id for handling nested flows
+- add comments for types like `SvelteFlowProps` or `Node` for a better developer experience
+
## 0.0.33
### Bugfix
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index f47dc169..5095441e 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -1,6 +1,6 @@
{
"name": "@xyflow/svelte",
- "version": "0.0.33",
+ "version": "0.0.34",
"description": "Svelte Flow - A highly customizable Svelte library for building node-based editors, workflow systems, diagrams and more.",
"keywords": [
"svelte",
diff --git a/packages/svelte/src/lib/components/BaseEdge/types.ts b/packages/svelte/src/lib/components/BaseEdge/types.ts
index c4135c1c..41a1189b 100644
--- a/packages/svelte/src/lib/components/BaseEdge/types.ts
+++ b/packages/svelte/src/lib/components/BaseEdge/types.ts
@@ -5,10 +5,19 @@ export type BaseEdgeProps = Pick<
'interactionWidth' | 'label' | 'labelStyle' | 'style'
> & {
id?: string;
+ /** SVG path of the edge */
path: string;
+ /** The x coordinate of the label */
labelX?: number;
+ /** The y coordinate of the label */
labelY?: number;
+ /** Marker at start of edge
+ * @example 'url(#arrow)'
+ */
markerStart?: string;
+ /** Marker at end of edge
+ * @example 'url(#arrow)'
+ */
markerEnd?: string;
class?: string;
};
diff --git a/packages/svelte/src/lib/components/Handle/Handle.svelte b/packages/svelte/src/lib/components/Handle/Handle.svelte
index 807594e0..212efe56 100644
--- a/packages/svelte/src/lib/components/Handle/Handle.svelte
+++ b/packages/svelte/src/lib/components/Handle/Handle.svelte
@@ -55,7 +55,8 @@
connectionLookup,
onconnect: onConnectAction,
onconnectstart: onConnectStartAction,
- onconnectend: onConnectEndAction
+ onconnectend: onConnectEndAction,
+ flowId
} = store;
function onPointerDown(event: MouseEvent | TouchEvent) {
@@ -72,6 +73,7 @@
connectionMode: $connectionMode,
lib: $lib,
autoPanOnConnect: $autoPanOnConnect,
+ flowId: $flowId,
isValidConnection: $isValidConnection,
updateConnection,
cancelConnection,
@@ -128,7 +130,7 @@
data-handleid={handleId}
data-nodeid={nodeId}
data-handlepos={position}
- data-id="{nodeId}-{id || null}-{type}"
+ data-id="{flowId}-{nodeId}-{id || null}-{type}"
class={cc([
'svelte-flow__handle',
`svelte-flow__handle-${position}`,
diff --git a/packages/svelte/src/lib/container/Pane/Pane.svelte b/packages/svelte/src/lib/container/Pane/Pane.svelte
index 21ed2a61..cce95636 100644
--- a/packages/svelte/src/lib/container/Pane/Pane.svelte
+++ b/packages/svelte/src/lib/container/Pane/Pane.svelte
@@ -30,10 +30,14 @@
diff --git a/packages/svelte/src/lib/plugins/Controls/Controls.svelte b/packages/svelte/src/lib/plugins/Controls/Controls.svelte
index a82c7c03..042b2be5 100644
--- a/packages/svelte/src/lib/plugins/Controls/Controls.svelte
+++ b/packages/svelte/src/lib/plugins/Controls/Controls.svelte
@@ -74,6 +74,7 @@
data-testid="svelte-flow__controls"
aria-label={ariaLabel ?? 'Svelte Flow controls'}
>
+
{#if showZoom}
{:else}{/if}
{/if}
+
+
diff --git a/packages/svelte/src/lib/plugins/Controls/types.ts b/packages/svelte/src/lib/plugins/Controls/types.ts
index 2aad9b39..fca86b00 100644
--- a/packages/svelte/src/lib/plugins/Controls/types.ts
+++ b/packages/svelte/src/lib/plugins/Controls/types.ts
@@ -1,9 +1,17 @@
+import type { HTMLButtonAttributes } from 'svelte/elements';
import type { PanelPosition } from '@xyflow/system';
export type ControlsProps = {
+ /** Position of the controls on the pane
+ * @example PanelPosition.TopLeft, PanelPosition.TopRight,
+ * PanelPosition.BottomLeft, PanelPosition.BottomRight
+ */
position?: PanelPosition;
+ /** Show button for zoom in/out */
showZoom?: boolean;
+ /** Show button for fit view */
showFitView?: boolean;
+ /** Show button for toggling interactivity */
showLock?: boolean;
buttonBgColor?: string;
buttonBgColorHover?: string;
@@ -11,3 +19,12 @@ export type ControlsProps = {
buttonColorHover?: string;
'aria-label'?: string;
};
+
+export type ControlButtonProps = HTMLButtonAttributes & {
+ class?: string;
+ bgColor?: string;
+ bgColorHover?: string;
+ color?: string;
+ colorHover?: string;
+ borderColor?: string;
+};
diff --git a/packages/svelte/src/lib/plugins/Minimap/types.ts b/packages/svelte/src/lib/plugins/Minimap/types.ts
index d39aab7a..d66577a8 100644
--- a/packages/svelte/src/lib/plugins/Minimap/types.ts
+++ b/packages/svelte/src/lib/plugins/Minimap/types.ts
@@ -5,25 +5,45 @@ import type { Node } from '$lib/types';
export type GetMiniMapNodeAttribute = (node: Node) => string;
export type MiniMapProps = {
+ /** Background color of minimap */
bgColor?: string;
+ /** Color of nodes on the minimap */
nodeColor?: string | GetMiniMapNodeAttribute;
+ /** Stroke color of nodes on the minimap */
nodeStrokeColor?: string | GetMiniMapNodeAttribute;
+ /** Class applied to nodes on the minimap */
nodeClass?: string | GetMiniMapNodeAttribute;
+ /** Border radius of nodes on the minimap */
nodeBorderRadius?: number;
+ /** Stroke width of nodes on the minimap */
nodeStrokeWidth?: number;
+ /** Color of the mask representing viewport */
maskColor?: string;
+ /** Stroke color of the mask representing viewport */
maskStrokeColor?: string;
+ /** Stroke width of the mask representing viewport */
maskStrokeWidth?: number;
+ /** Position of the minimap on the pane
+ * @example PanelPosition.TopLeft, PanelPosition.TopRight,
+ * PanelPosition.BottomLeft, PanelPosition.BottomRight
+ */
position?: PanelPosition;
+ /** Class applied to container */
class?: string;
+ /** Style applied to container */
style?: string;
+ /** The aria-label applied to container */
ariaLabel?: string | null;
+ /** Width of minimap */
width?: number;
+ /** Height of minimap */
height?: number;
// onClick?: (event: MouseEvent, position: XYPosition) => void;
// onNodeClick?: (event: MouseEvent, node: Node) => void;
pannable?: boolean;
zoomable?: boolean;
+ /** Invert the direction when panning the minimap viewport */
inversePan?: boolean;
+ /** Step size for zooming in/out */
zoomStep?: number;
};
diff --git a/packages/svelte/src/lib/plugins/NodeResizer/types.ts b/packages/svelte/src/lib/plugins/NodeResizer/types.ts
index b37e55c1..ebe44b8b 100644
--- a/packages/svelte/src/lib/plugins/NodeResizer/types.ts
+++ b/packages/svelte/src/lib/plugins/NodeResizer/types.ts
@@ -8,21 +8,39 @@ import type {
} from '@xyflow/system';
export type NodeResizerProps = {
+ /** Id of the node it is resizing
+ * @remarks optional if used inside custom node
+ */
nodeId?: string;
+ /** Color of the resize handle */
color?: string;
+ /** Class applied to handle */
handleClass?: string;
+ /** Style applied to handle */
handleStyle?: string;
+ /** Class applied to line */
lineClass?: string;
+ /** Style applied to line */
lineStyle?: string;
+ /** Are the controls visible */
isVisible?: boolean;
+ /** Minimum width of node */
minWidth?: number;
+ /** Minimum height of node */
minHeight?: number;
+ /** Maximum width of node */
maxWidth?: number;
+ /** Maximum height of node */
maxHeight?: number;
+ /** Keep aspect ratio when resizing */
keepAspectRatio?: boolean;
+ /** Callback to determine if node should resize */
shouldResize?: ShouldResize;
+ /** Callback called when resizing starts */
onResizeStart?: OnResizeStart;
+ /** Callback called when resizing */
onResize?: OnResize;
+ /** Callback called when resizing ends */
onResizeEnd?: OnResizeEnd;
};
@@ -40,7 +58,14 @@ export type ResizeControlProps = Pick<
| 'onResize'
| 'onResizeEnd'
> & {
+ /** Position of control
+ * @example ControlPosition.TopLeft, ControlPosition.TopRight,
+ * ControlPosition.BottomLeft, ControlPosition.BottomRight
+ */
position?: ControlPosition;
+ /** Variant of control
+ * @example ResizeControlVariant.Handle, ResizeControlVariant.Line
+ */
variant?: ResizeControlVariant;
class?: string;
style?: string;
diff --git a/packages/svelte/src/lib/plugins/NodeToolbar/types.ts b/packages/svelte/src/lib/plugins/NodeToolbar/types.ts
index 6c812de2..aec506a3 100644
--- a/packages/svelte/src/lib/plugins/NodeToolbar/types.ts
+++ b/packages/svelte/src/lib/plugins/NodeToolbar/types.ts
@@ -1,9 +1,19 @@
import type { Position, Align } from '@xyflow/system';
export type NodeToolbarProps = {
+ /** The id of the node, or array of ids the toolbar should be displayed at */
nodeId?: string | string[];
+ /** Position of the toolbar relative to the node
+ * @example Position.TopLeft, Position.TopRight,
+ * Position.BottomLeft, Position.BottomRight
+ */
position?: Position;
+ /** Align the toolbar relative to the node
+ * @example Align.Start, Align.Center, Align.End
+ */
align?: Align;
+ /** Offset the toolbar from the node */
offset?: number;
+ /** If true, node toolbar is visible even if node is not selected */
isVisible?: boolean;
};
diff --git a/packages/svelte/src/lib/store/index.ts b/packages/svelte/src/lib/store/index.ts
index a13cc7f1..18941bc6 100644
--- a/packages/svelte/src/lib/store/index.ts
+++ b/packages/svelte/src/lib/store/index.ts
@@ -7,6 +7,7 @@ import {
getElementsToRemove,
panBy as panBySystem,
updateNodeDimensions as updateNodeDimensionsSystem,
+ addEdge as addEdgeUtil,
type UpdateNodePositions,
type NodeDimensionUpdate,
type ViewportHelperFunctionOptions,
@@ -19,7 +20,6 @@ import {
errorMessages
} from '@xyflow/system';
-import { addEdge as addEdgeUtil } from '$lib/utils';
import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions, ConnectionData } from '$lib/types';
import { initialEdgeTypes, initialNodeTypes, getInitialStore } from './initial-store';
import type { SvelteFlowStore } from './types';
diff --git a/packages/svelte/src/lib/store/initial-store.ts b/packages/svelte/src/lib/store/initial-store.ts
index 90c59b2d..26c57c86 100644
--- a/packages/svelte/src/lib/store/initial-store.ts
+++ b/packages/svelte/src/lib/store/initial-store.ts
@@ -99,7 +99,7 @@ export const getInitialStore = ({
return {
flowId: writable(null),
nodes: createNodesStore(nextNodes, nodeLookup),
- nodeLookup: readable(nodeLookup),
+ nodeLookup: readable>(nodeLookup),
edgeLookup: readable(edgeLookup),
visibleNodes: readable([]),
edges: createEdgesStore(edges, connectionLookup, edgeLookup),
diff --git a/packages/svelte/src/lib/types/general.ts b/packages/svelte/src/lib/types/general.ts
index 5d33a97c..4f5fa30b 100644
--- a/packages/svelte/src/lib/types/general.ts
+++ b/packages/svelte/src/lib/types/general.ts
@@ -23,13 +23,26 @@ export type ConnectionData = {
};
export type HandleComponentProps = {
+ /** Type of the handle
+ * @example HandleType.Source, HandleType.Target
+ */
type: HandleType;
+ /** Position of the handle
+ * @example Position.TopLeft, Position.TopRight,
+ * Position.BottomLeft, Position.BottomRight
+ */
position?: Position;
+ /** Id of the handle
+ * @remarks optional if there is only one handle of this type
+ */
id?: string;
class?: string;
style?: string;
+ /** Should you be able to connect from/to this handle */
isConnectable?: boolean;
+ /** Shoould you be able to connect from this handle */
isConnectableStart?: boolean;
+ /** Should you be able to connect to this handle */
isConnectableEnd?: boolean;
onconnect?: (connections: Connection[]) => void;
ondisconnect?: (connections: Connection[]) => void;
diff --git a/packages/svelte/src/lib/utils/index.ts b/packages/svelte/src/lib/utils/index.ts
index bad572dc..10f205ca 100644
--- a/packages/svelte/src/lib/utils/index.ts
+++ b/packages/svelte/src/lib/utils/index.ts
@@ -1,12 +1,4 @@
-import {
- isNodeBase,
- isEdgeBase,
- addEdgeBase,
- getOutgoersBase,
- getIncomersBase,
- updateEdgeBase,
- getConnectedEdgesBase
-} from '@xyflow/system';
+import { isNodeBase, isEdgeBase } from '@xyflow/system';
import type { Edge, Node } from '$lib/types';
@@ -17,7 +9,8 @@ import type { Edge, Node } from '$lib/types';
* @param element - The element to test
* @returns A boolean indicating whether the element is an Node
*/
-export const isNode = isNodeBase;
+export const isNode = (element: unknown): element is NodeType =>
+ isNodeBase(element);
/**
* Test whether an object is useable as an Edge
@@ -26,52 +19,5 @@ export const isNode = isNodeBase;
* @param element - The element to test
* @returns A boolean indicating whether the element is an Edge
*/
-export const isEdge = isEdgeBase;
-
-/**
- * Pass in a node, and get connected nodes where edge.source === node.id
- * @public
- * @param node - The node to get the connected nodes from
- * @param nodes - The array of all nodes
- * @param edges - The array of all edges
- * @returns An array of nodes that are connected over eges where the source is the given node
- */
-export const getOutgoers = getOutgoersBase;
-
-/**
- * Pass in a node, and get connected nodes where edge.target === node.id
- * @public
- * @param node - The node to get the connected nodes from
- * @param nodes - The array of all nodes
- * @param edges - The array of all edges
- * @returns An array of nodes that are connected over eges where the target is the given node
- */
-export const getIncomers = getIncomersBase;
-
-/**
- * This util is a convenience function to add a new Edge to an array of edges
- * @remarks It also performs some validation to make sure you don't add an invalid edge or duplicate an existing one.
- * @public
- * @param edgeParams - Either an Edge or a Connection you want to add
- * @param edges - The array of all current edges
- * @returns A new array of edges with the new edge added
- */
-export const addEdge = addEdgeBase;
-
-/**
- * A handy utility to update an existing Edge with new properties
- * @param oldEdge - The edge you want to update
- * @param newConnection - The new connection you want to update the edge with
- * @param edges - The array of all current edges
- * @param options.shouldReplaceId - should the id of the old edge be replaced with the new connection id
- * @returns the updated edges array
- */
-export const updateEdge = updateEdgeBase;
-
-/**
- * Get all connecting edges for a given set of nodes
- * @param nodes - Nodes you want to get the connected edges for
- * @param edges - All edges
- * @returns Array of edges that connect any of the given nodes with each other
- */
-export const getConnectedEdges = getConnectedEdgesBase;
+export const isEdge = (element: unknown): element is EdgeType =>
+ isEdgeBase(element);
diff --git a/packages/system/package.json b/packages/system/package.json
index 6cc84fd4..4eecdb57 100644
--- a/packages/system/package.json
+++ b/packages/system/package.json
@@ -1,6 +1,6 @@
{
"name": "@xyflow/system",
- "version": "0.0.14",
+ "version": "0.0.15",
"description": "xyflow core system that powers React Flow and Svelte Flow.",
"keywords": [
"node-based UI",
diff --git a/packages/system/src/types/edges.ts b/packages/system/src/types/edges.ts
index c2318f0c..e28ad058 100644
--- a/packages/system/src/types/edges.ts
+++ b/packages/system/src/types/edges.ts
@@ -2,22 +2,40 @@ import { Position } from './utils';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type EdgeBase = {
+ /** Unique id of an edge */
id: string;
+ /** Type of an edge defined in edgeTypes */
type?: string;
+ /** Id of source node */
source: string;
+ /** Id of target node */
target: string;
+ /** Id of source handle
+ * only needed if there are multiple handles per node
+ */
sourceHandle?: string | null;
+ /** Id of target handle
+ * only needed if there are multiple handles per node
+ */
targetHandle?: string | null;
animated?: boolean;
hidden?: boolean;
deletable?: boolean;
selectable?: boolean;
+ /** Arbitrary data passed to an edge */
data?: EdgeData;
selected?: boolean;
+ /** Set the marker on the beginning of an edge
+ * @example 'arrow', 'arrowclosed' or custom marker
+ */
markerStart?: EdgeMarkerType;
+ /** Set the marker on the end of an edge
+ * @example 'arrow', 'arrowclosed' or custom marker
+ */
markerEnd?: EdgeMarkerType;
zIndex?: number;
ariaLabel?: string;
+ /** Padding around the edge where interaction is still possible */
interactionWidth?: number;
};
diff --git a/packages/system/src/types/handles.ts b/packages/system/src/types/handles.ts
index c1ea7d0e..32732935 100644
--- a/packages/system/src/types/handles.ts
+++ b/packages/system/src/types/handles.ts
@@ -27,12 +27,29 @@ export type ConnectionHandle = {
};
export type HandleProps = {
+ /** Type of the handle
+ * @example HandleType.Source, HandleType.Target
+ */
type: HandleType;
+ /** Position of the handle
+ * @example Position.TopLeft, Position.TopRight,
+ * Position.BottomLeft, Position.BottomRight
+ */
position: Position;
+ /** Should you be able to connect to/from this handle */
isConnectable?: boolean;
+ /** Should you be able to connect from this handle */
isConnectableStart?: boolean;
+ /** Should you be able to connect to this handle */
isConnectableEnd?: boolean;
+ /** Callback called when connection is made */
onConnect?: OnConnect;
+ /** Callback if connection is valid
+ * @remarks connection becomes an edge if isValidConnection returns true
+ */
isValidConnection?: IsValidConnection;
+ /** Id of the handle
+ * @remarks optional if there is only one handle of this type
+ */
id?: string;
};
diff --git a/packages/system/src/types/nodes.ts b/packages/system/src/types/nodes.ts
index c96ee085..40aa1c16 100644
--- a/packages/system/src/types/nodes.ts
+++ b/packages/system/src/types/nodes.ts
@@ -3,16 +3,34 @@ import { internalsSymbol } from '../constants';
import type { XYPosition, Position, CoordinateExtent, HandleElement } from '.';
import { Optional } from '../utils/types';
-// this is stuff that all nodes share independent of the framework
+/**
+ * Framework independent node data structure.
+ *
+ * @typeParam T - type of the node data
+ * @typeParam U - type of the node
+ */
export type NodeBase = {
+ /** Unique id of a node */
id: string;
+ /** Position of a node on the pane
+ * @example { x: 0, y: 0 }
+ */
position: XYPosition;
+ /** Arbitrary data passed to a node */
data: T;
+ /** Type of node defined in nodeTypes */
type?: U;
+ /** Only relevant for default, source, target nodeType. controls source position
+ * @example 'right', 'left', 'top', 'bottom'
+ */
sourcePosition?: Position;
+ /** Only relevant for default, source, target nodeType. controls target position
+ * @example 'right', 'left', 'top', 'bottom'
+ */
targetPosition?: Position;
hidden?: boolean;
selected?: boolean;
+ /** True, if node is being dragged */
dragging?: boolean;
draggable?: boolean;
selectable?: boolean;
@@ -21,11 +39,21 @@ export type NodeBase
dragHandle?: string;
width?: number | null;
height?: number | null;
+ /** Parent node id, used for creating sub-flows */
parentNode?: string;
zIndex?: number;
+ /** Boundary a node can be moved in
+ * @example 'parent' or [[0, 0], [100, 100]]
+ */
extent?: 'parent' | CoordinateExtent;
expandParent?: boolean;
ariaLabel?: string;
+ /** Origin of the node relative to it's position
+ * @example
+ * [0.5, 0.5] // centers the node
+ * [0, 0] // top left
+ * [1, 1] // bottom right
+ */
origin?: NodeOrigin;
handles?: NodeHandle[];
computed?: {
@@ -34,7 +62,7 @@ export type NodeBase
positionAbsolute?: XYPosition;
};
- // only used internally
+ // Only used internally
[internalsSymbol]?: {
z?: number;
handleBounds?: NodeHandleBounds;
@@ -47,7 +75,14 @@ export type NodeBase
};
// props that get passed to a custom node
+/**
+ * The node data structure that gets used for the nodes prop.
+ *
+ * @public
+ * @param id - The id of the node.
+ */
export type NodeProps = {
+ /** Id of the node */
id: NodeBase['id'];
data: T;
dragHandle: NodeBase['dragHandle'];
diff --git a/packages/system/src/utils/edges/general.ts b/packages/system/src/utils/edges/general.ts
index 14e73318..f53db23a 100644
--- a/packages/system/src/utils/edges/general.ts
+++ b/packages/system/src/utils/edges/general.ts
@@ -98,7 +98,7 @@ const connectionExists = (edge: EdgeBase, edges: EdgeBase[]) => {
* @param edges - The array of all current edges
* @returns A new array of edges with the new edge added
*/
-export const addEdgeBase = (
+export const addEdge = (
edgeParams: EdgeType | Connection,
edges: EdgeType[]
): EdgeType[] => {
@@ -145,7 +145,7 @@ export type UpdateEdgeOptions = {
* @param options.shouldReplaceId - should the id of the old edge be replaced with the new connection id
* @returns the updated edges array
*/
-export const updateEdgeBase = (
+export const updateEdge = (
oldEdge: EdgeType,
newConnection: Connection,
edges: EdgeType[],
diff --git a/packages/system/src/utils/graph.ts b/packages/system/src/utils/graph.ts
index 68024ce5..fa56a987 100644
--- a/packages/system/src/utils/graph.ts
+++ b/packages/system/src/utils/graph.ts
@@ -54,7 +54,7 @@ export const isNodeBase = (element: any):
* @param edges - The array of all edges
* @returns An array of nodes that are connected over eges where the source is the given node
*/
-export const getOutgoersBase = (
+export const getOutgoers = (
node: NodeType | { id: string },
nodes: NodeType[],
edges: EdgeType[]
@@ -81,7 +81,7 @@ export const getOutgoersBase = (
+export const getIncomers = (
node: NodeType | { id: string },
nodes: NodeType[],
edges: EdgeType[]
@@ -148,7 +148,7 @@ export const getNodesBounds = (nodes: NodeBase[], nodeOrigin: NodeOrigin = [0, 0
const box = nodes.reduce(
(currBox, node) => {
- const { x, y } = getNodePositionWithOrigin(node, node.origin || nodeOrigin).positionAbsolute;
+ const { x, y } = getNodePositionWithOrigin(node, node.origin || nodeOrigin);
return getBoundsOfBoxes(
currBox,
rectToBox({
@@ -212,7 +212,7 @@ export const getNodesInside = (
* @param edges - All edges
* @returns Array of edges that connect any of the given nodes with each other
*/
-export const getConnectedEdgesBase = (
+export const getConnectedEdges = (
nodes: NodeType[],
edges: EdgeType[]
): EdgeType[] => {
@@ -370,7 +370,7 @@ export async function getElementsToRemove edge.id);
const deletableEdges = edges.filter((edge) => edge.deletable !== false);
- const connectedEdges = getConnectedEdgesBase(matchingNodes, deletableEdges);
+ const connectedEdges = getConnectedEdges(matchingNodes, deletableEdges);
const matchingEdges: EdgeType[] = connectedEdges;
for (const edge of deletableEdges) {
diff --git a/packages/system/src/xyhandle/XYHandle.ts b/packages/system/src/xyhandle/XYHandle.ts
index 826a1c80..239e6356 100644
--- a/packages/system/src/xyhandle/XYHandle.ts
+++ b/packages/system/src/xyhandle/XYHandle.ts
@@ -27,6 +27,7 @@ export type OnPointerDownParams = {
isTarget: boolean;
nodes: NodeBase[];
lib: string;
+ flowId: string | null;
edgeUpdaterType?: HandleType;
updateConnection: UpdateConnection;
panBy: PanBy;
@@ -48,6 +49,7 @@ export type IsValidParams = {
isValidConnection?: IsValidConnection;
doc: Document | ShadowRoot;
lib: string;
+ flowId: string | null;
};
export type XYHandleInstance = {
@@ -79,6 +81,7 @@ function onPointerDown(
nodes,
lib,
autoPanOnConnect,
+ flowId,
panBy,
cancelConnection,
onConnectStart,
@@ -169,6 +172,7 @@ function onPointerDown(
isValidConnection,
doc,
lib,
+ flowId,
});
handleDomNode = result.handleDomNode;
@@ -251,12 +255,13 @@ function isValidHandle(
fromType,
doc,
lib,
+ flowId,
isValidConnection = alwaysValid,
}: IsValidParams
) {
const isTarget = fromType === 'target';
const handleDomNode = doc.querySelector(
- `.${lib}-flow__handle[data-id="${handle?.nodeId}-${handle?.id}-${handle?.type}"]`
+ `.${lib}-flow__handle[data-id="${flowId}-${handle?.nodeId}-${handle?.id}-${handle?.type}"]`
);
const { x, y } = getEventPosition(event);
const handleBelow = doc.elementFromPoint(x, y);