implemented new fitView logic
This commit is contained in:
@@ -24,9 +24,32 @@ function getSelector<NodeType extends Node = Node, SelectorReturn = ConnectionSt
|
||||
return storeSelector;
|
||||
}
|
||||
/**
|
||||
* Hook for accessing the connection state.
|
||||
* The `useConnection` hook returns the current connection when there is an active
|
||||
* connection interaction. If no connection interaction is active, it returns null
|
||||
* for every property. A typical use case for this hook is to colorize handles
|
||||
* based on a certain condition (e.g. if the connection is valid or not).
|
||||
*
|
||||
* @public
|
||||
* @param connectionSelector - An optional selector function used to extract a slice of the
|
||||
* `ConnectionState` data. Using a selector can prevent component re-renders where data you don't
|
||||
* otherwise care about might change. If a selector is not provided, the entire `ConnectionState`
|
||||
* object is returned unchanged.
|
||||
* @example
|
||||
*
|
||||
* ```tsx
|
||||
*import { useConnection } from '@xyflow/react';
|
||||
*
|
||||
*function App() {
|
||||
* const connection = useConnection();
|
||||
*
|
||||
* return (
|
||||
* <div> {connection ? `Someone is trying to make a connection from ${connection.fromNode} to this one.` : 'There are currently no incoming connections!'}
|
||||
*
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @returns ConnectionState
|
||||
*/
|
||||
export function useConnection<NodeType extends Node = Node, SelectorReturn = ConnectionState<InternalNode<NodeType>>>(
|
||||
|
||||
@@ -6,10 +6,22 @@ import type { Edge, ReactFlowState } from '../types';
|
||||
const edgesSelector = (state: ReactFlowState) => state.edges;
|
||||
|
||||
/**
|
||||
* Hook for getting the current edges from the store.
|
||||
* This hook returns an array of the current edges. Components that use this hook
|
||||
* will re-render **whenever any edge changes**.
|
||||
*
|
||||
* @public
|
||||
* @returns An array of edges
|
||||
* @returns An array of all edges currently in the flow.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
*import { useEdges } from '@xyflow/react';
|
||||
*
|
||||
*export default function () {
|
||||
* const edges = useEdges();
|
||||
*
|
||||
* return <div>There are currently {edges.length} edges!</div>;
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useEdges<EdgeType extends Edge = Edge>(): EdgeType[] {
|
||||
const edges = useStore(edgesSelector, shallow) as EdgeType[];
|
||||
|
||||
@@ -10,11 +10,16 @@ import {
|
||||
import { useStore } from './useStore';
|
||||
import { useNodeId } from '../contexts/NodeIdContext';
|
||||
|
||||
type useHandleConnectionsParams = {
|
||||
type UseHandleConnectionsParams = {
|
||||
/** What type of handle connections do you want to observe? */
|
||||
type: HandleType;
|
||||
/** The handle id (this is only needed if the node has multiple handles of the same type). */
|
||||
id?: string | null;
|
||||
/** If node id is not provided, the node id from the `NodeIdContext` is used. */
|
||||
nodeId?: string;
|
||||
/** Gets called when a connection is established. */
|
||||
onConnect?: (connections: Connection[]) => void;
|
||||
/** Gets called when a connection is removed. */
|
||||
onDisconnect?: (connections: Connection[]) => void;
|
||||
};
|
||||
|
||||
@@ -23,12 +28,7 @@ type useHandleConnectionsParams = {
|
||||
*
|
||||
* @public
|
||||
* @deprecated Use `useNodeConnections` instead.
|
||||
* @param param.type - handle type 'source' or 'target'
|
||||
* @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.onDisconnect - gets called when a connection is removed
|
||||
* @returns an array with handle connections
|
||||
* @returns An array with handle connections.
|
||||
*/
|
||||
export function useHandleConnections({
|
||||
type,
|
||||
@@ -36,7 +36,7 @@ export function useHandleConnections({
|
||||
nodeId,
|
||||
onConnect,
|
||||
onDisconnect,
|
||||
}: useHandleConnectionsParams): HandleConnection[] {
|
||||
}: UseHandleConnectionsParams): HandleConnection[] {
|
||||
console.warn(
|
||||
'[DEPRECATED] `useHandleConnections` is deprecated. Instead use `useNodeConnections` https://reactflow.dev/api-reference/hooks/useNodeConnections'
|
||||
);
|
||||
|
||||
@@ -5,11 +5,31 @@ import { useStore } from './useStore';
|
||||
import type { InternalNode, Node } from '../types';
|
||||
|
||||
/**
|
||||
* Hook for getting an internal node by id
|
||||
* This hook returns the internal representation of a specific node.
|
||||
* Components that use this hook will re-render **whenever the node changes**,
|
||||
* including when a node is selected or moved.
|
||||
*
|
||||
* @public
|
||||
* @param id - id of the node
|
||||
* @returns array with visible node ids
|
||||
* @param id - The ID of a node you want to observe.
|
||||
* @returns The `InternalNode` object for the node with the given ID.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
*import { useInternalNode } from '@xyflow/react';
|
||||
*
|
||||
*export default function () {
|
||||
* const internalNode = useInternalNode('node-1');
|
||||
* const absolutePosition = internalNode.internals.positionAbsolute;
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* The absolute position of the node is at:
|
||||
* <p>x: {absolutePosition.x}</p>
|
||||
* <p>y: {absolutePosition.y}</p>
|
||||
* </div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useInternalNode<NodeType extends Node = Node>(id: string): InternalNode<NodeType> | undefined {
|
||||
const node = useStore(
|
||||
|
||||
@@ -6,25 +6,58 @@ type PressedKeys = Set<string>;
|
||||
type KeyOrCode = 'key' | 'code';
|
||||
|
||||
export type UseKeyPressOptions = {
|
||||
/**
|
||||
* Listen to key presses on a specific element.
|
||||
* @default document
|
||||
*/
|
||||
target?: Window | Document | HTMLElement | ShadowRoot | null;
|
||||
/**
|
||||
* You can use this flag to prevent triggering the key press hook when an input field is focused.
|
||||
* @default true
|
||||
*/
|
||||
actInsideInputWithModifier?: boolean;
|
||||
preventDefault?: boolean;
|
||||
};
|
||||
|
||||
const defaultDoc = typeof document !== 'undefined' ? document : null;
|
||||
|
||||
/**
|
||||
* Hook for handling key events.
|
||||
* This hook lets you listen for specific key codes and tells you whether they are
|
||||
* currently pressed or not.
|
||||
*
|
||||
* @public
|
||||
* @param param.keyCode - The key code (string or array of strings) to use
|
||||
* @param param.options - Options
|
||||
* @returns boolean
|
||||
* @param options - Options
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
*import { useKeyPress } from '@xyflow/react';
|
||||
*
|
||||
*export default function () {
|
||||
* const spacePressed = useKeyPress('Space');
|
||||
* const cmdAndSPressed = useKeyPress(['Meta+s', 'Strg+s']);
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* {spacePressed && <p>Space pressed!</p>}
|
||||
* {cmdAndSPressed && <p>Cmd + S pressed!</p>}
|
||||
* </div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useKeyPress(
|
||||
// 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'
|
||||
/**
|
||||
* The key code (string or array of strings) specifies which key(s) should trigger
|
||||
* an action.
|
||||
*
|
||||
* A **string** can represent:
|
||||
* - A **single key**, e.g. `'a'`
|
||||
* - A **key combination**, using `'+'` to separate keys, e.g. `'a+d'`
|
||||
*
|
||||
* An **array of strings** represents **multiple possible key inputs**. For example, `['a', 'd+s']`
|
||||
* means the user can press either the single key `'a'` or the combination of `'d'` and `'s'`.
|
||||
* @default null
|
||||
*/
|
||||
keyCode: KeyCode | null = null,
|
||||
options: UseKeyPressOptions = { target: defaultDoc, actInsideInputWithModifier: true }
|
||||
): boolean {
|
||||
@@ -36,20 +69,24 @@ export function useKeyPress(
|
||||
// we need to remember the pressed keys in order to support combinations
|
||||
const pressedKeys = useRef<PressedKeys>(new Set([]));
|
||||
|
||||
// keyCodes = array with single keys [['a']] or key combinations [['a', 's']]
|
||||
// keysToWatch = array with all keys flattened ['a', 'd', 'ShiftLeft']
|
||||
// used to check if we store event.code or event.key. When the code is in the list of keysToWatch
|
||||
// we use the code otherwise the key. Explainer: When you press the left "command" key, the code is "MetaLeft"
|
||||
// and the key is "Meta". We want users to be able to pass keys and codes so we assume that the key is meant when
|
||||
// we can't find it in the list of keysToWatch.
|
||||
/*
|
||||
* keyCodes = array with single keys [['a']] or key combinations [['a', 's']]
|
||||
* keysToWatch = array with all keys flattened ['a', 'd', 'ShiftLeft']
|
||||
* used to check if we store event.code or event.key. When the code is in the list of keysToWatch
|
||||
* we use the code otherwise the key. Explainer: When you press the left "command" key, the code is "MetaLeft"
|
||||
* and the key is "Meta". We want users to be able to pass keys and codes so we assume that the key is meant when
|
||||
* we can't find it in the list of keysToWatch.
|
||||
*/
|
||||
const [keyCodes, keysToWatch] = useMemo<[Array<Keys>, Keys]>(() => {
|
||||
if (keyCode !== null) {
|
||||
const keyCodeArr = Array.isArray(keyCode) ? keyCode : [keyCode];
|
||||
const keys = keyCodeArr
|
||||
.filter((kc) => typeof kc === 'string')
|
||||
// we first replace all '+' with '\n' which we will use to split the keys on
|
||||
// then we replace '\n\n' with '\n+', this way we can also support the combination 'key++'
|
||||
// in the end we simply split on '\n' to get the key array
|
||||
/*
|
||||
* we first replace all '+' with '\n' which we will use to split the keys on
|
||||
* then we replace '\n\n' with '\n+', this way we can also support the combination 'key++'
|
||||
* in the end we simply split on '\n' to get the key array
|
||||
*/
|
||||
.map((kc) => kc.replace('+', '\n').replace('\n\n', '\n+').split('\n'));
|
||||
const keysFlat = keys.reduce((res: Keys, item) => res.concat(...item), []);
|
||||
|
||||
@@ -64,7 +101,7 @@ export function useKeyPress(
|
||||
|
||||
if (keyCode !== null) {
|
||||
const downHandler = (event: KeyboardEvent) => {
|
||||
modifierPressed.current = event.ctrlKey || event.metaKey || event.shiftKey;
|
||||
modifierPressed.current = event.ctrlKey || event.metaKey || event.shiftKey || event.altKey;
|
||||
const preventAction =
|
||||
(!modifierPressed.current || (modifierPressed.current && !options.actInsideInputWithModifier)) &&
|
||||
isInputDOMNode(event);
|
||||
@@ -76,19 +113,18 @@ export function useKeyPress(
|
||||
pressedKeys.current.add(event[keyOrCode]);
|
||||
|
||||
if (isMatchingKey(keyCodes, pressedKeys.current, false)) {
|
||||
event.preventDefault();
|
||||
const target = (event.composedPath?.()?.[0] || event.target) as Element | null;
|
||||
const isInteractiveElement = target?.nodeName === 'BUTTON' || target?.nodeName === 'A';
|
||||
|
||||
if (options.preventDefault !== false && (modifierPressed.current || !isInteractiveElement)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
setKeyPressed(true);
|
||||
}
|
||||
};
|
||||
|
||||
const upHandler = (event: KeyboardEvent) => {
|
||||
const preventAction =
|
||||
(!modifierPressed.current || (modifierPressed.current && !options.actInsideInputWithModifier)) &&
|
||||
isInputDOMNode(event);
|
||||
|
||||
if (preventAction) {
|
||||
return false;
|
||||
}
|
||||
const keyOrCode = useKeyOrCode(event.code, keysToWatch);
|
||||
|
||||
if (isMatchingKey(keyCodes, pressedKeys.current, true)) {
|
||||
@@ -133,12 +169,16 @@ export function useKeyPress(
|
||||
function isMatchingKey(keyCodes: Array<Keys>, pressedKeys: PressedKeys, isUp: boolean): boolean {
|
||||
return (
|
||||
keyCodes
|
||||
// we only want to compare same sizes of keyCode definitions
|
||||
// and pressed keys. When the user specified 'Meta' as a key somewhere
|
||||
// this would also be truthy without this filter when user presses 'Meta' + 'r'
|
||||
/*
|
||||
* we only want to compare same sizes of keyCode definitions
|
||||
* and pressed keys. When the user specified 'Meta' as a key somewhere
|
||||
* this would also be truthy without this filter when user presses 'Meta' + 'r'
|
||||
*/
|
||||
.filter((keys) => isUp || keys.length === pressedKeys.size)
|
||||
// since we want to support multiple possibilities only one of the
|
||||
// combinations need to be part of the pressed keys
|
||||
/*
|
||||
* since we want to support multiple possibilities only one of the
|
||||
* combinations need to be part of the pressed keys
|
||||
*/
|
||||
.some((keys) => keys.every((k) => pressedKeys.has(k)))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,8 +22,10 @@ export function useMoveSelectedNodes() {
|
||||
const nodeUpdates = new Map();
|
||||
const isSelected = selectedAndDraggable(nodesDraggable);
|
||||
|
||||
// by default a node moves 5px on each key press
|
||||
// if snap grid is enabled, we use that for the velocity
|
||||
/*
|
||||
* by default a node moves 5px on each key press
|
||||
* if snap grid is enabled, we use that for the velocity
|
||||
*/
|
||||
const xVelo = snapToGrid ? snapGrid[0] : 5;
|
||||
const yVelo = snapToGrid ? snapGrid[1] : 5;
|
||||
|
||||
|
||||
@@ -14,23 +14,39 @@ import { useNodeId } from '../contexts/NodeIdContext';
|
||||
const error014 = errorMessages['error014']();
|
||||
|
||||
type UseNodeConnectionsParams = {
|
||||
/** ID of the node, filled in automatically if used inside custom node. */
|
||||
id?: string;
|
||||
/** What type of handle connections do you want to observe? */
|
||||
handleType?: HandleType;
|
||||
/** Filter by handle id (this is only needed if the node has multiple handles of the same type). */
|
||||
handleId?: string;
|
||||
/** Gets called when a connection is established. */
|
||||
onConnect?: (connections: Connection[]) => void;
|
||||
/** Gets called when a connection is removed. */
|
||||
onDisconnect?: (connections: Connection[]) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to retrieve all edges connected to a node. Can be filtered by handle type and id.
|
||||
* This hook returns an array of connections on a specific node, handle type ('source', 'target') or handle ID.
|
||||
*
|
||||
* @public
|
||||
* @param param.id - node id - optional if called inside a custom node
|
||||
* @param param.handleType - filter by handle type 'source' or 'target'
|
||||
* @param param.handleId - filter by 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.onDisconnect - gets called when a connection is removed
|
||||
* @returns an array with connections
|
||||
* @returns An array with connections.
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useNodeConnections } from '@xyflow/react';
|
||||
*
|
||||
*export default function () {
|
||||
* const connections = useNodeConnections({
|
||||
* handleType: 'target',
|
||||
* handleId: 'my-handle',
|
||||
* });
|
||||
*
|
||||
* return (
|
||||
* <div>There are currently {connections.length} incoming connections!</div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useNodeConnections({
|
||||
id,
|
||||
@@ -57,7 +73,7 @@ export function useNodeConnections({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// @todo dicuss if onConnect/onDisconnect should be called when the component mounts/unmounts
|
||||
// @todo discuss if onConnect/onDisconnect should be called when the component mounts/unmounts
|
||||
if (prevConnections.current && prevConnections.current !== connections) {
|
||||
const _connections = connections ?? new Map();
|
||||
handleConnectionChange(prevConnections.current, _connections, onDisconnect);
|
||||
|
||||
@@ -6,10 +6,23 @@ import type { Node, ReactFlowState } from '../types';
|
||||
const nodesSelector = (state: ReactFlowState) => state.nodes;
|
||||
|
||||
/**
|
||||
* Hook for getting the current nodes from the store.
|
||||
* This hook returns an array of the current nodes. Components that use this hook
|
||||
* will re-render **whenever any node changes**, including when a node is selected
|
||||
* or moved.
|
||||
*
|
||||
* @public
|
||||
* @returns An array of nodes
|
||||
* @returns An array of all nodes currently in the flow.
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useNodes } from '@xyflow/react';
|
||||
*
|
||||
*export default function() {
|
||||
* const nodes = useNodes();
|
||||
*
|
||||
* return <div>There are currently {nodes.length} nodes!</div>;
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useNodes<NodeType extends Node = Node>(): NodeType[] {
|
||||
const nodes = useStore(nodesSelector, shallow) as NodeType[];
|
||||
|
||||
@@ -5,17 +5,31 @@ import { useStore } from '../hooks/useStore';
|
||||
import type { Node } from '../types';
|
||||
|
||||
/**
|
||||
* Hook for receiving data of one or multiple nodes
|
||||
* This hook lets you subscribe to changes of a specific nodes `data` object.
|
||||
*
|
||||
* @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 object (or array of object) with {id, type, data} representing each node
|
||||
* @returns An object (or array of object) with `id`, `type`, `data` representing each node.
|
||||
*
|
||||
* @example
|
||||
*```jsx
|
||||
*import { useNodesData } from '@xyflow/react';
|
||||
*
|
||||
*export default function() {
|
||||
* const nodeData = useNodesData('nodeId-1');
|
||||
* const nodesData = useNodesData(['nodeId-1', 'nodeId-2']);
|
||||
*
|
||||
* return null;
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useNodesData<NodeType extends Node = Node>(
|
||||
/** The id of the node to get the data from. */
|
||||
nodeId: string
|
||||
): Pick<NodeType, 'id' | 'type' | 'data'> | null;
|
||||
export function useNodesData<NodeType extends Node = Node>(nodeIds: string[]): Pick<NodeType, 'id' | 'type' | 'data'>[];
|
||||
export function useNodesData<NodeType extends Node = Node>(
|
||||
/** The ids of the nodes to get the data from. */
|
||||
nodeIds: string[]
|
||||
): Pick<NodeType, 'id' | 'type' | 'data'>[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function useNodesData(nodeIds: any): any {
|
||||
const nodesData = useStore(
|
||||
|
||||
@@ -4,15 +4,58 @@ import { applyNodeChanges, applyEdgeChanges } from '../utils/changes';
|
||||
import type { Node, Edge, OnNodesChange, OnEdgesChange } from '../types';
|
||||
|
||||
/**
|
||||
* Hook for managing the state of nodes - should only be used for prototyping / simple use cases.
|
||||
* This hook makes it easy to prototype a controlled flow where you manage the
|
||||
* state of nodes and edges outside the `ReactFlowInstance`. You can think of it
|
||||
* like React's `useState` hook with an additional helper callback.
|
||||
*
|
||||
* @public
|
||||
* @param initialNodes
|
||||
* @returns an array [nodes, setNodes, onNodesChange]
|
||||
* @returns
|
||||
* - `nodes`: The current array of nodes. You might pass this directly to the `nodes` prop of your
|
||||
* `<ReactFlow />` component, or you may want to manipulate it first to perform some layouting,
|
||||
* for example.
|
||||
* - `setNodes`: A function that you can use to update the nodes. You can pass it a new array of
|
||||
* nodes or a callback that receives the current array of nodes and returns a new array of nodes.
|
||||
* This is the same as the second element of the tuple returned by React's `useState` hook.
|
||||
* - `onNodesChange`: A handy callback that can take an array of `NodeChanges` and update the nodes
|
||||
* state accordingly. You'll typically pass this directly to the `onNodesChange` prop of your
|
||||
* `<ReactFlow />` component.
|
||||
* @example
|
||||
*
|
||||
*```tsx
|
||||
*import { ReactFlow, useNodesState, useEdgesState } from '@xyflow/react';
|
||||
*
|
||||
*const initialNodes = [];
|
||||
*const initialEdges = [];
|
||||
*
|
||||
*export default function () {
|
||||
* const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
* const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
*
|
||||
* return (
|
||||
* <ReactFlow
|
||||
* nodes={nodes}
|
||||
* edges={edges}
|
||||
* onNodesChange={onNodesChange}
|
||||
* onEdgesChange={onEdgesChange}
|
||||
* />
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*
|
||||
* @remarks This hook was created to make prototyping easier and our documentation
|
||||
* examples clearer. Although it is OK to use this hook in production, in
|
||||
* practice you may want to use a more sophisticated state management solution
|
||||
* like Zustand {@link https://reactflow.dev/docs/guides/state-management/} instead.
|
||||
*
|
||||
*/
|
||||
export function useNodesState<NodeType extends Node>(
|
||||
initialNodes: NodeType[]
|
||||
): [NodeType[], Dispatch<SetStateAction<NodeType[]>>, OnNodesChange<NodeType>] {
|
||||
): [
|
||||
//
|
||||
nodes: NodeType[],
|
||||
setNodes: Dispatch<SetStateAction<NodeType[]>>,
|
||||
onNodesChange: OnNodesChange<NodeType>
|
||||
] {
|
||||
const [nodes, setNodes] = useState(initialNodes);
|
||||
const onNodesChange: OnNodesChange<NodeType> = useCallback(
|
||||
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
|
||||
@@ -23,15 +66,60 @@ export function useNodesState<NodeType extends Node>(
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for managing the state of edges - should only be used for prototyping / simple use cases.
|
||||
* This hook makes it easy to prototype a controlled flow where you manage the
|
||||
* state of nodes and edges outside the `ReactFlowInstance`. You can think of it
|
||||
* like React's `useState` hook with an additional helper callback.
|
||||
*
|
||||
* @public
|
||||
* @param initialEdges
|
||||
* @returns an array [edges, setEdges, onEdgesChange]
|
||||
* @returns
|
||||
* - `edges`: The current array of edges. You might pass this directly to the `edges` prop of your
|
||||
* `<ReactFlow />` component, or you may want to manipulate it first to perform some layouting,
|
||||
* for example.
|
||||
*
|
||||
* - `setEdges`: A function that you can use to update the edges. You can pass it a new array of
|
||||
* edges or a callback that receives the current array of edges and returns a new array of edges.
|
||||
* This is the same as the second element of the tuple returned by React's `useState` hook.
|
||||
*
|
||||
* - `onEdgesChange`: A handy callback that can take an array of `EdgeChanges` and update the edges
|
||||
* state accordingly. You'll typically pass this directly to the `onEdgesChange` prop of your
|
||||
* `<ReactFlow />` component.
|
||||
* @example
|
||||
*
|
||||
*```tsx
|
||||
*import { ReactFlow, useNodesState, useEdgesState } from '@xyflow/react';
|
||||
*
|
||||
*const initialNodes = [];
|
||||
*const initialEdges = [];
|
||||
*
|
||||
*export default function () {
|
||||
* const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
* const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
*
|
||||
* return (
|
||||
* <ReactFlow
|
||||
* nodes={nodes}
|
||||
* edges={edges}
|
||||
* onNodesChange={onNodesChange}
|
||||
* onEdgesChange={onEdgesChange}
|
||||
* />
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*
|
||||
* @remarks This hook was created to make prototyping easier and our documentation
|
||||
* examples clearer. Although it is OK to use this hook in production, in
|
||||
* practice you may want to use a more sophisticated state management solution
|
||||
* like Zustand {@link https://reactflow.dev/docs/guides/state-management/} instead.
|
||||
*
|
||||
*/
|
||||
export function useEdgesState<EdgeType extends Edge = Edge>(
|
||||
initialEdges: EdgeType[]
|
||||
): [EdgeType[], Dispatch<SetStateAction<EdgeType[]>>, OnEdgesChange<EdgeType>] {
|
||||
): [
|
||||
//
|
||||
edges: EdgeType[],
|
||||
setEdges: Dispatch<SetStateAction<EdgeType[]>>,
|
||||
onEdgesChange: OnEdgesChange<EdgeType>
|
||||
] {
|
||||
const [edges, setEdges] = useState(initialEdges);
|
||||
const onEdgesChange: OnEdgesChange<EdgeType> = useCallback(
|
||||
(changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useStore } from './useStore';
|
||||
import type { ReactFlowState } from '../types';
|
||||
import { nodeHasDimensions } from '@xyflow/system';
|
||||
|
||||
import { useStore } from './useStore';
|
||||
import type { ReactFlowState } from '../types';
|
||||
|
||||
export type UseNodesInitializedOptions = {
|
||||
/** @default false */
|
||||
includeHiddenNodes?: boolean;
|
||||
};
|
||||
|
||||
@@ -22,18 +24,44 @@ const selector = (options: UseNodesInitializedOptions) => (s: ReactFlowState) =>
|
||||
return true;
|
||||
};
|
||||
|
||||
const defaultOptions = {
|
||||
includeHiddenNodes: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook which returns true when all nodes are initialized.
|
||||
* This hook tells you whether all the nodes in a flow have been measured and given
|
||||
*a width and height. When you add a node to the flow, this hook will return
|
||||
*`false` and then `true` again once the node has been measured.
|
||||
*
|
||||
* @public
|
||||
* @param options.includeHiddenNodes - defaults to false
|
||||
* @returns boolean indicating whether all nodes are initialized
|
||||
* @returns Whether or not the nodes have been initialized by the `<ReactFlow />` component and
|
||||
* given a width and height.
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useReactFlow, useNodesInitialized } from '@xyflow/react';
|
||||
*import { useEffect, useState } from 'react';
|
||||
*
|
||||
*const options = {
|
||||
* includeHiddenNodes: false,
|
||||
*};
|
||||
*
|
||||
*export default function useLayout() {
|
||||
* const { getNodes } = useReactFlow();
|
||||
* const nodesInitialized = useNodesInitialized(options);
|
||||
* const [layoutedNodes, setLayoutedNodes] = useState(getNodes());
|
||||
*
|
||||
* useEffect(() => {
|
||||
* if (nodesInitialized) {
|
||||
* setLayoutedNodes(yourLayoutingFunction(getNodes()));
|
||||
* }
|
||||
* }, [nodesInitialized]);
|
||||
*
|
||||
* return layoutedNodes;
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useNodesInitialized(options: UseNodesInitializedOptions = defaultOptions): boolean {
|
||||
export function useNodesInitialized(
|
||||
options: UseNodesInitializedOptions = {
|
||||
includeHiddenNodes: false,
|
||||
}
|
||||
): boolean {
|
||||
const initialized = useStore(selector(options));
|
||||
|
||||
return initialized;
|
||||
|
||||
@@ -1,20 +1,53 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useStoreApi } from './useStore';
|
||||
import type { OnSelectionChangeFunc } from '../types';
|
||||
import type { OnSelectionChangeFunc, Node, Edge } from '../types';
|
||||
|
||||
export type UseOnSelectionChangeOptions = {
|
||||
onChange: OnSelectionChangeFunc;
|
||||
export type UseOnSelectionChangeOptions<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
|
||||
/** The handler to register. */
|
||||
onChange: OnSelectionChangeFunc<NodeType, EdgeType>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for registering an onSelectionChange handler.
|
||||
* This hook lets you listen for changes to both node and edge selection. As the
|
||||
*name implies, the callback you provide will be called whenever the selection of
|
||||
*_either_ nodes or edges changes.
|
||||
*
|
||||
* @public
|
||||
* @param params.onChange - The handler to register
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useState } from 'react';
|
||||
*import { ReactFlow, useOnSelectionChange } from '@xyflow/react';
|
||||
*
|
||||
*function SelectionDisplay() {
|
||||
* const [selectedNodes, setSelectedNodes] = useState([]);
|
||||
* const [selectedEdges, setSelectedEdges] = useState([]);
|
||||
*
|
||||
* // the passed handler has to be memoized, otherwise the hook will not work correctly
|
||||
* const onChange = useCallback(({ nodes, edges }) => {
|
||||
* setSelectedNodes(nodes.map((node) => node.id));
|
||||
* setSelectedEdges(edges.map((edge) => edge.id));
|
||||
* }, []);
|
||||
*
|
||||
* useOnSelectionChange({
|
||||
* onChange,
|
||||
* });
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <p>Selected nodes: {selectedNodes.join(', ')}</p>
|
||||
* <p>Selected edges: {selectedEdges.join(', ')}</p>
|
||||
* </div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*
|
||||
* @remarks You need to memoize the passed `onChange` handler, otherwise the hook will not work correctly.
|
||||
*/
|
||||
export function useOnSelectionChange({ onChange }: UseOnSelectionChangeOptions) {
|
||||
const store = useStoreApi();
|
||||
export function useOnSelectionChange<NodeType extends Node = Node, EdgeType extends Edge = Edge>({
|
||||
onChange,
|
||||
}: UseOnSelectionChangeOptions<NodeType, EdgeType>) {
|
||||
const store = useStoreApi<NodeType, EdgeType>();
|
||||
|
||||
useEffect(() => {
|
||||
const nextOnSelectionChangeHandlers = [...store.getState().onSelectionChangeHandlers, onChange];
|
||||
|
||||
@@ -4,18 +4,35 @@ import type { OnViewportChange } from '@xyflow/system';
|
||||
import { useStoreApi } from './useStore';
|
||||
|
||||
export type UseOnViewportChangeOptions = {
|
||||
/** Gets called when the viewport starts changing. */
|
||||
onStart?: OnViewportChange;
|
||||
/** Gets called when the viewport changes. */
|
||||
onChange?: OnViewportChange;
|
||||
/** Gets called when the viewport stops changing. */
|
||||
onEnd?: OnViewportChange;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for registering an onViewportChange handler.
|
||||
* The `useOnViewportChange` hook lets you listen for changes to the viewport such
|
||||
* as panning and zooming. You can provide a callback for each phase of a viewport
|
||||
* change: `onStart`, `onChange`, and `onEnd`.
|
||||
*
|
||||
* @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
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useCallback } from 'react';
|
||||
*import { useOnViewportChange } from '@xyflow/react';
|
||||
*
|
||||
*function ViewportChangeLogger() {
|
||||
* useOnViewportChange({
|
||||
* onStart: (viewport: Viewport) => console.log('start', viewport),
|
||||
* onChange: (viewport: Viewport) => console.log('change', viewport),
|
||||
* onEnd: (viewport: Viewport) => console.log('end', viewport),
|
||||
* });
|
||||
*
|
||||
* return null;
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useOnViewportChange({ onStart, onChange, onEnd }: UseOnViewportChangeOptions) {
|
||||
const store = useStoreApi();
|
||||
|
||||
@@ -15,15 +15,44 @@ import useViewportHelper from './useViewportHelper';
|
||||
import { useStore, useStoreApi } from './useStore';
|
||||
import { useBatchContext } from '../components/BatchProvider';
|
||||
import { elementToRemoveChange, isEdge, isNode } from '../utils';
|
||||
import type { ReactFlowInstance, Node, Edge, InternalNode, ReactFlowState, GeneralHelpers } from '../types';
|
||||
import type {
|
||||
ReactFlowInstance,
|
||||
Node,
|
||||
Edge,
|
||||
InternalNode,
|
||||
ReactFlowState,
|
||||
GeneralHelpers,
|
||||
FitViewOptions,
|
||||
} from '../types';
|
||||
|
||||
const selector = (s: ReactFlowState) => !!s.panZoom;
|
||||
|
||||
/**
|
||||
* Hook for accessing the ReactFlow instance.
|
||||
* This hook returns a ReactFlowInstance that can be used to update nodes and edges, manipulate the viewport, or query the current state of the flow.
|
||||
*
|
||||
* @public
|
||||
* @returns ReactFlowInstance
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useCallback, useState } from 'react';
|
||||
*import { useReactFlow } from '@xyflow/react';
|
||||
*
|
||||
*export function NodeCounter() {
|
||||
* const reactFlow = useReactFlow();
|
||||
* const [count, setCount] = useState(0);
|
||||
* const countNodes = useCallback(() => {
|
||||
* setCount(reactFlow.getNodes().length);
|
||||
* // you need to pass it as a dependency if you are using it with useEffect or useCallback
|
||||
* // because at the first render, it's not initialized yet and some functions might not work.
|
||||
* }, [reactFlow]);
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <button onClick={countNodes}>Update count</button>
|
||||
* <p>There are {count} nodes in the flow.</p>
|
||||
* </div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(): ReactFlowInstance<
|
||||
NodeType,
|
||||
@@ -72,7 +101,7 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
|
||||
setNodes((prevNodes) =>
|
||||
prevNodes.map((node) => {
|
||||
if (node.id === id) {
|
||||
const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node as NodeType) : nodeUpdate;
|
||||
const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node) : nodeUpdate;
|
||||
return options.replace && isNode(nextNode) ? (nextNode as NodeType) : { ...node, ...nextNode };
|
||||
}
|
||||
|
||||
@@ -89,7 +118,7 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
|
||||
setEdges((prevEdges) =>
|
||||
prevEdges.map((edge) => {
|
||||
if (edge.id === id) {
|
||||
const nextEdge = typeof edgeUpdate === 'function' ? edgeUpdate(edge as EdgeType) : edgeUpdate;
|
||||
const nextEdge = typeof edgeUpdate === 'function' ? edgeUpdate(edge) : edgeUpdate;
|
||||
return options.replace && isEdge(nextEdge) ? (nextEdge as EdgeType) : { ...edge, ...nextEdge };
|
||||
}
|
||||
|
||||
@@ -184,7 +213,7 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
|
||||
return (nodes || store.getState().nodes).filter((n) => {
|
||||
const internalNode = store.getState().nodeLookup.get(n.id);
|
||||
|
||||
if (internalNode && !isRect && (n.id === nodeOrRect!.id || !internalNode.internals.positionAbsolute)) {
|
||||
if (internalNode && !isRect && (n.id === nodeOrRect.id || !internalNode.internals.positionAbsolute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -248,6 +277,17 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
|
||||
.connectionLookup.get(`${nodeId}${type ? (handleId ? `-${type}-${handleId}` : `-${type}`) : ''}`)
|
||||
?.values() ?? []
|
||||
),
|
||||
fitView: async (options: FitViewOptions<NodeType> | undefined) => {
|
||||
// We either create a new Promise or reuse the existing one
|
||||
// Even if fitView is called multiple times in a row, we only end up with a single Promise
|
||||
const fitViewResolver = store.getState().fitViewResolver ?? Promise.withResolvers<boolean>();
|
||||
|
||||
// We schedule a fitView by setting fitViewQueued and triggering a setNodes
|
||||
store.setState({ fitViewQueued: true, fitViewOptions: options, fitViewResolver });
|
||||
batchContext.nodeQueue.push((nodes) => [...nodes]);
|
||||
|
||||
return fitViewResolver.promise;
|
||||
},
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export function useResizeHandler(domNode: MutableRefObject<HTMLDivElement | null
|
||||
if (!domNode.current) {
|
||||
return false;
|
||||
}
|
||||
const size = getDimensions(domNode.current!);
|
||||
const size = getDimensions(domNode.current);
|
||||
|
||||
if (size.height === 0 || size.width === 0) {
|
||||
store.getState().onError?.('004', errorMessages['error004']());
|
||||
|
||||
@@ -9,16 +9,27 @@ import type { Edge, Node, ReactFlowState } from '../types';
|
||||
const zustandErrorMessage = errorMessages['error001']();
|
||||
|
||||
/**
|
||||
* Hook for accessing the internal store. Should only be used in rare cases.
|
||||
* This hook can be used to subscribe to internal state changes of the React Flow
|
||||
* component. The `useStore` hook is re-exported from the [Zustand](https://github.com/pmndrs/zustand)
|
||||
* state management library, so you should check out their docs for more details.
|
||||
*
|
||||
* @public
|
||||
* @param selector
|
||||
* @param equalityFn
|
||||
* @returns The selected state slice
|
||||
* @param selector - A selector function that returns a slice of the flow's internal state.
|
||||
* Extracting or transforming just the state you need is a good practice to avoid unnecessary
|
||||
* re-renders.
|
||||
* @param equalityFn - A function to compare the previous and next value. This is incredibly useful
|
||||
* for preventing unnecessary re-renders. Good sensible defaults are using `Object.is` or importing
|
||||
* `zustand/shallow`, but you can be as granular as you like.
|
||||
* @returns The selected state slice.
|
||||
*
|
||||
* @example
|
||||
* const nodes = useStore((state: ReactFlowState<MyNodeType>) => state.nodes);
|
||||
* ```ts
|
||||
* const nodes = useStore((state) => state.nodes);
|
||||
* ```
|
||||
*
|
||||
* @remarks This hook should only be used if there is no other way to access the internal
|
||||
* state. For many of the common use cases, there are dedicated hooks available
|
||||
* such as {@link useReactFlow}, {@link useViewport}, etc.
|
||||
*/
|
||||
function useStore<StateSlice = unknown>(
|
||||
selector: (state: ReactFlowState) => StateSlice,
|
||||
@@ -33,6 +44,19 @@ function useStore<StateSlice = unknown>(
|
||||
return useZustandStore(store, selector, equalityFn);
|
||||
}
|
||||
|
||||
/**
|
||||
* In some cases, you might need to access the store directly. This hook returns the store object which can be used on demand to access the state or dispatch actions.
|
||||
*
|
||||
* @returns The store object.
|
||||
* @example
|
||||
* ```ts
|
||||
* const store = useStoreApi();
|
||||
* ```
|
||||
*
|
||||
* @remarks This hook should only be used if there is no other way to access the internal
|
||||
* state. For many of the common use cases, there are dedicated hooks available
|
||||
* such as {@link useReactFlow}, {@link useViewport}, etc.
|
||||
*/
|
||||
function useStoreApi<NodeType extends Node = Node, EdgeType extends Edge = Edge>() {
|
||||
const store = useContext(StoreContext) as UseBoundStoreWithEqualityFn<
|
||||
StoreApi<ReactFlowState<NodeType, EdgeType>>
|
||||
|
||||
@@ -4,10 +4,49 @@ import type { UpdateNodeInternals, InternalNodeUpdate } from '@xyflow/system';
|
||||
import { useStoreApi } from '../hooks/useStore';
|
||||
|
||||
/**
|
||||
* Hook for updating node internals.
|
||||
* When you programmatically add or remove handles to a node or update a node's
|
||||
* handle position, you need to let React Flow know about it using this hook. This
|
||||
* will update the internal dimensions of the node and properly reposition handles
|
||||
* on the canvas if necessary.
|
||||
*
|
||||
* @public
|
||||
* @returns function for updating node internals
|
||||
* @returns Use this function to tell React Flow to update the internal state of one or more nodes
|
||||
* that you have changed programmatically.
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useCallback, useState } from 'react';
|
||||
*import { Handle, useUpdateNodeInternals } from '@xyflow/react';
|
||||
*
|
||||
*export default function RandomHandleNode({ id }) {
|
||||
* const updateNodeInternals = useUpdateNodeInternals();
|
||||
* const [handleCount, setHandleCount] = useState(0);
|
||||
* const randomizeHandleCount = useCallback(() => {
|
||||
* setHandleCount(Math.floor(Math.random() * 10));
|
||||
* updateNodeInternals(id);
|
||||
* }, [id, updateNodeInternals]);
|
||||
*
|
||||
* return (
|
||||
* <>
|
||||
* {Array.from({ length: handleCount }).map((_, index) => (
|
||||
* <Handle
|
||||
* key={index}
|
||||
* type="target"
|
||||
* position="left"
|
||||
* id={`handle-${index}`}
|
||||
* />
|
||||
* ))}
|
||||
*
|
||||
* <div>
|
||||
* <button onClick={randomizeHandleCount}>Randomize handle count</button>
|
||||
* <p>There are {handleCount} handles on this node.</p>
|
||||
* </div>
|
||||
* </>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
* @remarks This hook can only be used in a component that is a child of a
|
||||
*{@link ReactFlowProvider} or a {@link ReactFlow} component.
|
||||
*/
|
||||
export function useUpdateNodeInternals(): UpdateNodeInternals {
|
||||
const store = useStoreApi();
|
||||
|
||||
@@ -11,10 +11,33 @@ const viewportSelector = (state: ReactFlowState) => ({
|
||||
});
|
||||
|
||||
/**
|
||||
* Hook for getting the current viewport from the store.
|
||||
* The `useViewport` hook is a convenient way to read the current state of the
|
||||
* {@link Viewport} in a component. Components that use this hook
|
||||
* will re-render **whenever the viewport changes**.
|
||||
*
|
||||
* @public
|
||||
* @returns The current viewport
|
||||
* @returns The current viewport.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
*```jsx
|
||||
*import { useViewport } from '@xyflow/react';
|
||||
*
|
||||
*export default function ViewportDisplay() {
|
||||
* const { x, y, zoom } = useViewport();
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <p>
|
||||
* The viewport is currently at ({x}, {y}) and zoomed to {zoom}.
|
||||
* </p>
|
||||
* </div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*
|
||||
* @remarks This hook can only be used in a component that is a child of a
|
||||
*{@link ReactFlowProvider} or a {@link ReactFlow} component.
|
||||
*/
|
||||
export function useViewport(): Viewport {
|
||||
const viewport = useStore(viewportSelector, shallow);
|
||||
|
||||
@@ -2,11 +2,9 @@ import { useMemo } from 'react';
|
||||
import {
|
||||
pointToRendererPoint,
|
||||
getViewportForBounds,
|
||||
getFitViewNodes,
|
||||
fitView,
|
||||
type XYPosition,
|
||||
rendererPointToPoint,
|
||||
getDimensions,
|
||||
SnapGrid,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import { useStoreApi } from '../hooks/useStore';
|
||||
@@ -64,28 +62,6 @@ const useViewportHelper = (): ViewportHelperFunctions => {
|
||||
const [x, y, zoom] = store.getState().transform;
|
||||
return { x, y, zoom };
|
||||
},
|
||||
fitView: (options) => {
|
||||
const { nodeLookup, minZoom, maxZoom, panZoom, domNode } = store.getState();
|
||||
|
||||
if (!panZoom || !domNode) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
const fitViewNodes = getFitViewNodes(nodeLookup, options);
|
||||
const { width, height } = getDimensions(domNode);
|
||||
|
||||
return fitView(
|
||||
{
|
||||
nodes: fitViewNodes,
|
||||
width,
|
||||
height,
|
||||
minZoom,
|
||||
maxZoom,
|
||||
panZoom,
|
||||
},
|
||||
options
|
||||
);
|
||||
},
|
||||
setCenter: async (x, y, options) => {
|
||||
const { width, height, maxZoom, panZoom } = store.getState();
|
||||
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : maxZoom;
|
||||
@@ -119,21 +95,25 @@ const useViewportHelper = (): ViewportHelperFunctions => {
|
||||
|
||||
return Promise.resolve(true);
|
||||
},
|
||||
screenToFlowPosition: (clientPosition: XYPosition, options: { snapToGrid: boolean } = { snapToGrid: true }) => {
|
||||
const { transform, snapGrid, domNode } = store.getState();
|
||||
screenToFlowPosition: (
|
||||
clientPosition: XYPosition,
|
||||
options: { snapToGrid?: boolean; snapGrid?: SnapGrid } = {}
|
||||
) => {
|
||||
const { transform, snapGrid, snapToGrid, domNode } = store.getState();
|
||||
|
||||
if (!domNode) {
|
||||
return clientPosition;
|
||||
}
|
||||
|
||||
const { x: domX, y: domY } = domNode.getBoundingClientRect();
|
||||
|
||||
const correctedPosition = {
|
||||
x: clientPosition.x - domX,
|
||||
y: clientPosition.y - domY,
|
||||
};
|
||||
const _snapGrid = options.snapGrid ?? snapGrid;
|
||||
const _snapToGrid = options.snapToGrid ?? snapToGrid;
|
||||
|
||||
return pointToRendererPoint(correctedPosition, transform, options.snapToGrid, snapGrid);
|
||||
return pointToRendererPoint(correctedPosition, transform, _snapToGrid, _snapGrid);
|
||||
},
|
||||
flowToScreenPosition: (flowPosition: XYPosition) => {
|
||||
const { transform, domNode } = store.getState();
|
||||
|
||||
@@ -8,8 +8,8 @@ import type { Node, ReactFlowState } from '../types';
|
||||
const selector = (onlyRenderVisible: boolean) => (s: ReactFlowState) => {
|
||||
return onlyRenderVisible
|
||||
? getNodesInside<Node>(s.nodeLookup, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true).map(
|
||||
(node) => node.id
|
||||
)
|
||||
(node) => node.id
|
||||
)
|
||||
: Array.from(s.nodeLookup.keys());
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user