Merge pull request #3700 from xyflow/refactor/tsdoc

Refactor: Cleanup types + TSDoc integration
This commit is contained in:
Moritz Klack
2023-12-19 13:16:03 +01:00
committed by GitHub
49 changed files with 771 additions and 222 deletions
@@ -4,12 +4,12 @@ import type { PanelPosition, XYPosition } from '@xyflow/system';
import type { Node } from '../../types'; import type { Node } from '../../types';
export type GetMiniMapNodeAttribute<NodeData = any> = (node: Node<NodeData>) => string; export type GetMiniMapNodeAttribute<NodeType extends Node = Node> = (node: NodeType) => string;
export type MiniMapProps<NodeData = any> = Omit<HTMLAttributes<SVGSVGElement>, 'onClick'> & { export type MiniMapProps<NodeType extends Node = Node> = Omit<HTMLAttributes<SVGSVGElement>, 'onClick'> & {
nodeColor?: string | GetMiniMapNodeAttribute<NodeData>; nodeColor?: string | GetMiniMapNodeAttribute<NodeType>;
nodeStrokeColor?: string | GetMiniMapNodeAttribute<NodeData>; nodeStrokeColor?: string | GetMiniMapNodeAttribute<NodeType>;
nodeClassName?: string | GetMiniMapNodeAttribute<NodeData>; nodeClassName?: string | GetMiniMapNodeAttribute<NodeType>;
nodeBorderRadius?: number; nodeBorderRadius?: number;
nodeStrokeWidth?: number; nodeStrokeWidth?: number;
nodeComponent?: ComponentType<MiniMapNodeProps>; nodeComponent?: ComponentType<MiniMapNodeProps>;
@@ -18,7 +18,7 @@ export type MiniMapProps<NodeData = any> = Omit<HTMLAttributes<SVGSVGElement>, '
maskStrokeWidth?: number; maskStrokeWidth?: number;
position?: PanelPosition; position?: PanelPosition;
onClick?: (event: MouseEvent, position: XYPosition) => void; onClick?: (event: MouseEvent, position: XYPosition) => void;
onNodeClick?: (event: MouseEvent, node: Node<NodeData>) => void; onNodeClick?: (event: MouseEvent, node: NodeType) => void;
pannable?: boolean; pannable?: boolean;
zoomable?: boolean; zoomable?: boolean;
ariaLabel?: string | null; ariaLabel?: string | null;
@@ -27,8 +27,8 @@ export type MiniMapProps<NodeData = any> = Omit<HTMLAttributes<SVGSVGElement>, '
offsetScale?: number; offsetScale?: number;
}; };
export type MiniMapNodes = Pick< export type MiniMapNodes<NodeType extends Node = Node> = Pick<
MiniMapProps, MiniMapProps<NodeType>,
'nodeColor' | 'nodeStrokeColor' | 'nodeClassName' | 'nodeBorderRadius' | 'nodeStrokeWidth' | 'nodeComponent' 'nodeColor' | 'nodeStrokeColor' | 'nodeClassName' | 'nodeBorderRadius' | 'nodeStrokeWidth' | 'nodeComponent'
> & { > & {
onClick?: (event: MouseEvent, nodeId: string) => void; onClick?: (event: MouseEvent, nodeId: string) => void;
@@ -9,6 +9,12 @@ function getMediaQuery() {
return window.matchMedia('(prefers-color-scheme: dark)'); return window.matchMedia('(prefers-color-scheme: dark)');
} }
/**
* Hook for receiving the current color mode class 'dark' or 'light'.
*
* @internal
* @param colorMode - The color mode to use ('dark', 'light' or 'system')
*/
export default function useColorModeClass(colorMode: ColorMode): ColorModeClass { export default function useColorModeClass(colorMode: ColorMode): ColorModeClass {
const [colorModeClass, setColorModeClass] = useState<ColorModeClass | null>( const [colorModeClass, setColorModeClass] = useState<ColorModeClass | null>(
colorMode === 'system' ? null : colorMode colorMode === 'system' ? null : colorMode
+5
View File
@@ -13,6 +13,11 @@ type UseDragParams = {
isSelectable?: boolean; isSelectable?: boolean;
}; };
/**
* Hook for calling XYDrag helper from @xyflow/system.
*
* @internal
*/
function useDrag({ nodeRef, disabled = false, noDragClassName, handleSelector, nodeId, isSelectable }: UseDragParams) { function useDrag({ nodeRef, disabled = false, noDragClassName, handleSelector, nodeId, isSelectable }: UseDragParams) {
const store = useStoreApi(); const store = useStoreApi();
const [dragging, setDragging] = useState<boolean>(false); const [dragging, setDragging] = useState<boolean>(false);
+6
View File
@@ -5,6 +5,12 @@ import type { Edge, ReactFlowState } from '../types';
const edgesSelector = (state: ReactFlowState) => state.edges; const edgesSelector = (state: ReactFlowState) => state.edges;
/**
* Hook for getting the current edges from the store.
*
* @public
* @returns An array of edges
*/
function useEdges<EdgeData>(): Edge<EdgeData>[] { function useEdges<EdgeData>(): Edge<EdgeData>[] {
const edges = useStore(edgesSelector, shallow); const edges = useStore(edgesSelector, shallow);
@@ -10,6 +10,11 @@ const selected = (item: Node | Edge) => item.selected;
const deleteKeyOptions: UseKeyPressOptions = { actInsideInputWithModifier: false }; const deleteKeyOptions: UseKeyPressOptions = { actInsideInputWithModifier: false };
/**
* Hook for handling global key events.
*
* @internal
*/
export default ({ export default ({
deleteKeyCode, deleteKeyCode,
multiSelectionKeyCode, multiSelectionKeyCode,
@@ -17,8 +17,8 @@ type useHandleConnectionsParams = {
* *
* @public * @public
* @param param.type - handle type 'source' or 'target' * @param param.type - handle type 'source' or 'target'
* @param param.id - the handle id (this is only needed if the node has multiple handles of the same type)
* @param param.nodeId - node id - if not provided, the node id from the NodeIdContext is used * @param param.nodeId - node id - if not provided, the node id from the NodeIdContext is used
* @param param.id - the handle id (this is only needed if the node has multiple handles of the same type)
* @param param.onConnect - gets called when a connection is established * @param param.onConnect - gets called when a connection is established
* @param param.onDisconnect - gets called when a connection is removed * @param param.onDisconnect - gets called when a connection is removed
* @returns an array with connections * @returns an array with connections
+12 -4
View File
@@ -12,11 +12,19 @@ export type UseKeyPressOptions = {
const defaultDoc = typeof document !== 'undefined' ? document : null; const defaultDoc = typeof document !== 'undefined' ? document : null;
// the keycode can be a string 'a' or an array of strings ['a', 'a+d'] /**
// a string means a single key 'a' or a combination when '+' is used 'a+d' * Hook for handling key events.
// an array means different possibilites. Explainer: ['a', 'd+s'] here the *
// user can use the single key 'a' or the combination 'd' + 's' * @public
* @param param.keyCode - The key code (string or array of strings) to use
* @param param.options - Options
* @returns boolean
*/
export default ( export default (
// the keycode can be a string 'a' or an array of strings ['a', 'a+d']
// a string means a single key 'a' or a combination when '+' is used 'a+d'
// an array means different possibilites. Explainer: ['a', 'd+s'] here the
// user can use the single key 'a' or the combination 'd' + 's'
keyCode: KeyCode | null = null, keyCode: KeyCode | null = null,
options: UseKeyPressOptions = { target: defaultDoc, actInsideInputWithModifier: true } options: UseKeyPressOptions = { target: defaultDoc, actInsideInputWithModifier: true }
): boolean => { ): boolean => {
+8 -2
View File
@@ -5,8 +5,14 @@ import type { Node, ReactFlowState } from '../types';
const nodesSelector = (state: ReactFlowState) => state.nodes; const nodesSelector = (state: ReactFlowState) => state.nodes;
function useNodes<NodeData>(): Node<NodeData>[] { /**
const nodes = useStore(nodesSelector, shallow); * Hook for getting the current nodes from the store.
*
* @public
* @returns An array of nodes
*/
function useNodes<NodeType extends Node = Node>(): NodeType[] {
const nodes = useStore(nodesSelector, shallow) as NodeType[];
return nodes; return nodes;
} }
+8
View File
@@ -4,6 +4,14 @@ import { shallow } from 'zustand/shallow';
import { useStore } from '../hooks/useStore'; import { useStore } from '../hooks/useStore';
import type { Node } from '../types'; import type { Node } from '../types';
/**
* Hook for receiving data of one or multiple nodes
*
* @public
* @param nodeId - The id (or ids) of the node to get the data from
* @param guard - Optional guard function to narrow down the node type
* @returns An array od data objects
*/
export function useNodesData<NodeType extends Node = Node>(nodeId: string): NodeType['data'] | null; export function useNodesData<NodeType extends Node = Node>(nodeId: string): NodeType['data'] | null;
export function useNodesData<NodeType extends Node = Node>(nodeIds: string[]): NodeType['data'][]; export function useNodesData<NodeType extends Node = Node>(nodeIds: string[]): NodeType['data'][];
export function useNodesData<NodeType extends Node = Node>( export function useNodesData<NodeType extends Node = Node>(
+21 -8
View File
@@ -7,18 +7,16 @@ import type { Node, NodeChange, Edge, EdgeChange } from '../types';
type ApplyChanges<ItemType, ChangesType> = (changes: ChangesType[], items: ItemType[]) => ItemType[]; type ApplyChanges<ItemType, ChangesType> = (changes: ChangesType[], items: ItemType[]) => ItemType[];
type OnChange<ChangesType> = (changes: ChangesType[]) => void; type OnChange<ChangesType> = (changes: ChangesType[]) => void;
// returns a hook that can be used liked this:
// const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
function createUseItemsState( function createUseItemsState(
applyChanges: ApplyChanges<Node, NodeChange> applyChanges: ApplyChanges<Node, NodeChange>
): <NodeData = any>( ): <NodeType extends Node = Node>(
initialItems: Node<NodeData>[] initialItems: NodeType[]
) => [Node<NodeData>[], Dispatch<SetStateAction<Node<NodeData>[]>>, OnChange<NodeChange>]; ) => [NodeType[], Dispatch<SetStateAction<NodeType[]>>, OnChange<NodeChange>];
function createUseItemsState( function createUseItemsState(
applyChanges: ApplyChanges<Edge, EdgeChange> applyChanges: ApplyChanges<Edge, EdgeChange>
): <EdgeData = any>( ): <EdgeType extends Edge = Edge>(
initialItems: Edge<EdgeData>[] initialItems: EdgeType[]
) => [Edge<EdgeData>[], Dispatch<SetStateAction<Edge<EdgeData>[]>>, OnChange<EdgeChange>]; ) => [EdgeType[], Dispatch<SetStateAction<EdgeType[]>>, OnChange<EdgeChange>];
function createUseItemsState( function createUseItemsState(
applyChanges: ApplyChanges<any, any> applyChanges: ApplyChanges<any, any>
): (initialItems: any[]) => [any[], Dispatch<SetStateAction<any[]>>, OnChange<any>] { ): (initialItems: any[]) => [any[], Dispatch<SetStateAction<any[]>>, OnChange<any>] {
@@ -31,5 +29,20 @@ function createUseItemsState(
}; };
} }
/**
* Hook for managing the state of nodes - should only be used for prototyping / simple use cases.
*
* @public
* @param initialNodes
* @returns an array [nodes, setNodes, onNodesChange]
*/
export const useNodesState = createUseItemsState(applyNodeChanges); export const useNodesState = createUseItemsState(applyNodeChanges);
/**
* Hook for managing the state of edges - should only be used for prototyping / simple use cases.
*
* @public
* @param initialEdges
* @returns an array [edges, setEdges, onEdgesChange]
*/
export const useEdgesState = createUseItemsState(applyEdgeChanges); export const useEdgesState = createUseItemsState(applyEdgeChanges);
@@ -21,6 +21,13 @@ const defaultOptions = {
includeHiddenNodes: false, includeHiddenNodes: false,
}; };
/**
* Hook which returns true when all nodes are initialized.
*
* @public
* @param options.includeHiddenNodes - defaults to false
* @returns boolean indicating whether all nodes are initialized
*/
function useNodesInitialized(options: UseNodesInitializedOptions = defaultOptions): boolean { function useNodesInitialized(options: UseNodesInitializedOptions = defaultOptions): boolean {
const initialized = useStore(selector(options)); const initialized = useStore(selector(options));
@@ -3,6 +3,11 @@ import { useEffect, useRef } from 'react';
import useReactFlow from './useReactFlow'; import useReactFlow from './useReactFlow';
import type { OnInit } from '../types'; import type { OnInit } from '../types';
/**
* Hook for calling onInit handler.
*
* @internal
*/
function useOnInitHandler(onInit: OnInit | undefined) { function useOnInitHandler(onInit: OnInit | undefined) {
const rfInstance = useReactFlow(); const rfInstance = useReactFlow();
const isInitialized = useRef<boolean>(false); const isInitialized = useRef<boolean>(false);
@@ -7,6 +7,12 @@ export type UseOnSelectionChangeOptions = {
onChange: OnSelectionChangeFunc; onChange: OnSelectionChangeFunc;
}; };
/**
* Hook for registering an onSelectionChange handler.
*
* @public
* @params params.onChange - The handler to register
*/
function useOnSelectionChange({ onChange }: UseOnSelectionChangeOptions) { function useOnSelectionChange({ onChange }: UseOnSelectionChangeOptions) {
const store = useStoreApi(); const store = useStoreApi();
@@ -9,6 +9,14 @@ export type UseOnViewportChangeOptions = {
onEnd?: OnViewportChange; onEnd?: OnViewportChange;
}; };
/**
* Hook for registering an onViewportChange handler.
*
* @public
* @param params.onStart - gets called when the viewport starts changing
* @param params.onChange - gets called when the viewport changes
* @param params.onEnd - gets called when the viewport stops changing
*/
function useOnViewportChange({ onStart, onChange, onEnd }: UseOnViewportChangeOptions) { function useOnViewportChange({ onStart, onChange, onEnd }: UseOnViewportChangeOptions) {
const store = useStoreApi(); const store = useStoreApi();
+43 -84
View File
@@ -1,13 +1,5 @@
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { import { getElementsToRemove, getOverlappingArea, isRectObject, nodeToRect, type Rect } from '@xyflow/system';
getElementsToRemove,
getIncomersBase,
getOutgoersBase,
getOverlappingArea,
isRectObject,
nodeToRect,
type Rect,
} from '@xyflow/system';
import useViewportHelper from './useViewportHelper'; import useViewportHelper from './useViewportHelper';
import { useStoreApi } from './useStore'; import { useStoreApi } from './useStore';
@@ -26,32 +18,40 @@ import type {
} from '../types'; } from '../types';
import { isNode } from '../utils'; import { isNode } from '../utils';
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ /**
export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlowInstance<NodeData, EdgeData> { * Hook for accessing the ReactFlow instance.
*
* @public
* @returns ReactFlowInstance
*/
export default function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(): ReactFlowInstance<
NodeType,
EdgeType
> {
const viewportHelper = useViewportHelper(); const viewportHelper = useViewportHelper();
const store = useStoreApi(); const store = useStoreApi();
const getNodes = useCallback<Instance.GetNodes<NodeData>>(() => { const getNodes = useCallback<Instance.GetNodes<NodeType>>(() => {
return store.getState().nodes.map((n) => ({ ...n })); return store.getState().nodes.map((n) => ({ ...n })) as NodeType[];
}, []); }, []);
const getNode = useCallback<Instance.GetNode<NodeData>>((id) => { const getNode = useCallback<Instance.GetNode<NodeType>>((id) => {
return store.getState().nodeLookup.get(id); return store.getState().nodeLookup.get(id) as NodeType;
}, []); }, []);
const getEdges = useCallback<Instance.GetEdges<EdgeData>>(() => { const getEdges = useCallback<Instance.GetEdges<EdgeType>>(() => {
const { edges = [] } = store.getState(); const { edges = [] } = store.getState();
return edges.map((e) => ({ ...e })); return edges.map((e) => ({ ...e })) as EdgeType[];
}, []); }, []);
const getEdge = useCallback<Instance.GetEdge<EdgeData>>((id) => { const getEdge = useCallback<Instance.GetEdge<EdgeType>>((id) => {
const { edges = [] } = store.getState(); const { edges = [] } = store.getState();
return edges.find((e) => e.id === id); return edges.find((e) => e.id === id) as EdgeType;
}, []); }, []);
const setNodes = useCallback<Instance.SetNodes<NodeData>>((payload) => { const setNodes = useCallback<Instance.SetNodes<NodeType>>((payload) => {
const { nodes, setNodes, hasDefaultNodes, onNodesChange } = store.getState(); const { nodes, setNodes, hasDefaultNodes, onNodesChange } = store.getState();
const nextNodes = typeof payload === 'function' ? payload(nodes) : payload; const nextNodes = typeof payload === 'function' ? payload(nodes as NodeType[]) : payload;
if (hasDefaultNodes) { if (hasDefaultNodes) {
setNodes(nextNodes); setNodes(nextNodes);
@@ -59,14 +59,14 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
const changes = const changes =
nextNodes.length === 0 nextNodes.length === 0
? nodes.map((node) => ({ type: 'remove', id: node.id } as NodeRemoveChange)) ? nodes.map((node) => ({ type: 'remove', id: node.id } as NodeRemoveChange))
: nextNodes.map((node) => ({ item: node, type: 'reset' } as NodeResetChange<NodeData>)); : nextNodes.map((node) => ({ item: node, type: 'reset' } as NodeResetChange<NodeType>));
onNodesChange(changes); onNodesChange(changes);
} }
}, []); }, []);
const setEdges = useCallback<Instance.SetEdges<EdgeData>>((payload) => { const setEdges = useCallback<Instance.SetEdges<EdgeType>>((payload) => {
const { edges = [], setEdges, hasDefaultEdges, onEdgesChange } = store.getState(); const { edges = [], setEdges, hasDefaultEdges, onEdgesChange } = store.getState();
const nextEdges = typeof payload === 'function' ? payload(edges) : payload; const nextEdges = typeof payload === 'function' ? payload(edges as EdgeType[]) : payload;
if (hasDefaultEdges) { if (hasDefaultEdges) {
setEdges(nextEdges); setEdges(nextEdges);
@@ -74,12 +74,12 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
const changes = const changes =
nextEdges.length === 0 nextEdges.length === 0
? edges.map((edge) => ({ type: 'remove', id: edge.id } as EdgeRemoveChange)) ? edges.map((edge) => ({ type: 'remove', id: edge.id } as EdgeRemoveChange))
: nextEdges.map((edge) => ({ item: edge, type: 'reset' } as EdgeResetChange<EdgeData>)); : nextEdges.map((edge) => ({ item: edge, type: 'reset' } as EdgeResetChange<EdgeType>));
onEdgesChange(changes); onEdgesChange(changes);
} }
}, []); }, []);
const addNodes = useCallback<Instance.AddNodes<NodeData>>((payload) => { const addNodes = useCallback<Instance.AddNodes<NodeType>>((payload) => {
const nodes = Array.isArray(payload) ? payload : [payload]; const nodes = Array.isArray(payload) ? payload : [payload];
const { nodes: currentNodes, hasDefaultNodes, onNodesChange, setNodes } = store.getState(); const { nodes: currentNodes, hasDefaultNodes, onNodesChange, setNodes } = store.getState();
@@ -87,29 +87,29 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
const nextNodes = [...currentNodes, ...nodes]; const nextNodes = [...currentNodes, ...nodes];
setNodes(nextNodes); setNodes(nextNodes);
} else if (onNodesChange) { } else if (onNodesChange) {
const changes = nodes.map((node) => ({ item: node, type: 'add' } as NodeAddChange<NodeData>)); const changes = nodes.map((node) => ({ item: node, type: 'add' } as NodeAddChange<NodeType>));
onNodesChange(changes); onNodesChange(changes);
} }
}, []); }, []);
const addEdges = useCallback<Instance.AddEdges<EdgeData>>((payload) => { const addEdges = useCallback<Instance.AddEdges<EdgeType>>((payload) => {
const nextEdges = Array.isArray(payload) ? payload : [payload]; const nextEdges = Array.isArray(payload) ? payload : [payload];
const { edges = [], setEdges, hasDefaultEdges, onEdgesChange } = store.getState(); const { edges = [], setEdges, hasDefaultEdges, onEdgesChange } = store.getState();
if (hasDefaultEdges) { if (hasDefaultEdges) {
setEdges([...edges, ...nextEdges]); setEdges([...edges, ...nextEdges]);
} else if (onEdgesChange) { } else if (onEdgesChange) {
const changes = nextEdges.map((edge) => ({ item: edge, type: 'add' } as EdgeAddChange<EdgeData>)); const changes = nextEdges.map((edge) => ({ item: edge, type: 'add' } as EdgeAddChange<EdgeType>));
onEdgesChange(changes); onEdgesChange(changes);
} }
}, []); }, []);
const toObject = useCallback<Instance.ToObject<NodeData, EdgeData>>(() => { const toObject = useCallback<Instance.ToObject<NodeType, EdgeType>>(() => {
const { nodes = [], edges = [], transform } = store.getState(); const { nodes = [], edges = [], transform } = store.getState();
const [x, y, zoom] = transform; const [x, y, zoom] = transform;
return { return {
nodes: nodes.map((n) => ({ ...n })), nodes: nodes.map((n) => ({ ...n })) as NodeType[],
edges: edges.map((e) => ({ ...e })), edges: edges.map((e) => ({ ...e })) as EdgeType[],
viewport: { viewport: {
x, x,
y, y,
@@ -181,11 +181,9 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
}, []); }, []);
const getNodeRect = useCallback( const getNodeRect = useCallback(
( (nodeOrRect: NodeType | { id: Node['id'] } | Rect): [Rect | null, NodeType | null | undefined, boolean] => {
nodeOrRect: Node<NodeData> | { id: Node['id'] } | Rect
): [Rect | null, Node<NodeData> | null | undefined, boolean] => {
const isRect = isRectObject(nodeOrRect); const isRect = isRectObject(nodeOrRect);
const node = isRect ? null : store.getState().nodeLookup.get(nodeOrRect.id); const node = isRect ? null : (store.getState().nodeLookup.get(nodeOrRect.id) as NodeType);
if (!isRect && !node) { if (!isRect && !node) {
[null, null, isRect]; [null, null, isRect];
@@ -198,7 +196,7 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
[] []
); );
const getIntersectingNodes = useCallback<Instance.GetIntersectingNodes<NodeData>>( const getIntersectingNodes = useCallback<Instance.GetIntersectingNodes<NodeType>>(
(nodeOrRect, partially = true, nodes) => { (nodeOrRect, partially = true, nodes) => {
const [nodeRect, node, isRect] = getNodeRect(nodeOrRect); const [nodeRect, node, isRect] = getNodeRect(nodeOrRect);
@@ -216,12 +214,12 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
const partiallyVisible = partially && overlappingArea > 0; const partiallyVisible = partially && overlappingArea > 0;
return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height; return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
}); }) as NodeType[];
}, },
[] []
); );
const isNodeIntersecting = useCallback<Instance.IsNodeIntersecting<NodeData>>( const isNodeIntersecting = useCallback<Instance.IsNodeIntersecting<NodeType>>(
(nodeOrRect, area, partially = true) => { (nodeOrRect, area, partially = true) => {
const [nodeRect] = getNodeRect(nodeOrRect); const [nodeRect] = getNodeRect(nodeOrRect);
@@ -237,48 +235,13 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
[] []
); );
const getConnectedEdges = useCallback<Instance.getConnectedEdges>((node) => { const updateNode = useCallback<Instance.UpdateNode<NodeType>>(
const { edges } = store.getState();
const nodeIds = new Set();
if (typeof node === 'string') {
nodeIds.add(node);
} else if (node.length >= 1) {
node.forEach((n) => {
nodeIds.add(n.id);
});
}
return edges.filter((edge) => nodeIds.has(edge.source) || nodeIds.has(edge.target));
}, []);
const getIncomers = useCallback<Instance.getIncomers>((node) => {
const { nodes, edges } = store.getState();
if (typeof node === 'string') {
return getIncomersBase({ id: node }, nodes, edges);
}
return getIncomersBase(node, nodes, edges);
}, []);
const getOutgoers = useCallback<Instance.getOutgoers>((node) => {
const { nodes, edges } = store.getState();
if (typeof node == 'string') {
return getOutgoersBase({ id: node }, nodes, edges);
}
return getOutgoersBase(node, nodes, edges);
}, []);
const updateNode = useCallback<Instance.UpdateNode>(
(id, nodeUpdate, options = { replace: true }) => { (id, nodeUpdate, options = { replace: true }) => {
setNodes((prevNodes) => setNodes((prevNodes) =>
prevNodes.map((node) => { prevNodes.map((node) => {
if (node.id === id) { if (node.id === id) {
const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node as Node) : nodeUpdate; const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node as NodeType) : nodeUpdate;
return options.replace && isNode(nextNode) ? nextNode : { ...node, ...nextNode }; return options.replace && isNode(nextNode) ? (nextNode as NodeType) : { ...node, ...nextNode };
} }
return node; return node;
@@ -288,7 +251,7 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
[setNodes] [setNodes]
); );
const updateNodeData = useCallback<Instance.UpdateNodeData>( const updateNodeData = useCallback<Instance.UpdateNodeData<NodeType>>(
(id, dataUpdate, options = { replace: false }) => { (id, dataUpdate, options = { replace: false }) => {
updateNode( updateNode(
id, id,
@@ -317,9 +280,6 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
deleteElements, deleteElements,
getIntersectingNodes, getIntersectingNodes,
isNodeIntersecting, isNodeIntersecting,
getConnectedEdges,
getIncomers,
getOutgoers,
updateNode, updateNode,
updateNodeData, updateNodeData,
}; };
@@ -337,8 +297,7 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
deleteElements, deleteElements,
getIntersectingNodes, getIntersectingNodes,
isNodeIntersecting, isNodeIntersecting,
getConnectedEdges, updateNode,
getIncomers, updateNodeData,
getOutgoers,
]); ]);
} }
@@ -3,6 +3,11 @@ import { errorMessages, getDimensions } from '@xyflow/system';
import { useStoreApi } from '../hooks/useStore'; import { useStoreApi } from '../hooks/useStore';
/**
* Hook for handling resize events.
*
* @internal
*/
function useResizeHandler(domNode: MutableRefObject<HTMLDivElement | null>): void { function useResizeHandler(domNode: MutableRefObject<HTMLDivElement | null>): void {
const store = useStoreApi(); const store = useStoreApi();
+8
View File
@@ -10,6 +10,14 @@ const zustandErrorMessage = errorMessages['error001']();
type ExtractState = StoreApi<ReactFlowState> extends { getState: () => infer T } ? T : never; type ExtractState = StoreApi<ReactFlowState> extends { getState: () => infer T } ? T : never;
/**
* Hook for accessing the internal store. Should only be used in rare cases.
*
* @public
* @param selector
* @param equalityFn
* @returns The selected state slice
*/
function useStore<StateSlice = ExtractState>( function useStore<StateSlice = ExtractState>(
selector: (state: ReactFlowState) => StateSlice, selector: (state: ReactFlowState) => StateSlice,
equalityFn?: (a: StateSlice, b: StateSlice) => boolean equalityFn?: (a: StateSlice, b: StateSlice) => boolean
@@ -3,6 +3,12 @@ import type { UpdateNodeInternals, NodeDimensionUpdate } from '@xyflow/system';
import { useStoreApi } from '../hooks/useStore'; import { useStoreApi } from '../hooks/useStore';
/**
* Hook for updating node internals.
*
* @public
* @returns function for updating node internals
*/
function useUpdateNodeInternals(): UpdateNodeInternals { function useUpdateNodeInternals(): UpdateNodeInternals {
const store = useStoreApi(); const store = useStoreApi();
@@ -7,6 +7,12 @@ import { useStoreApi } from '../hooks/useStore';
const selectedAndDraggable = (nodesDraggable: boolean) => (n: Node) => const selectedAndDraggable = (nodesDraggable: boolean) => (n: Node) =>
n.selected && (n.draggable || (nodesDraggable && typeof n.draggable === 'undefined')); n.selected && (n.draggable || (nodesDraggable && typeof n.draggable === 'undefined'));
/**
* Hook for updating node positions.
*
* @internal
* @returns function for updating node positions
*/
function useUpdateNodePositions() { function useUpdateNodePositions() {
const store = useStoreApi(); const store = useStoreApi();
+6
View File
@@ -10,6 +10,12 @@ const viewportSelector = (state: ReactFlowState) => ({
zoom: state.transform[2], zoom: state.transform[2],
}); });
/**
* Hook for getting the current viewport from the store.
*
* @public
* @returns The current viewport
*/
function useViewport(): Viewport { function useViewport(): Viewport {
const viewport = useStore(viewportSelector, shallow); const viewport = useStore(viewportSelector, shallow);
@@ -12,6 +12,12 @@ import type { ViewportHelperFunctions, ReactFlowState } from '../types';
const selector = (s: ReactFlowState) => !!s.panZoom; const selector = (s: ReactFlowState) => !!s.panZoom;
/**
* Hook for getting viewport helper functions.
*
* @internal
* @returns viewport helper functions
*/
const useViewportHelper = (): ViewportHelperFunctions => { const useViewportHelper = (): ViewportHelperFunctions => {
const store = useStoreApi(); const store = useStoreApi();
const panZoomInitialized = useStore(selector); const panZoomInitialized = useStore(selector);
@@ -6,6 +6,12 @@ import type { ReactFlowState } from '../types';
const selector = (state: ReactFlowState) => state.panZoom?.syncViewport; const selector = (state: ReactFlowState) => state.panZoom?.syncViewport;
/**
* Hook for syncing the viewport with the panzoom instance.
*
* @internal
* @param viewport
*/
export default function useViewportSync(viewport?: Viewport) { export default function useViewportSync(viewport?: Viewport) {
const syncViewport = useStore(selector); const syncViewport = useStore(selector);
const store = useStoreApi(); const store = useStoreApi();
@@ -5,6 +5,13 @@ import { isEdgeVisible } from '@xyflow/system';
import { useStore } from './useStore'; import { useStore } from './useStore';
import { type ReactFlowState } from '../types'; import { type ReactFlowState } from '../types';
/**
* Hook for getting the visible edge ids from the store.
*
* @internal
* @param onlyRenderVisible
* @returns array with visible edge ids
*/
function useVisibleEdgeIds(onlyRenderVisible: boolean): string[] { function useVisibleEdgeIds(onlyRenderVisible: boolean): string[] {
const edgeIds = useStore( const edgeIds = useStore(
useCallback( useCallback(
@@ -13,6 +13,13 @@ const selector = (onlyRenderVisible: boolean) => (s: ReactFlowState) => {
: Array.from(s.nodeLookup.keys()); : Array.from(s.nodeLookup.keys());
}; };
/**
* Hook for getting the visible node ids from the store.
*
* @internal
* @param onlyRenderVisible
* @returns array with visible node ids
*/
function useVisibleNodeIds(onlyRenderVisible: boolean) { function useVisibleNodeIds(onlyRenderVisible: boolean) {
const nodeIds = useStore(useCallback(selector(onlyRenderVisible), [onlyRenderVisible]), shallow); const nodeIds = useStore(useCallback(selector(onlyRenderVisible), [onlyRenderVisible]), shallow);
+12 -8
View File
@@ -30,16 +30,20 @@ export type NodeRemoveChange = {
type: 'remove'; type: 'remove';
}; };
export type NodeAddChange<NodeData = any> = { export type NodeAddChange<NodeType extends Node = Node> = {
item: Node<NodeData>; item: NodeType;
type: 'add'; type: 'add';
}; };
export type NodeResetChange<NodeData = any> = { export type NodeResetChange<NodeType extends Node = Node> = {
item: Node<NodeData>; item: NodeType;
type: 'reset'; type: 'reset';
}; };
/**
* Union type of all possible node changes.
* @public
*/
export type NodeChange = export type NodeChange =
| NodeDimensionChange | NodeDimensionChange
| NodePositionChange | NodePositionChange
@@ -50,12 +54,12 @@ export type NodeChange =
export type EdgeSelectionChange = NodeSelectionChange; export type EdgeSelectionChange = NodeSelectionChange;
export type EdgeRemoveChange = NodeRemoveChange; export type EdgeRemoveChange = NodeRemoveChange;
export type EdgeAddChange<EdgeData = any> = { export type EdgeAddChange<EdgeType extends Edge = Edge> = {
item: Edge<EdgeData>; item: EdgeType;
type: 'add'; type: 'add';
}; };
export type EdgeResetChange<EdgeData = any> = { export type EdgeResetChange<EdgeType extends Edge = Edge> = {
item: Edge<EdgeData>; item: EdgeType;
type: 'reset'; type: 'reset';
}; };
export type EdgeChange = EdgeSelectionChange | EdgeRemoveChange | EdgeAddChange | EdgeResetChange; export type EdgeChange = EdgeSelectionChange | EdgeRemoveChange | EdgeAddChange | EdgeResetChange;
+6 -1
View File
@@ -20,6 +20,7 @@ import type {
OnError, OnError,
IsValidConnection, IsValidConnection,
ColorMode, ColorMode,
SnapGrid,
} from '@xyflow/system'; } from '@xyflow/system';
import type { import type {
@@ -44,6 +45,10 @@ import type {
EdgeMouseHandler, EdgeMouseHandler,
} from '.'; } from '.';
/**
* ReactFlow component props.
* @public
*/
export type ReactFlowProps = Omit<HTMLAttributes<HTMLDivElement>, 'onError'> & { export type ReactFlowProps = Omit<HTMLAttributes<HTMLDivElement>, 'onError'> & {
nodes?: Node[]; nodes?: Node[];
edges?: Edge[]; edges?: Edge[];
@@ -110,7 +115,7 @@ export type ReactFlowProps = Omit<HTMLAttributes<HTMLDivElement>, 'onError'> & {
multiSelectionKeyCode?: KeyCode | null; multiSelectionKeyCode?: KeyCode | null;
zoomActivationKeyCode?: KeyCode | null; zoomActivationKeyCode?: KeyCode | null;
snapToGrid?: boolean; snapToGrid?: boolean;
snapGrid?: [number, number]; snapGrid?: SnapGrid;
onlyRenderVisibleElements?: boolean; onlyRenderVisibleElements?: boolean;
nodesDraggable?: boolean; nodesDraggable?: boolean;
nodesConnectable?: boolean; nodesConnectable?: boolean;
+73 -27
View File
@@ -13,7 +13,6 @@ import type {
HandleElement, HandleElement,
ConnectionStatus, ConnectionStatus,
EdgePosition, EdgePosition,
Optional,
StepPathOptions, StepPathOptions,
OnError, OnError,
} from '@xyflow/system'; } from '@xyflow/system';
@@ -31,12 +30,13 @@ export type EdgeLabelOptions = {
export type EdgeUpdatable = boolean | HandleType; export type EdgeUpdatable = boolean | HandleType;
export type DefaultEdge<EdgeData = any> = EdgeBase<EdgeData> & { export type DefaultEdge<EdgeData = any> = EdgeBase<EdgeData> &
style?: CSSProperties; EdgeLabelOptions & {
className?: string; style?: CSSProperties;
updatable?: EdgeUpdatable; className?: string;
focusable?: boolean; updatable?: EdgeUpdatable;
} & EdgeLabelOptions; focusable?: boolean;
};
type SmoothStepEdgeType<T> = DefaultEdge<T> & { type SmoothStepEdgeType<T> = DefaultEdge<T> & {
type: 'smoothstep'; type: 'smoothstep';
@@ -53,6 +53,10 @@ type StepEdgeType<T> = DefaultEdge<T> & {
pathOptions?: StepPathOptions; pathOptions?: StepPathOptions;
}; };
/**
* The Edge type is mainly used for the `edges` that get passed to the ReactFlow component
* @public
*/
export type Edge<T = any> = DefaultEdge<T> | SmoothStepEdgeType<T> | BezierEdgeType<T> | StepEdgeType<T>; export type Edge<T = any> = DefaultEdge<T> | SmoothStepEdgeType<T> | BezierEdgeType<T> | StepEdgeType<T>;
export type EdgeMouseHandler = (event: ReactMouseEvent, edge: Edge) => void; export type EdgeMouseHandler = (event: ReactMouseEvent, edge: Edge) => void;
@@ -88,14 +92,18 @@ export type EdgeTextProps = HTMLAttributes<SVGElement> &
y: number; y: number;
}; };
// props that get passed to a custom edge /**
* Custom edge component props
* @public
*/
export type EdgeProps<T = any> = Pick< export type EdgeProps<T = any> = Pick<
Edge<T>, Edge<T>,
'id' | 'animated' | 'data' | 'style' | 'selected' | 'source' | 'target' 'id' | 'animated' | 'data' | 'style' | 'selected' | 'source' | 'target'
> & > &
Pick<EdgeWrapperProps, 'sourceHandleId' | 'targetHandleId'> &
EdgePosition & EdgePosition &
EdgeLabelOptions & { EdgeLabelOptions & {
sourceHandleId?: string | null;
targetHandleId?: string | null;
markerStart?: string; markerStart?: string;
markerEnd?: string; markerEnd?: string;
// @TODO: how can we get better types for pathOptions? // @TODO: how can we get better types for pathOptions?
@@ -103,31 +111,69 @@ export type EdgeProps<T = any> = Pick<
interactionWidth?: number; interactionWidth?: number;
}; };
export type BaseEdgeProps = Pick<EdgeProps, 'style' | 'markerStart' | 'markerEnd' | 'interactionWidth'> & /**
* BaseEdge component props
* @public
*/
export type BaseEdgeProps = EdgeLabelOptions & {
id?: string;
interactionWidth?: number;
labelX?: number;
labelY?: number;
markerStart?: string;
markerEnd?: string;
path: string;
style?: CSSProperties;
};
/**
* Helper type for edge components that get exported by the library
* @public
*/
export type EdgeComponentProps = EdgePosition &
EdgeLabelOptions & { EdgeLabelOptions & {
id?: string; id?: EdgeProps['id'];
labelX?: number; markerStart?: EdgeProps['markerStart'];
labelY?: number; markerEnd?: EdgeProps['markerEnd'];
path: string; interactionWidth?: EdgeProps['interactionWidth'];
style?: EdgeProps['style'];
sourceHandleId?: EdgeProps['sourceHandleId'];
targetHandleId?: EdgeProps['targetHandleId'];
}; };
export type EdgeComponentProps<T = any> = Optional<Omit<EdgeProps<T>, 'source' | 'target'>, 'id'>; export type EdgeComponentWithPathOptions<PathOptions> = EdgeComponentProps & {
pathOptions?: PathOptions;
export type StraightEdgeProps<T = any> = Omit<EdgeComponentProps<T>, 'sourcePosition' | 'targetPosition'>;
export type SmoothStepEdgeProps<T = any> = EdgeComponentProps<T> & {
pathOptions?: SmoothStepPathOptions;
}; };
export type BezierEdgeProps<T = any> = EdgeComponentProps<T> & { /**
pathOptions?: BezierPathOptions; * BezierEdge component props
}; * @public
*/
export type BezierEdgeProps = EdgeComponentWithPathOptions<BezierPathOptions>;
export type StepEdgeProps<T = any> = EdgeComponentProps<T> & { /**
pathOptions?: StepPathOptions; * SmoothStepEdge component props
}; * @public
*/
export type SmoothStepEdgeProps = EdgeComponentWithPathOptions<SmoothStepPathOptions>;
export type SimpleBezierEdgeProps<T = any> = EdgeComponentProps<T>; /**
* StepEdge component props
* @public
*/
export type StepEdgeProps = EdgeComponentWithPathOptions<StepPathOptions>;
/**
* StraightEdge component props
* @public
*/
export type StraightEdgeProps = Omit<EdgeComponentProps, 'sourcePosition' | 'targetPosition'>;
/**
* SimpleBezier component props
* @public
*/
export type SimpleBezierEdgeProps = EdgeComponentProps;
export type OnEdgeUpdateFunc<T = any> = (oldEdge: Edge<T>, newConnection: Connection) => void; export type OnEdgeUpdateFunc<T = any> = (oldEdge: Edge<T>, newConnection: Connection) => void;
+6 -4
View File
@@ -1,9 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import type { ComponentType } from 'react';
import { import {
FitViewParamsBase, FitViewParamsBase,
FitViewOptionsBase, FitViewOptionsBase,
NodeProps,
ZoomInOut, ZoomInOut,
ZoomTo, ZoomTo,
SetViewport, SetViewport,
@@ -12,9 +10,11 @@ import {
SetCenter, SetCenter,
FitBounds, FitBounds,
XYPosition, XYPosition,
NodeProps,
} from '@xyflow/system'; } from '@xyflow/system';
import type { NodeChange, EdgeChange, Node, Edge, EdgeProps, ReactFlowInstance } from '.'; import type { NodeChange, EdgeChange, Node, Edge, ReactFlowInstance, EdgeProps } from '.';
import { ComponentType } from 'react';
export type OnNodesChange = (changes: NodeChange[]) => void; export type OnNodesChange = (changes: NodeChange[]) => void;
export type OnEdgesChange = (changes: EdgeChange[]) => void; export type OnEdgesChange = (changes: EdgeChange[]) => void;
@@ -41,7 +41,9 @@ export type OnSelectionChangeFunc = (params: OnSelectionChangeParams) => void;
export type FitViewParams = FitViewParamsBase<Node>; export type FitViewParams = FitViewParamsBase<Node>;
export type FitViewOptions = FitViewOptionsBase<Node>; export type FitViewOptions = FitViewOptionsBase<Node>;
export type FitView = (fitViewOptions?: FitViewOptions) => boolean; export type FitView = (fitViewOptions?: FitViewOptions) => boolean;
export type OnInit<NodeData = any, EdgeData = any> = (reactFlowInstance: ReactFlowInstance<NodeData, EdgeData>) => void; export type OnInit<NodeType extends Node = Node, EdgeType extends Edge = Edge> = (
reactFlowInstance: ReactFlowInstance<NodeType, EdgeType>
) => void;
export type ViewportHelperFunctions = { export type ViewportHelperFunctions = {
zoomIn: ZoomInOut; zoomIn: ZoomInOut;
+41 -38
View File
@@ -3,9 +3,9 @@
import type { Rect, Viewport } from '@xyflow/system'; import type { Rect, Viewport } from '@xyflow/system';
import type { Node, Edge, ViewportHelperFunctions } from '.'; import type { Node, Edge, ViewportHelperFunctions } from '.';
export type ReactFlowJsonObject<NodeData = any, EdgeData = any> = { export type ReactFlowJsonObject<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
nodes: Node<NodeData>[]; nodes: NodeType[];
edges: Edge<EdgeData>[]; edges: EdgeType[];
viewport: Viewport; viewport: Viewport;
}; };
@@ -15,30 +15,33 @@ export type DeleteElementsOptions = {
}; };
export namespace Instance { export namespace Instance {
export type GetNodes<NodeData> = () => Node<NodeData>[]; export type GetNodes<NodeType extends Node = Node> = () => NodeType[];
export type SetNodes<NodeData> = ( export type SetNodes<NodeType extends Node = Node> = (
payload: Node<NodeData>[] | ((nodes: Node<NodeData>[]) => Node<NodeData>[]) payload: NodeType[] | ((nodes: NodeType[]) => NodeType[])
) => void; ) => void;
export type AddNodes<NodeData> = (payload: Node<NodeData>[] | Node<NodeData>) => void; export type AddNodes<NodeType extends Node = Node> = (payload: NodeType[] | NodeType) => void;
export type GetNode<NodeData> = (id: string) => Node<NodeData> | undefined; export type GetNode<NodeType extends Node = Node> = (id: string) => NodeType | undefined;
export type GetEdges<EdgeData> = () => Edge<EdgeData>[]; export type GetEdges<EdgeType extends Edge = Edge> = () => EdgeType[];
export type SetEdges<EdgeData> = ( export type SetEdges<EdgeType extends Edge = Edge> = (
payload: Edge<EdgeData>[] | ((edges: Edge<EdgeData>[]) => Edge<EdgeData>[]) payload: EdgeType[] | ((edges: EdgeType[]) => EdgeType[])
) => void; ) => void;
export type GetEdge<EdgeData> = (id: string) => Edge<EdgeData> | undefined; export type GetEdge<EdgeType extends Edge = Edge> = (id: string) => EdgeType | undefined;
export type AddEdges<EdgeData> = (payload: Edge<EdgeData>[] | Edge<EdgeData>) => void; export type AddEdges<EdgeType extends Edge = Edge> = (payload: EdgeType[] | EdgeType) => void;
export type ToObject<NodeData = any, EdgeData = any> = () => ReactFlowJsonObject<NodeData, EdgeData>; export type ToObject<NodeType extends Node = Node, EdgeType extends Edge = Edge> = () => ReactFlowJsonObject<
NodeType,
EdgeType
>;
export type DeleteElements = ({ nodes, edges }: DeleteElementsOptions) => { export type DeleteElements = ({ nodes, edges }: DeleteElementsOptions) => {
deletedNodes: Node[]; deletedNodes: Node[];
deletedEdges: Edge[]; deletedEdges: Edge[];
}; };
export type GetIntersectingNodes<NodeData> = ( export type GetIntersectingNodes<NodeType extends Node = Node> = (
node: Node<NodeData> | { id: Node['id'] } | Rect, node: NodeType | { id: Node['id'] } | Rect,
partially?: boolean, partially?: boolean,
nodes?: Node<NodeData>[] nodes?: NodeType[]
) => Node<NodeData>[]; ) => NodeType[];
export type IsNodeIntersecting<NodeData> = ( export type IsNodeIntersecting<NodeType extends Node = Node> = (
node: Node<NodeData> | { id: Node['id'] } | Rect, node: NodeType | { id: Node['id'] } | Rect,
area: Rect, area: Rect,
partially?: boolean partially?: boolean
) => boolean; ) => boolean;
@@ -46,32 +49,32 @@ export namespace Instance {
export type getIncomers = (node: string | Node | { id: Node['id'] }) => Node[]; export type getIncomers = (node: string | Node | { id: Node['id'] }) => Node[];
export type getOutgoers = (node: string | Node | { id: Node['id'] }) => Node[]; export type getOutgoers = (node: string | Node | { id: Node['id'] }) => Node[];
export type UpdateNode = ( export type UpdateNode<NodeType extends Node = Node> = (
id: string, id: string,
dataUpdate: Partial<Node> | ((node: Node) => Partial<Node>), nodeUpdate: Partial<NodeType> | ((node: NodeType) => Partial<NodeType>),
options?: { replace: boolean } options?: { replace: boolean }
) => void; ) => void;
export type UpdateNodeData = ( export type UpdateNodeData<NodeType extends Node = Node> = (
id: string, id: string,
dataUpdate: object | ((node: Node) => object), dataUpdate: object | ((node: NodeType) => object),
options?: { replace: boolean } options?: { replace: boolean }
) => void; ) => void;
} }
export type ReactFlowInstance<NodeData = any, EdgeData = any> = { export type ReactFlowInstance<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
getNodes: Instance.GetNodes<NodeData>; getNodes: Instance.GetNodes<NodeType>;
setNodes: Instance.SetNodes<NodeData>; setNodes: Instance.SetNodes<NodeType>;
addNodes: Instance.AddNodes<NodeData>; addNodes: Instance.AddNodes<NodeType>;
getNode: Instance.GetNode<NodeData>; getNode: Instance.GetNode<NodeType>;
getEdges: Instance.GetEdges<EdgeData>; getEdges: Instance.GetEdges<EdgeType>;
setEdges: Instance.SetEdges<EdgeData>; setEdges: Instance.SetEdges<EdgeType>;
addEdges: Instance.AddEdges<EdgeData>; addEdges: Instance.AddEdges<EdgeType>;
getEdge: Instance.GetEdge<EdgeData>; getEdge: Instance.GetEdge<EdgeType>;
toObject: Instance.ToObject<NodeData, EdgeData>; toObject: Instance.ToObject<NodeType, EdgeType>;
deleteElements: Instance.DeleteElements; deleteElements: Instance.DeleteElements;
getIntersectingNodes: Instance.GetIntersectingNodes<NodeData>; getIntersectingNodes: Instance.GetIntersectingNodes<NodeType>;
isNodeIntersecting: Instance.IsNodeIntersecting<NodeData>; isNodeIntersecting: Instance.IsNodeIntersecting<NodeType>;
updateNode: Instance.UpdateNode; updateNode: Instance.UpdateNode<NodeType>;
updateNodeData: Instance.UpdateNodeData; updateNodeData: Instance.UpdateNodeData<NodeType>;
viewportInitialized: boolean; viewportInitialized: boolean;
} & Omit<ViewportHelperFunctions, 'initialized'>; } & Omit<ViewportHelperFunctions, 'initialized'>;
+6
View File
@@ -1,8 +1,13 @@
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react'; import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
import type { CoordinateExtent, NodeBase, NodeOrigin, OnError } from '@xyflow/system'; import type { CoordinateExtent, NodeBase, NodeOrigin, OnError } from '@xyflow/system';
import { NodeTypes } from './general'; import { NodeTypes } from './general';
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
/**
* The node data structure that gets used for the nodes prop.
* @public
*/
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
@@ -10,6 +15,7 @@ export type Node<NodeData = any, NodeType extends string | undefined = string |
style?: CSSProperties; style?: CSSProperties;
className?: string; className?: string;
resizing?: boolean; resizing?: boolean;
focusable?: boolean;
}; };
export type NodeMouseHandler = (event: ReactMouseEvent, node: Node) => void; export type NodeMouseHandler = (event: ReactMouseEvent, node: Node) => void;
+44 -4
View File
@@ -142,12 +142,52 @@ function applyChanges(changes: any[], elements: any[]): any[] {
return updatedElements; return updatedElements;
} }
export function applyNodeChanges<NodeData = any>(changes: NodeChange[], nodes: Node<NodeData>[]): Node<NodeData>[] { /**
return applyChanges(changes, nodes) as Node<NodeData>[]; * Drop in function that applies node changes to an array of nodes.
* @public
* @remarks Various events on the <ReactFlow /> component can produce an {@link NodeChange} that describes how to update the edges of your flow in some way.
If you don't need any custom behaviour, this util can be used to take an array of these changes and apply them to your edges.
* @param changes - Array of changes to apply
* @param nodes - Array of nodes to apply the changes to
* @returns Array of updated nodes
* @example
* const onNodesChange = useCallback(
(changes) => {
setNodes((oldNodes) => applyNodeChanges(changes, oldNodes));
},
[setNodes],
);
return (
<ReactFLow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
);
*/
export function applyNodeChanges<NodeType extends Node = Node>(changes: NodeChange[], nodes: NodeType[]): NodeType[] {
return applyChanges(changes, nodes) as NodeType[];
} }
export function applyEdgeChanges<EdgeData = any>(changes: EdgeChange[], edges: Edge<EdgeData>[]): Edge<EdgeData>[] { /**
return applyChanges(changes, edges) as Edge<EdgeData>[]; * Drop in function that applies edge changes to an array of edges.
* @public
* @remarks Various events on the <ReactFlow /> component can produce an {@link EdgeChange} that describes how to update the edges of your flow in some way.
If you don't need any custom behaviour, this util can be used to take an array of these changes and apply them to your edges.
* @param changes - Array of changes to apply
* @param edges - Array of edge to apply the changes to
* @returns Array of updated edges
* @example
* const onEdgesChange = useCallback(
(changes) => {
setEdges((oldEdges) => applyEdgeChanges(changes, oldEdges));
},
[setEdges],
);
return (
<ReactFLow nodes={nodes} edges={edges} onEdgesChange={onEdgesChange} />
);
*/
export function applyEdgeChanges<EdgeType extends Edge = Edge>(changes: EdgeChange[], edges: EdgeType[]): EdgeType[] {
return applyChanges(changes, edges) as EdgeType[];
} }
export const createSelectionChange = (id: string, selected: boolean): NodeSelectionChange | EdgeSelectionChange => ({ export const createSelectionChange = (id: string, selected: boolean): NodeSelectionChange | EdgeSelectionChange => ({
+58
View File
@@ -10,10 +10,68 @@ import {
import type { Edge, Node } from '../types'; import type { Edge, Node } from '../types';
/**
* Test whether an object is useable as a Node
* @public
* @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Node if it returns true
* @param element - The element to test
* @returns A boolean indicating whether the element is an Node
*/
export const isNode = isNodeBase<Node>; export const isNode = isNodeBase<Node>;
/**
* Test whether an object is useable as an Edge
* @public
* @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Edge if it returns true
* @param element - The element to test
* @returns A boolean indicating whether the element is an Edge
*/
export const isEdge = isEdgeBase<Edge>; export const isEdge = isEdgeBase<Edge>;
/**
* 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<Node, Edge>; export const getOutgoers = getOutgoersBase<Node, Edge>;
/**
* 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<Node, Edge>; export const getIncomers = getIncomersBase<Node, Edge>;
/**
* 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<Edge>; export const addEdge = addEdgeBase<Edge>;
/**
* 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<Edge>; export const updateEdge = updateEdgeBase<Edge>;
/**
* 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<Node, Edge>; export const getConnectedEdges = getConnectedEdgesBase<Node, Edge>;
@@ -9,6 +9,12 @@ function getMediaQuery() {
return window.matchMedia('(prefers-color-scheme: dark)'); return window.matchMedia('(prefers-color-scheme: dark)');
} }
/**
* Hook for receiving the current color mode class 'dark' or 'light'.
*
* @internal
* @param colorMode - The color mode to use ('dark', 'light' or 'system')
*/
export function useColorModeClass(colorMode: ColorMode = 'light'): Readable<ColorModeClass> { export function useColorModeClass(colorMode: ColorMode = 'light'): Readable<ColorModeClass> {
const colorModeClass = readable<ColorModeClass>('light', (set) => { const colorModeClass = readable<ColorModeClass>('light', (set) => {
if (colorMode !== 'system') { if (colorMode !== 'system') {
@@ -3,6 +3,12 @@ import type { Readable } from 'svelte/store';
import { useStore } from '$lib/store'; import { useStore } from '$lib/store';
import type { ConnectionProps } from '$lib/store/derived-connection-props'; import type { ConnectionProps } from '$lib/store/derived-connection-props';
/**
* Hook for receiving the current connection.
*
* @public
* @returns current connection as a readable store
*/
export function useConnection(): Readable<ConnectionProps> { export function useConnection(): Readable<ConnectionProps> {
const { connection } = useStore(); const { connection } = useStore();
@@ -11,6 +11,15 @@ export type useHandleConnectionsParams = {
const initialConnections: Connection[] = []; const initialConnections: Connection[] = [];
/**
* Hook to check if a <Handle /> is connected to another <Handle /> and get the connections.
*
* @public
* @param param.nodeId
* @param param.type - handle type 'source' or 'target'
* @param param.id - the handle id (this is only needed if the node has multiple handles of the same type)
* @returns an array with connections
*/
export function useHandleConnections({ nodeId, type, id = null }: useHandleConnectionsParams) { export function useHandleConnections({ nodeId, type, id = null }: useHandleConnectionsParams) {
const { edges, connectionLookup } = useStore(); const { edges, connectionLookup } = useStore();
let prevConnections: Map<string, Connection> | undefined = undefined; let prevConnections: Map<string, Connection> | undefined = undefined;
@@ -21,6 +21,14 @@ function areNodesDataEqual(a: Node['data'][] | null, b: Node['data'][] | null) {
return true; return true;
} }
/**
* Hook for receiving data of one or multiple nodes
*
* @public
* @param nodeId - The id (or ids) of the node to get the data from
* @param guard - Optional guard function to narrow down the node type
* @returns A readable store with an array of data objects
*/
export function useNodesData<NodeType extends Node = Node>( export function useNodesData<NodeType extends Node = Node>(
nodeId: string nodeId: string
): Readable<NodeType['data'] | null>; ): Readable<NodeType['data'] | null>;
@@ -1,10 +1,22 @@
import { useStore } from '$lib/store'; import { useStore } from '$lib/store';
/**
* Hook for getting the current nodes from the store.
*
* @public
* @returns store with an array of nodes
*/
export function useNodes() { export function useNodes() {
const { nodes } = useStore(); const { nodes } = useStore();
return nodes; return nodes;
} }
/**
* Hook for getting the current edges from the store.
*
* @public
* @returns store with an array of edges
*/
export function useEdges() { export function useEdges() {
const { edges } = useStore(); const { edges } = useStore();
return edges; return edges;
@@ -20,6 +20,12 @@ import { useStore } from '$lib/store';
import type { Edge, FitViewOptions, Node } from '$lib/types'; import type { Edge, FitViewOptions, Node } from '$lib/types';
import { isNode } from '$lib/utils'; import { isNode } from '$lib/utils';
/**
* Hook for accessing the ReactFlow instance.
*
* @public
* @returns helper functions
*/
export function useSvelteFlow(): { export function useSvelteFlow(): {
zoomIn: ZoomInOut; zoomIn: ZoomInOut;
zoomOut: ZoomInOut; zoomOut: ZoomInOut;
@@ -3,6 +3,12 @@ import type { UpdateNodeInternals } from '@xyflow/system';
import { useStore } from '$lib/store'; import { useStore } from '$lib/store';
/**
* Hook for updating node internals.
*
* @public
* @returns function for updating node internals
*/
export function useUpdateNodeInternals(): UpdateNodeInternals { export function useUpdateNodeInternals(): UpdateNodeInternals {
const { domNode, updateNodeDimensions } = useStore(); const { domNode, updateNodeDimensions } = useStore();
+43 -20
View File
@@ -6,7 +6,6 @@ import type {
DefaultEdgeOptionsBase, DefaultEdgeOptionsBase,
EdgePosition, EdgePosition,
SmoothStepPathOptions, SmoothStepPathOptions,
Optional,
StepPathOptions StepPathOptions
} from '@xyflow/system'; } from '@xyflow/system';
@@ -34,12 +33,18 @@ type StepEdgeType<T> = DefaultEdge<T> & {
pathOptions?: StepPathOptions; pathOptions?: StepPathOptions;
}; };
/**
* The Edge type is mainly used for the `edges` that get passed to the SvelteFlow component.
*/
export type Edge<T = any> = export type Edge<T = any> =
| DefaultEdge<T> | DefaultEdge<T>
| SmoothStepEdgeType<T> | SmoothStepEdgeType<T>
| BezierEdgeType<T> | BezierEdgeType<T>
| StepEdgeType<T>; | StepEdgeType<T>;
/**
* Custom edge component props.
*/
export type EdgeProps<T = any> = Omit<Edge<T>, 'sourceHandle' | 'targetHandle' | 'type'> & export type EdgeProps<T = any> = Omit<Edge<T>, 'sourceHandle' | 'targetHandle' | 'type'> &
EdgePosition & { EdgePosition & {
markerStart?: string; markerStart?: string;
@@ -48,30 +53,48 @@ export type EdgeProps<T = any> = Omit<Edge<T>, 'sourceHandle' | 'targetHandle' |
targetHandleId?: string | null; targetHandleId?: string | null;
}; };
export type EdgeComponentProps<T = any> = Optional< /**
Omit< * Helper type for edge components that get exported by the library.
EdgeProps<T>, */
'source' | 'target' | 'sourceHandleId' | 'targetHandleId' | 'animated' | 'selected' | 'data' export type EdgeComponentProps = EdgePosition & {
>, id?: EdgeProps['id'];
'id' hidden?: EdgeProps['hidden'];
>; deletable?: EdgeProps['deletable'];
selectable?: EdgeProps['selectable'];
export type BezierEdgeProps<T = any> = EdgeComponentProps<T> & { markerStart?: EdgeProps['markerStart'];
pathOptions?: BezierPathOptions; markerEnd?: EdgeProps['markerEnd'];
zIndex?: EdgeProps['zIndex'];
ariaLabel?: EdgeProps['ariaLabel'];
interactionWidth?: EdgeProps['interactionWidth'];
label?: EdgeProps['label'];
labelStyle?: EdgeProps['labelStyle'];
style?: EdgeProps['style'];
class?: EdgeProps['class'];
}; };
export type SmoothStepEdgeProps<T = any> = EdgeComponentProps<T> & { export type EdgeComponentWithPathOptions<PathOptions> = EdgeComponentProps & {
pathOptions?: SmoothStepPathOptions; pathOptions?: PathOptions;
}; };
export type StepEdgeProps<T = any> = EdgeComponentProps<T> & { /**
pathOptions?: StepPathOptions; * BezierEdge component props
}; */
export type BezierEdgeProps = EdgeComponentWithPathOptions<BezierPathOptions>;
export type StraightEdgeProps<T = any> = Omit< /**
EdgeComponentProps<T>, * SmoothStepEdge component props
'sourcePosition' | 'targetPosition' */
>; export type SmoothStepEdgeProps = EdgeComponentWithPathOptions<SmoothStepPathOptions>;
/**
* StepEdge component props
*/
export type StepEdgeProps = EdgeComponentWithPathOptions<StepPathOptions>;
/**
* StraightEdge component props
*/
export type StraightEdgeProps = Omit<EdgeComponentProps, 'sourcePosition' | 'targetPosition'>;
export type EdgeTypes = Record<string, ComponentType<SvelteComponent<EdgeProps>>>; export type EdgeTypes = Record<string, ComponentType<SvelteComponent<EdgeProps>>>;
+4 -4
View File
@@ -1,11 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import type { ComponentType, SvelteComponent } from 'svelte'; import type { ComponentType, SvelteComponent } from 'svelte';
import type { NodeBase, NodeProps } from '@xyflow/system'; import type { NodeBase, NodeProps } from '@xyflow/system';
// @todo: currently the helper function only like Node from '@reactflow/core' /**
// we need a base node type or helpes that accept Node like types * The node data structure that gets used for the nodes prop.
// eslint-disable-next-line @typescript-eslint/no-explicit-any * @public
*/
export type Node< export type Node<
NodeData = any, NodeData = any,
NodeType extends string | undefined = string | undefined NodeType extends string | undefined = string | undefined
+58
View File
@@ -10,10 +10,68 @@ import {
import type { Edge, Node } from '$lib/types'; import type { Edge, Node } from '$lib/types';
/**
* Test whether an object is useable as a Node
* @public
* @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Node if it returns true
* @param element - The element to test
* @returns A boolean indicating whether the element is an Node
*/
export const isNode = isNodeBase<Node>; export const isNode = isNodeBase<Node>;
/**
* Test whether an object is useable as an Edge
* @public
* @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Edge if it returns true
* @param element - The element to test
* @returns A boolean indicating whether the element is an Edge
*/
export const isEdge = isEdgeBase<Edge>; export const isEdge = isEdgeBase<Edge>;
/**
* 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<Node, Edge>; export const getOutgoers = getOutgoersBase<Node, Edge>;
/**
* 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<Node, Edge>; export const getIncomers = getIncomersBase<Node, Edge>;
/**
* 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<Edge>; export const addEdge = addEdgeBase<Edge>;
/**
* 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<Edge>; export const updateEdge = updateEdgeBase<Edge>;
/**
* 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<Node, Edge>; export const getConnectedEdges = getConnectedEdgesBase<Node, Edge>;
-1
View File
@@ -26,7 +26,6 @@ export type NodeBase<T = any, U extends string | undefined = string | undefined>
extent?: 'parent' | CoordinateExtent; extent?: 'parent' | CoordinateExtent;
expandParent?: boolean; expandParent?: boolean;
ariaLabel?: string; ariaLabel?: string;
focusable?: boolean;
origin?: NodeOrigin; origin?: NodeOrigin;
handles?: NodeHandle[]; handles?: NodeHandle[];
computed?: { computed?: {
@@ -69,6 +69,29 @@ function getControlWithCurvature({ pos, x1, y1, x2, y2, c }: GetControlWithCurva
} }
} }
/**
* Get a bezier path from source to target handle
* @param params.sourceX - The x position of the source handle
* @param params.sourceY - The y position of the source handle
* @param params.sourcePosition - The position of the source handle (default: Position.Bottom)
* @param params.targetX - The x position of the target handle
* @param params.targetY - The y position of the target handle
* @param params.targetPosition - The position of the target handle (default: Position.Top)
* @param params.curvature - The curvature of the bezier edge
* @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label
* @example
* const source = { x: 0, y: 20 };
const target = { x: 150, y: 100 };
const [path, labelX, labelY, offsetX, offsetY] = getBezierPath({
sourceX: source.x,
sourceY: source.y,
sourcePosition: Position.Right,
targetX: target.x,
targetY: target.y,
targetPosition: Position.Left,
});
*/
export function getBezierPath({ export function getBezierPath({
sourceX, sourceX,
sourceY, sourceY,
@@ -90,6 +90,14 @@ const connectionExists = (edge: EdgeBase, edges: EdgeBase[]) => {
); );
}; };
/**
* 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 addEdgeBase = <EdgeType extends EdgeBase>( export const addEdgeBase = <EdgeType extends EdgeBase>(
edgeParams: EdgeType | Connection, edgeParams: EdgeType | Connection,
edges: EdgeType[] edges: EdgeType[]
@@ -129,6 +137,14 @@ export type UpdateEdgeOptions = {
shouldReplaceId?: boolean; shouldReplaceId?: boolean;
}; };
/**
* 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 updateEdgeBase = <EdgeType extends EdgeBase>( export const updateEdgeBase = <EdgeType extends EdgeBase>(
oldEdge: EdgeType, oldEdge: EdgeType,
newConnection: Connection, newConnection: Connection,
@@ -190,6 +190,28 @@ function getBend(a: XYPosition, b: XYPosition, c: XYPosition, size: number): str
return `L ${x},${y + bendSize * yDir}Q ${x},${y} ${x + bendSize * xDir},${y}`; return `L ${x},${y + bendSize * yDir}Q ${x},${y} ${x + bendSize * xDir},${y}`;
} }
/**
* Get a smooth step path from source to target handle
* @param params.sourceX - The x position of the source handle
* @param params.sourceY - The y position of the source handle
* @param params.sourcePosition - The position of the source handle (default: Position.Bottom)
* @param params.targetX - The x position of the target handle
* @param params.targetY - The y position of the target handle
* @param params.targetPosition - The position of the target handle (default: Position.Top)
* @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label
* @example
* const source = { x: 0, y: 20 };
const target = { x: 150, y: 100 };
const [path, labelX, labelY, offsetX, offsetY] = getSmoothStepPath({
sourceX: source.x,
sourceY: source.y,
sourcePosition: Position.Right,
targetX: target.x,
targetY: target.y,
targetPosition: Position.Left,
});
*/
export function getSmoothStepPath({ export function getSmoothStepPath({
sourceX, sourceX,
sourceY, sourceY,
@@ -7,6 +7,26 @@ export type GetStraightPathParams = {
targetY: number; targetY: number;
}; };
/**
* Get a straight path from source to target handle
* @param params.sourceX - The x position of the source handle
* @param params.sourceY - The y position of the source handle
* @param params.targetX - The x position of the target handle
* @param params.targetY - The y position of the target handle
* @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label
* @example
* const source = { x: 0, y: 20 };
const target = { x: 150, y: 100 };
const [path, labelX, labelY, offsetX, offsetY] = getStraightPath({
sourceX: source.x,
sourceY: source.y,
sourcePosition: Position.Right,
targetX: target.x,
targetY: target.y,
targetPosition: Position.Left,
});
*/
export function getStraightPath({ export function getStraightPath({
sourceX, sourceX,
sourceY, sourceY,
+27 -5
View File
@@ -8,8 +8,8 @@ import type {
NodeOrigin, NodeOrigin,
SnapGrid, SnapGrid,
Transform, Transform,
Viewport,
} from '../types'; } from '../types';
import { type Viewport } from '../types';
import { getNodePositionWithOrigin } from './graph'; import { getNodePositionWithOrigin } from './graph';
export const clamp = (val: number, min = 0, max = 1): number => Math.min(Math.max(val, min), max); export const clamp = (val: number, min = 0, max = 1): number => Math.min(Math.max(val, min), max);
@@ -19,8 +19,14 @@ export const clampPosition = (position: XYPosition = { x: 0, y: 0 }, extent: Coo
y: clamp(position.y, extent[0][1], extent[1][1]), y: clamp(position.y, extent[0][1], extent[1][1]),
}); });
// returns a number between 0 and 1 that represents the velocity of the movement /**
// when the mouse is close to the edge of the canvas * Calculates the velocity of panning when the mouse is close to the edge of the canvas
* @internal
* @param value - One dimensional poition of the mouse (x or y)
* @param min - Minimal position on canvas before panning starts
* @param max - Maximal position on canvas before panning starts
* @returns - A number between 0 and 1 that represents the velocity of panning
*/
const calcAutoPanVelocity = (value: number, min: number, max: number): number => { const calcAutoPanVelocity = (value: number, min: number, max: number): number => {
if (value < min) { if (value < min) {
return clamp(Math.abs(value - min), 1, 50) / 50; return clamp(Math.abs(value - min), 1, 50) / 50;
@@ -127,12 +133,12 @@ export const getPositionWithOrigin = ({
}; };
}; };
export function snapPosition(position: XYPosition, snapGrid: SnapGrid = [1, 1]): XYPosition { export const snapPosition = (position: XYPosition, snapGrid: SnapGrid = [1, 1]): XYPosition => {
return { return {
x: snapGrid[0] * Math.round(position.x / snapGrid[0]), x: snapGrid[0] * Math.round(position.x / snapGrid[0]),
y: snapGrid[1] * Math.round(position.y / snapGrid[1]), y: snapGrid[1] * Math.round(position.y / snapGrid[1]),
}; };
} };
export const pointToRendererPoint = ( export const pointToRendererPoint = (
{ x, y }: XYPosition, { x, y }: XYPosition,
@@ -155,6 +161,22 @@ export const rendererPointToPoint = ({ x, y }: XYPosition, [tx, ty, tScale]: Tra
}; };
}; };
/**
* Returns a viewport that encloses the given bounds with optional padding.
* @public
* @remarks You can determine bounds of nodes with {@link getNodesBounds} and {@link getBoundsOfRects}
* @param bounds - Bounds to fit inside viewport
* @param width - Width of the viewport
* @param height - Height of the viewport
* @param minZoom - Minimum zoom level of the resulting viewport
* @param maxZoom - Maximum zoom level of the resulting viewport
* @param padding - Optional padding around the bounds
* @returns A transforned {@link Viewport} that encloses the given bounds which you can pass to e.g. {@link setViewport}
* @example
* const { x, y, zoom } = getViewportForBounds(
{ x: 0, y: 0, width: 100, height: 100},
1200, 800, 0.5, 2);
*/
export const getViewportForBounds = ( export const getViewportForBounds = (
bounds: Rect, bounds: Rect,
width: number, width: number,
+53 -3
View File
@@ -25,12 +25,34 @@ import {
} from '../types'; } from '../types';
import { errorMessages } from '../constants'; import { errorMessages } from '../constants';
/**
* Test whether an object is useable as an Edge
* @public
* @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Edge if it returns true
* @param element - The element to test
* @returns A boolean indicating whether the element is an Edge
*/
export const isEdgeBase = <EdgeType extends EdgeBase = EdgeBase>(element: any): element is EdgeType => export const isEdgeBase = <EdgeType extends EdgeBase = EdgeBase>(element: any): element is EdgeType =>
'id' in element && 'source' in element && 'target' in element; 'id' in element && 'source' in element && 'target' in element;
/**
* Test whether an object is useable as a Node
* @public
* @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Node if it returns true
* @param element - The element to test
* @returns A boolean indicating whether the element is an Node
*/
export const isNodeBase = <NodeType extends NodeBase = NodeBase>(element: any): element is NodeType => export const isNodeBase = <NodeType extends NodeBase = NodeBase>(element: any): element is NodeType =>
'id' in element && !('source' in element) && !('target' in element); 'id' in element && !('source' in element) && !('target' in element);
/**
* 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 getOutgoersBase = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>( export const getOutgoersBase = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
node: NodeType | { id: string }, node: NodeType | { id: string },
nodes: NodeType[], nodes: NodeType[],
@@ -50,6 +72,14 @@ export const getOutgoersBase = <NodeType extends NodeBase = NodeBase, EdgeType e
return nodes.filter((n) => outgoerIds.has(n.id)); return nodes.filter((n) => outgoerIds.has(n.id));
}; };
/**
* 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 getIncomersBase = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>( export const getIncomersBase = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
node: NodeType | { id: string }, node: NodeType | { id: string },
nodes: NodeType[], nodes: NodeType[],
@@ -102,6 +132,14 @@ export const getNodePositionWithOrigin = (
}; };
}; };
/**
* Determines a bounding box that contains all given nodes in an array
* @public
* @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 nodeOrigin - Origin of the nodes: [0, 0] - top left, [0.5, 0.5] - center
* @returns Bounding box enclosing all nodes
*/
export const getNodesBounds = (nodes: NodeBase[], nodeOrigin: NodeOrigin = [0, 0]): Rect => { export const getNodesBounds = (nodes: NodeBase[], nodeOrigin: NodeOrigin = [0, 0]): 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 };
@@ -167,6 +205,12 @@ export const getNodesInside = <NodeType extends NodeBase>(
return visibleNodes; return visibleNodes;
}; };
/**
* 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 getConnectedEdgesBase = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>( export const getConnectedEdgesBase = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
nodes: NodeType[], nodes: NodeType[],
edges: EdgeType[] edges: EdgeType[]
@@ -281,9 +325,15 @@ export function calcNextPosition<NodeType extends NodeBase>(
}; };
} }
// helper function to get arrays of nodes and edges that can be deleted /**
// you can pass in a list of nodes and edges that should be deleted * Pass in nodes & edges to delete, get arrays of nodes and edges that actually can be deleted
// and the function only returns elements that are deletable and also handles connected nodes and child nodes * @internal
* @param param.nodesToRemove - The nodes to remove
* @param param.edgesToRemove - The edges to remove
* @param param.nodes - All nodes
* @param param.edges - All edges
* @returns matchingNodes: nodes that can be deleted, matchingEdges: edges that can be deleted
*/
export function getElementsToRemove<NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>({ export function getElementsToRemove<NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>({
nodesToRemove, nodesToRemove,
edgesToRemove, edgesToRemove,