@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
'@xyflow/react': patch
|
||||||
|
'@xyflow/svelte': patch
|
||||||
|
'@xyflow/system': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Add more TSDocs to components, hooks, utils funcs and types
|
||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
useEdgesState,
|
useEdgesState,
|
||||||
useOnSelectionChange,
|
useOnSelectionChange,
|
||||||
OnSelectionChangeParams,
|
OnSelectionChangeParams,
|
||||||
OnSelectionChangeFunc,
|
|
||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
|
|
||||||
const initialNodes: Node[] = [
|
const initialNodes: Node[] = [
|
||||||
|
|||||||
@@ -90,4 +90,57 @@ function BackgroundComponent({
|
|||||||
|
|
||||||
BackgroundComponent.displayName = 'Background';
|
BackgroundComponent.displayName = 'Background';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `<Background />` component makes it convenient to render different types of backgrounds common in node-based UIs. It comes with three variants: lines, dots and cross.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* A simple example of how to use the Background component.
|
||||||
|
*
|
||||||
|
* ```tsx
|
||||||
|
* import { useState } from 'react';
|
||||||
|
* import { ReactFlow, Background, BackgroundVariant } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
* export default function Flow() {
|
||||||
|
* return (
|
||||||
|
* <ReactFlow defaultNodes={[...]} defaultEdges={[...]}>
|
||||||
|
* <Background color="#ccc" variant={BackgroundVariant.Dots} />
|
||||||
|
* </ReactFlow>
|
||||||
|
* );
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* In this example you can see how to combine multiple backgrounds
|
||||||
|
*
|
||||||
|
* ```tsx
|
||||||
|
* import { ReactFlow, Background, BackgroundVariant } from '@xyflow/react';
|
||||||
|
* import '@xyflow/react/dist/style.css';
|
||||||
|
*
|
||||||
|
* export default function Flow() {
|
||||||
|
* return (
|
||||||
|
* <ReactFlow defaultNodes={[...]} defaultEdges={[...]}>
|
||||||
|
* <Background
|
||||||
|
* id="1"
|
||||||
|
* gap={10}
|
||||||
|
* color="#f1f1f1"
|
||||||
|
* variant={BackgroundVariant.Lines}
|
||||||
|
* />
|
||||||
|
* <Background
|
||||||
|
* id="2"
|
||||||
|
* gap={100}
|
||||||
|
* color="#ccc"
|
||||||
|
* variant={BackgroundVariant.Lines}
|
||||||
|
* />
|
||||||
|
* </ReactFlow>
|
||||||
|
* );
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
*
|
||||||
|
* When combining multiple <Background /> components it’s important to give each of them a unique id prop!
|
||||||
|
*
|
||||||
|
*/
|
||||||
export const Background = memo(BackgroundComponent);
|
export const Background = memo(BackgroundComponent);
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
import { CSSProperties } from 'react';
|
import { CSSProperties } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The three variants are exported as an enum for convenience. You can either import
|
||||||
|
* the enum and use it like `BackgroundVariant.Lines` or you can use the raw string
|
||||||
|
* value directly.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export enum BackgroundVariant {
|
export enum BackgroundVariant {
|
||||||
Lines = 'lines',
|
Lines = 'lines',
|
||||||
Dots = 'dots',
|
Dots = 'dots',
|
||||||
Cross = 'cross',
|
Cross = 'cross',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expand
|
||||||
|
*/
|
||||||
export type BackgroundProps = {
|
export type BackgroundProps = {
|
||||||
id?: string;
|
id?: string;
|
||||||
/** Color of the pattern */
|
/** Color of the pattern */
|
||||||
@@ -24,7 +33,8 @@ export type BackgroundProps = {
|
|||||||
offset?: number | [number, number];
|
offset?: number | [number, number];
|
||||||
/** Line width of the Line pattern */
|
/** Line width of the Line pattern */
|
||||||
lineWidth?: number;
|
lineWidth?: number;
|
||||||
/** Variant of the pattern
|
/**
|
||||||
|
* Variant of the pattern
|
||||||
* @example BackgroundVariant.Lines, BackgroundVariant.Dots, BackgroundVariant.Cross
|
* @example BackgroundVariant.Lines, BackgroundVariant.Dots, BackgroundVariant.Cross
|
||||||
* 'lines', 'dots', 'cross'
|
* 'lines', 'dots', 'cross'
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,6 +2,29 @@ import cc from 'classcat';
|
|||||||
|
|
||||||
import type { ControlButtonProps } from './types';
|
import type { ControlButtonProps } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You can add buttons to the control panel by using the `<ControlButton />` component
|
||||||
|
* and pass it as a child to the [`<Controls />`](/api-reference/components/controls) component.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @example
|
||||||
|
*```jsx
|
||||||
|
*import { MagicWand } from '@radix-ui/react-icons'
|
||||||
|
*import { ReactFlow, Controls, ControlButton } from '@xyflow/react'
|
||||||
|
*
|
||||||
|
*export default function Flow() {
|
||||||
|
* return (
|
||||||
|
* <ReactFlow nodes={[...]} edges={[...]}>
|
||||||
|
* <Controls>
|
||||||
|
* <ControlButton onClick={() => alert('Something magical just happened. ✨')}>
|
||||||
|
* <MagicWand />
|
||||||
|
* </ControlButton>
|
||||||
|
* </Controls>
|
||||||
|
* </ReactFlow>
|
||||||
|
* )
|
||||||
|
*}
|
||||||
|
*```
|
||||||
|
*/
|
||||||
export function ControlButton({ children, className, ...rest }: ControlButtonProps) {
|
export function ControlButton({ children, className, ...rest }: ControlButtonProps) {
|
||||||
return (
|
return (
|
||||||
<button type="button" className={cc(['react-flow__controls-button', className])} {...rest}>
|
<button type="button" className={cc(['react-flow__controls-button', className])} {...rest}>
|
||||||
|
|||||||
@@ -125,4 +125,25 @@ function ControlsComponent({
|
|||||||
|
|
||||||
ControlsComponent.displayName = 'Controls';
|
ControlsComponent.displayName = 'Controls';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `<Controls />` component renders a small panel that contains convenient
|
||||||
|
* buttons to zoom in, zoom out, fit the view, and lock the viewport.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @example
|
||||||
|
*```tsx
|
||||||
|
*import { ReactFlow, Controls } from '@xyflow/react'
|
||||||
|
*
|
||||||
|
*export default function Flow() {
|
||||||
|
* return (
|
||||||
|
* <ReactFlow nodes={[...]} edges={[...]}>
|
||||||
|
* <Controls />
|
||||||
|
* </ReactFlow>
|
||||||
|
* )
|
||||||
|
*}
|
||||||
|
*```
|
||||||
|
*
|
||||||
|
* @remarks To extend or customise the controls, you can use the [`<ControlButton />`](/api-reference/components/control-button) component
|
||||||
|
*
|
||||||
|
*/
|
||||||
export const Controls = memo(ControlsComponent);
|
export const Controls = memo(ControlsComponent);
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import type { PanelPosition } from '@xyflow/system';
|
|||||||
|
|
||||||
import type { FitViewOptions } from '../../types';
|
import type { FitViewOptions } from '../../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expand
|
||||||
|
*/
|
||||||
export type ControlProps = {
|
export type ControlProps = {
|
||||||
/** Show button for zoom in/out */
|
/** Show button for zoom in/out */
|
||||||
showZoom?: boolean;
|
showZoom?: boolean;
|
||||||
@@ -20,7 +23,8 @@ export type ControlProps = {
|
|||||||
onFitView?: () => void;
|
onFitView?: () => void;
|
||||||
/** Callback when interactivity is toggled */
|
/** Callback when interactivity is toggled */
|
||||||
onInteractiveChange?: (interactiveStatus: boolean) => void;
|
onInteractiveChange?: (interactiveStatus: boolean) => void;
|
||||||
/** Position of the controls on the pane
|
/**
|
||||||
|
* Position of the controls on the pane
|
||||||
* @example PanelPosition.TopLeft, PanelPosition.TopRight,
|
* @example PanelPosition.TopLeft, PanelPosition.TopRight,
|
||||||
* PanelPosition.BottomLeft, PanelPosition.BottomRight
|
* PanelPosition.BottomLeft, PanelPosition.BottomRight
|
||||||
*/
|
*/
|
||||||
@@ -34,4 +38,7 @@ export type ControlProps = {
|
|||||||
orientation?: 'horizontal' | 'vertical';
|
orientation?: 'horizontal' | 'vertical';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expand
|
||||||
|
*/
|
||||||
export type ControlButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
|
export type ControlButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
|
||||||
|
|||||||
@@ -44,8 +44,10 @@ function MiniMapComponent<NodeType extends Node = Node>({
|
|||||||
nodeClassName = '',
|
nodeClassName = '',
|
||||||
nodeBorderRadius = 5,
|
nodeBorderRadius = 5,
|
||||||
nodeStrokeWidth,
|
nodeStrokeWidth,
|
||||||
// We need to rename the prop to be `CapitalCase` so that JSX will render it as
|
/*
|
||||||
// a component properly.
|
* We need to rename the prop to be `CapitalCase` so that JSX will render it as
|
||||||
|
* a component properly.
|
||||||
|
*/
|
||||||
nodeComponent,
|
nodeComponent,
|
||||||
bgColor,
|
bgColor,
|
||||||
maskColor,
|
maskColor,
|
||||||
@@ -111,16 +113,16 @@ function MiniMapComponent<NodeType extends Node = Node>({
|
|||||||
|
|
||||||
const onSvgClick = onClick
|
const onSvgClick = onClick
|
||||||
? (event: MouseEvent) => {
|
? (event: MouseEvent) => {
|
||||||
const [x, y] = minimapInstance.current?.pointer(event) || [0, 0];
|
const [x, y] = minimapInstance.current?.pointer(event) || [0, 0];
|
||||||
onClick(event, { x, y });
|
onClick(event, { x, y });
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const onSvgNodeClick = onNodeClick
|
const onSvgNodeClick = onNodeClick
|
||||||
? useCallback((event: MouseEvent, nodeId: string) => {
|
? useCallback((event: MouseEvent, nodeId: string) => {
|
||||||
const node = store.getState().nodeLookup.get(nodeId)!;
|
const node = store.getState().nodeLookup.get(nodeId)!;
|
||||||
onNodeClick(event, node);
|
onNodeClick(event, node);
|
||||||
}, [])
|
}, [])
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -176,4 +178,24 @@ function MiniMapComponent<NodeType extends Node = Node>({
|
|||||||
|
|
||||||
MiniMapComponent.displayName = 'MiniMap';
|
MiniMapComponent.displayName = 'MiniMap';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `<MiniMap />` component can be used to render an overview of your flow. It
|
||||||
|
* renders each node as an SVG element and visualizes where the current viewport is
|
||||||
|
* in relation to the rest of the flow.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```jsx
|
||||||
|
*import { ReactFlow, MiniMap } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*export default function Flow() {
|
||||||
|
* return (
|
||||||
|
* <ReactFlow nodes={[...]]} edges={[...]]}>
|
||||||
|
* <MiniMap nodeStrokeWidth={3} />
|
||||||
|
* </ReactFlow>
|
||||||
|
* );
|
||||||
|
*}
|
||||||
|
*```
|
||||||
|
*/
|
||||||
export const MiniMap = memo(MiniMapComponent) as typeof MiniMapComponent;
|
export const MiniMap = memo(MiniMapComponent) as typeof MiniMapComponent;
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ function MiniMapNodes<NodeType extends Node>({
|
|||||||
nodeClassName = '',
|
nodeClassName = '',
|
||||||
nodeBorderRadius = 5,
|
nodeBorderRadius = 5,
|
||||||
nodeStrokeWidth,
|
nodeStrokeWidth,
|
||||||
// We need to rename the prop to be `CapitalCase` so that JSX will render it as
|
/*
|
||||||
// a component properly.
|
* We need to rename the prop to be `CapitalCase` so that JSX will render it as
|
||||||
|
* a component properly.
|
||||||
|
*/
|
||||||
nodeComponent: NodeComponent = MiniMapNode,
|
nodeComponent: NodeComponent = MiniMapNode,
|
||||||
onClick,
|
onClick,
|
||||||
}: MiniMapNodesProps<NodeType>) {
|
}: MiniMapNodesProps<NodeType>) {
|
||||||
@@ -36,11 +38,13 @@ function MiniMapNodes<NodeType extends Node>({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{nodeIds.map((nodeId) => (
|
{nodeIds.map((nodeId) => (
|
||||||
// The split of responsibilities between MiniMapNodes and
|
/*
|
||||||
// NodeComponentWrapper may appear weird. However, it’s designed to
|
* The split of responsibilities between MiniMapNodes and
|
||||||
// minimize the cost of updates when individual nodes change.
|
* NodeComponentWrapper may appear weird. However, it’s designed to
|
||||||
//
|
* minimize the cost of updates when individual nodes change.
|
||||||
// For more details, see a similar commit in `NodeRenderer/index.tsx`.
|
*
|
||||||
|
* For more details, see a similar commit in `NodeRenderer/index.tsx`.
|
||||||
|
*/
|
||||||
<NodeComponentWrapper<NodeType>
|
<NodeComponentWrapper<NodeType>
|
||||||
key={nodeId}
|
key={nodeId}
|
||||||
id={nodeId}
|
id={nodeId}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import type { Node } from '../../types';
|
|||||||
|
|
||||||
export type GetMiniMapNodeAttribute<NodeType extends Node = Node> = (node: NodeType) => string;
|
export type GetMiniMapNodeAttribute<NodeType extends Node = Node> = (node: NodeType) => string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expand
|
||||||
|
*/
|
||||||
export type MiniMapProps<NodeType extends Node = Node> = Omit<HTMLAttributes<SVGSVGElement>, 'onClick'> & {
|
export type MiniMapProps<NodeType extends Node = Node> = Omit<HTMLAttributes<SVGSVGElement>, 'onClick'> & {
|
||||||
/** Color of nodes on minimap */
|
/** Color of nodes on minimap */
|
||||||
nodeColor?: string | GetMiniMapNodeAttribute<NodeType>;
|
nodeColor?: string | GetMiniMapNodeAttribute<NodeType>;
|
||||||
@@ -27,7 +30,8 @@ export type MiniMapProps<NodeType extends Node = Node> = Omit<HTMLAttributes<SVG
|
|||||||
maskStrokeColor?: string;
|
maskStrokeColor?: string;
|
||||||
/** Stroke width of mask representing viewport */
|
/** Stroke width of mask representing viewport */
|
||||||
maskStrokeWidth?: number;
|
maskStrokeWidth?: number;
|
||||||
/** Position of minimap on pane
|
/**
|
||||||
|
* Position of minimap on pane
|
||||||
* @example PanelPosition.TopLeft, PanelPosition.TopRight,
|
* @example PanelPosition.TopLeft, PanelPosition.TopRight,
|
||||||
* PanelPosition.BottomLeft, PanelPosition.BottomRight
|
* PanelPosition.BottomLeft, PanelPosition.BottomRight
|
||||||
*/
|
*/
|
||||||
@@ -57,6 +61,12 @@ export type MiniMapNodes<NodeType extends Node = Node> = Pick<
|
|||||||
onClick?: (event: MouseEvent, nodeId: string) => void;
|
onClick?: (event: MouseEvent, nodeId: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The props that are passed to the MiniMapNode component
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @expand
|
||||||
|
*/
|
||||||
export type MiniMapNodeProps = {
|
export type MiniMapNodeProps = {
|
||||||
id: string;
|
id: string;
|
||||||
x: number;
|
x: number;
|
||||||
|
|||||||
@@ -99,8 +99,10 @@ function ResizeControl({
|
|||||||
const parentExpandChanges = handleExpandParent([child], nodeLookup, parentLookup, nodeOrigin);
|
const parentExpandChanges = handleExpandParent([child], nodeLookup, parentLookup, nodeOrigin);
|
||||||
changes.push(...parentExpandChanges);
|
changes.push(...parentExpandChanges);
|
||||||
|
|
||||||
// when the parent was expanded by the child node, its position will be clamped at
|
/*
|
||||||
// 0,0 when node origin is 0,0 and to width, height if it's 1,1
|
* when the parent was expanded by the child node, its position will be clamped at
|
||||||
|
* 0,0 when node origin is 0,0 and to width, height if it's 1,1
|
||||||
|
*/
|
||||||
nextPosition.x = change.x ? Math.max(origin[0] * width, change.x) : undefined;
|
nextPosition.x = change.x ? Math.max(origin[0] * width, change.x) : undefined;
|
||||||
nextPosition.y = change.y ? Math.max(origin[1] * height, change.y) : undefined;
|
nextPosition.y = change.y ? Math.max(origin[1] * height, change.y) : undefined;
|
||||||
}
|
}
|
||||||
@@ -201,4 +203,9 @@ export function ResizeControlLine(props: ResizeControlLineProps) {
|
|||||||
return <ResizeControl {...props} variant={ResizeControlVariant.Line} />;
|
return <ResizeControl {...props} variant={ResizeControlVariant.Line} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To create your own resizing UI, you can use the `NodeResizeControl` component where you can pass children (such as icons).
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
*/
|
||||||
export const NodeResizeControl = memo(ResizeControl);
|
export const NodeResizeControl = memo(ResizeControl);
|
||||||
|
|||||||
@@ -3,6 +3,30 @@ import { ResizeControlVariant, XY_RESIZER_HANDLE_POSITIONS, XY_RESIZER_LINE_POSI
|
|||||||
import { NodeResizeControl } from './NodeResizeControl';
|
import { NodeResizeControl } from './NodeResizeControl';
|
||||||
import type { NodeResizerProps } from './types';
|
import type { NodeResizerProps } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `<NodeResizer />` component can be used to add a resize functionality to your
|
||||||
|
* nodes. It renders draggable controls around the node to resize in all directions.
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*```jsx
|
||||||
|
*import { memo } from 'react';
|
||||||
|
*import { Handle, Position, NodeResizer } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*function ResizableNode({ data }) {
|
||||||
|
* return (
|
||||||
|
* <>
|
||||||
|
* <NodeResizer minWidth={100} minHeight={30} />
|
||||||
|
* <Handle type="target" position={Position.Left} />
|
||||||
|
* <div style={{ padding: 10 }}>{data.label}</div>
|
||||||
|
* <Handle type="source" position={Position.Right} />
|
||||||
|
* </>
|
||||||
|
* );
|
||||||
|
*};
|
||||||
|
*
|
||||||
|
*export default memo(ResizableNode);
|
||||||
|
*```
|
||||||
|
*/
|
||||||
export function NodeResizer({
|
export function NodeResizer({
|
||||||
nodeId,
|
nodeId,
|
||||||
isVisible = true,
|
isVisible = true,
|
||||||
|
|||||||
@@ -9,8 +9,12 @@ import type {
|
|||||||
OnResizeEnd,
|
OnResizeEnd,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expand
|
||||||
|
*/
|
||||||
export type NodeResizerProps = {
|
export type NodeResizerProps = {
|
||||||
/** Id of the node it is resizing
|
/**
|
||||||
|
* Id of the node it is resizing
|
||||||
* @remarks optional if used inside custom node
|
* @remarks optional if used inside custom node
|
||||||
*/
|
*/
|
||||||
nodeId?: string;
|
nodeId?: string;
|
||||||
@@ -46,6 +50,9 @@ export type NodeResizerProps = {
|
|||||||
onResizeEnd?: OnResizeEnd;
|
onResizeEnd?: OnResizeEnd;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expand
|
||||||
|
*/
|
||||||
export type ResizeControlProps = Pick<
|
export type ResizeControlProps = Pick<
|
||||||
NodeResizerProps,
|
NodeResizerProps,
|
||||||
| 'nodeId'
|
| 'nodeId'
|
||||||
@@ -60,12 +67,14 @@ export type ResizeControlProps = Pick<
|
|||||||
| 'onResize'
|
| 'onResize'
|
||||||
| 'onResizeEnd'
|
| 'onResizeEnd'
|
||||||
> & {
|
> & {
|
||||||
/** Position of the control
|
/**
|
||||||
|
* Position of the control
|
||||||
* @example ControlPosition.TopLeft, ControlPosition.TopRight,
|
* @example ControlPosition.TopLeft, ControlPosition.TopRight,
|
||||||
* ControlPosition.BottomLeft, ControlPosition.BottomRight
|
* ControlPosition.BottomLeft, ControlPosition.BottomRight
|
||||||
*/
|
*/
|
||||||
position?: ControlPosition;
|
position?: ControlPosition;
|
||||||
/** Variant of the control
|
/**
|
||||||
|
* Variant of the control
|
||||||
* @example ResizeControlVariant.Handle, ResizeControlVariant.Line
|
* @example ResizeControlVariant.Handle, ResizeControlVariant.Line
|
||||||
*/
|
*/
|
||||||
variant?: ResizeControlVariant;
|
variant?: ResizeControlVariant;
|
||||||
@@ -74,6 +83,9 @@ export type ResizeControlProps = Pick<
|
|||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expand
|
||||||
|
*/
|
||||||
export type ResizeControlLineProps = ResizeControlProps & {
|
export type ResizeControlLineProps = ResizeControlProps & {
|
||||||
position?: ControlLinePosition;
|
position?: ControlLinePosition;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,6 +38,41 @@ const storeSelector = (state: ReactFlowState) => ({
|
|||||||
selectedNodesCount: state.nodes.filter((node) => node.selected).length,
|
selectedNodesCount: state.nodes.filter((node) => node.selected).length,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component can render a toolbar or tooltip to one side of a custom node. This
|
||||||
|
* toolbar doesn't scale with the viewport so that the content is always visible.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @example
|
||||||
|
* ```jsx
|
||||||
|
*import { memo } from 'react';
|
||||||
|
*import { Handle, Position, NodeToolbar } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*function CustomNode({ data }) {
|
||||||
|
* return (
|
||||||
|
* <>
|
||||||
|
* <NodeToolbar isVisible={data.toolbarVisible} position={data.toolbarPosition}>
|
||||||
|
* <button>delete</button>
|
||||||
|
* <button>copy</button>
|
||||||
|
* <button>expand</button>
|
||||||
|
* </NodeToolbar>
|
||||||
|
*
|
||||||
|
* <div style={{ padding: '10px 20px' }}>
|
||||||
|
* {data.label}
|
||||||
|
* </div>
|
||||||
|
*
|
||||||
|
* <Handle type="target" position={Position.Left} />
|
||||||
|
* <Handle type="source" position={Position.Right} />
|
||||||
|
* </>
|
||||||
|
* );
|
||||||
|
*};
|
||||||
|
*
|
||||||
|
*export default memo(CustomNode);
|
||||||
|
*```
|
||||||
|
* @remarks By default, the toolbar is only visible when a node is selected. If multiple
|
||||||
|
* nodes are selected it will not be visible to prevent overlapping toolbars or
|
||||||
|
* clutter. You can override this behavior by setting the `isVisible` prop to `true`.
|
||||||
|
*/
|
||||||
export function NodeToolbar({
|
export function NodeToolbar({
|
||||||
nodeId,
|
nodeId,
|
||||||
children,
|
children,
|
||||||
@@ -74,7 +109,7 @@ export function NodeToolbar({
|
|||||||
const isActive =
|
const isActive =
|
||||||
typeof isVisible === 'boolean'
|
typeof isVisible === 'boolean'
|
||||||
? isVisible
|
? isVisible
|
||||||
: nodes.size === 1 && nodes.values().next().value.selected && selectedNodesCount === 1;
|
: nodes.size === 1 && nodes.values().next().value?.selected && selectedNodesCount === 1;
|
||||||
|
|
||||||
if (!isActive || !nodes.size) {
|
if (!isActive || !nodes.size) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
import type { HTMLAttributes } from 'react';
|
import type { HTMLAttributes } from 'react';
|
||||||
import type { Position, Align } from '@xyflow/system';
|
import type { Position, Align } from '@xyflow/system';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expand
|
||||||
|
*/
|
||||||
export type NodeToolbarProps = HTMLAttributes<HTMLDivElement> & {
|
export type NodeToolbarProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
/** Id of the node, or array of ids the toolbar should be displayed at */
|
/** Id of the node, or array of ids the toolbar should be displayed at */
|
||||||
nodeId?: string | string[];
|
nodeId?: string | string[];
|
||||||
/** If true, node toolbar is visible even if node is not selected */
|
/** If true, node toolbar is visible even if node is not selected */
|
||||||
isVisible?: boolean;
|
isVisible?: boolean;
|
||||||
/** Position of the toolbar relative to the node
|
/**
|
||||||
|
* Position of the toolbar relative to the node
|
||||||
* @example Position.TopLeft, Position.TopRight,
|
* @example Position.TopLeft, Position.TopRight,
|
||||||
* Position.BottomLeft, Position.BottomRight
|
* Position.BottomLeft, Position.BottomRight
|
||||||
*/
|
*/
|
||||||
position?: Position;
|
position?: Position;
|
||||||
/** Offset the toolbar from the node */
|
/** Offset the toolbar from the node */
|
||||||
offset?: number;
|
offset?: number;
|
||||||
/** Align the toolbar relative to the node
|
/**
|
||||||
|
* Align the toolbar relative to the node
|
||||||
* @example Align.Start, Align.Center, Align.End
|
* @example Align.Start, Align.Center, Align.End
|
||||||
*/
|
*/
|
||||||
align?: Align;
|
align?: Align;
|
||||||
|
|||||||
@@ -30,9 +30,11 @@ export function BatchProvider<NodeType extends Node = Node, EdgeType extends Edg
|
|||||||
const nodeQueueHandler = useCallback((queueItems: QueueItem<NodeType>[]) => {
|
const nodeQueueHandler = useCallback((queueItems: QueueItem<NodeType>[]) => {
|
||||||
const { nodes = [], setNodes, hasDefaultNodes, onNodesChange, nodeLookup } = store.getState();
|
const { nodes = [], setNodes, hasDefaultNodes, onNodesChange, nodeLookup } = store.getState();
|
||||||
|
|
||||||
// This is essentially an `Array.reduce` in imperative clothing. Processing
|
/*
|
||||||
// this queue is a relatively hot path so we'd like to avoid the overhead of
|
* This is essentially an `Array.reduce` in imperative clothing. Processing
|
||||||
// array methods where we can.
|
* this queue is a relatively hot path so we'd like to avoid the overhead of
|
||||||
|
* array methods where we can.
|
||||||
|
*/
|
||||||
let next = nodes as NodeType[];
|
let next = nodes as NodeType[];
|
||||||
for (const payload of queueItems) {
|
for (const payload of queueItems) {
|
||||||
next = typeof payload === 'function' ? payload(next) : payload;
|
next = typeof payload === 'function' ? payload(next) : payload;
|
||||||
|
|||||||
@@ -12,21 +12,27 @@ import { Queue, QueueItem } from './types';
|
|||||||
* @returns a Queue object
|
* @returns a Queue object
|
||||||
*/
|
*/
|
||||||
export function useQueue<T>(runQueue: (items: QueueItem<T>[]) => void) {
|
export function useQueue<T>(runQueue: (items: QueueItem<T>[]) => void) {
|
||||||
// Because we're using a ref above, we need some way to let React know when to
|
/*
|
||||||
// actually process the queue. We increment this number any time we mutate the
|
* Because we're using a ref above, we need some way to let React know when to
|
||||||
// queue, creating a new state to trigger the layout effect below.
|
* actually process the queue. We increment this number any time we mutate the
|
||||||
// Using a boolean dirty flag here instead would lead to issues related to
|
* queue, creating a new state to trigger the layout effect below.
|
||||||
// automatic batching. (https://github.com/xyflow/xyflow/issues/4779)
|
* Using a boolean dirty flag here instead would lead to issues related to
|
||||||
|
* automatic batching. (https://github.com/xyflow/xyflow/issues/4779)
|
||||||
|
*/
|
||||||
const [serial, setSerial] = useState(BigInt(0));
|
const [serial, setSerial] = useState(BigInt(0));
|
||||||
|
|
||||||
// A reference of all the batched updates to process before the next render. We
|
/*
|
||||||
// want a reference here so multiple synchronous calls to `setNodes` etc can be
|
* A reference of all the batched updates to process before the next render. We
|
||||||
// batched together.
|
* want a reference here so multiple synchronous calls to `setNodes` etc can be
|
||||||
|
* batched together.
|
||||||
|
*/
|
||||||
const [queue] = useState(() => createQueue<T>(() => setSerial(n => n + BigInt(1))));
|
const [queue] = useState(() => createQueue<T>(() => setSerial(n => n + BigInt(1))));
|
||||||
|
|
||||||
// Layout effects are guaranteed to run before the next render which means we
|
/*
|
||||||
// shouldn't run into any issues with stale state or weird issues that come from
|
* Layout effects are guaranteed to run before the next render which means we
|
||||||
// rendering things one frame later than expected (we used to use `setTimeout`).
|
* shouldn't run into any issues with stale state or weird issues that come from
|
||||||
|
* rendering things one frame later than expected (we used to use `setTimeout`).
|
||||||
|
*/
|
||||||
useIsomorphicLayoutEffect(() => {
|
useIsomorphicLayoutEffect(() => {
|
||||||
const queueItems = queue.get();
|
const queueItems = queue.get();
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,46 @@ import type { ReactFlowState } from '../../types';
|
|||||||
|
|
||||||
const selector = (s: ReactFlowState) => s.domNode?.querySelector('.react-flow__edgelabel-renderer');
|
const selector = (s: ReactFlowState) => s.domNode?.querySelector('.react-flow__edgelabel-renderer');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edges are SVG-based. If you want to render more complex labels you can use the
|
||||||
|
* `<EdgeLabelRenderer />` component to access a div based renderer. This component
|
||||||
|
* is a portal that renders the label in a `<div />` that is positioned on top of
|
||||||
|
* the edges. You can see an example usage of the component in the [edge label renderer](/examples/edges/edge-label-renderer) example.
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*```jsx
|
||||||
|
*import React from 'react';
|
||||||
|
*import { getBezierPath, EdgeLabelRenderer, BaseEdge } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*export function CustomEdge({ id, data, ...props }) {
|
||||||
|
* const [edgePath, labelX, labelY] = getBezierPath(props);
|
||||||
|
*
|
||||||
|
* return (
|
||||||
|
* <>
|
||||||
|
* <BaseEdge id={id} path={edgePath} />
|
||||||
|
* <EdgeLabelRenderer>
|
||||||
|
* <div
|
||||||
|
* style={{
|
||||||
|
* position: 'absolute',
|
||||||
|
* transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
||||||
|
* background: '#ffcc00',
|
||||||
|
* padding: 10,
|
||||||
|
* }}
|
||||||
|
* className="nodrag nopan"
|
||||||
|
* >
|
||||||
|
* {data.label}
|
||||||
|
* </div>
|
||||||
|
* </EdgeLabelRenderer>
|
||||||
|
* </>
|
||||||
|
* );
|
||||||
|
*};
|
||||||
|
*```
|
||||||
|
*
|
||||||
|
* @remarks The `<EdgeLabelRenderer />` has no pointer events by default. If you want to
|
||||||
|
* add mouse interactions you need to set the style `pointerEvents: all` and add
|
||||||
|
* the `nopan` class on the label or the element you want to interact with.
|
||||||
|
*/
|
||||||
export function EdgeLabelRenderer({ children }: { children: ReactNode }) {
|
export function EdgeLabelRenderer({ children }: { children: ReactNode }) {
|
||||||
const edgeLabelRenderer = useStore(selector);
|
const edgeLabelRenderer = useStore(selector);
|
||||||
|
|
||||||
|
|||||||
@@ -136,28 +136,28 @@ export function EdgeWrapper<EdgeType extends Edge = Edge>({
|
|||||||
|
|
||||||
const onEdgeDoubleClick = onDoubleClick
|
const onEdgeDoubleClick = onDoubleClick
|
||||||
? (event: React.MouseEvent) => {
|
? (event: React.MouseEvent) => {
|
||||||
onDoubleClick(event, { ...edge });
|
onDoubleClick(event, { ...edge });
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
const onEdgeContextMenu = onContextMenu
|
const onEdgeContextMenu = onContextMenu
|
||||||
? (event: React.MouseEvent) => {
|
? (event: React.MouseEvent) => {
|
||||||
onContextMenu(event, { ...edge });
|
onContextMenu(event, { ...edge });
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
const onEdgeMouseEnter = onMouseEnter
|
const onEdgeMouseEnter = onMouseEnter
|
||||||
? (event: React.MouseEvent) => {
|
? (event: React.MouseEvent) => {
|
||||||
onMouseEnter(event, { ...edge });
|
onMouseEnter(event, { ...edge });
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
const onEdgeMouseMove = onMouseMove
|
const onEdgeMouseMove = onMouseMove
|
||||||
? (event: React.MouseEvent) => {
|
? (event: React.MouseEvent) => {
|
||||||
onMouseMove(event, { ...edge });
|
onMouseMove(event, { ...edge });
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
const onEdgeMouseLeave = onMouseLeave
|
const onEdgeMouseLeave = onMouseLeave
|
||||||
? (event: React.MouseEvent) => {
|
? (event: React.MouseEvent) => {
|
||||||
onMouseLeave(event, { ...edge });
|
onMouseLeave(event, { ...edge });
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
|
|||||||
@@ -4,6 +4,33 @@ import cc from 'classcat';
|
|||||||
import { EdgeText } from './EdgeText';
|
import { EdgeText } from './EdgeText';
|
||||||
import type { BaseEdgeProps } from '../../types';
|
import type { BaseEdgeProps } from '../../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `<BaseEdge />` component gets used internally for all the edges. It can be
|
||||||
|
* used inside a custom edge and handles the invisible helper edge and the edge label
|
||||||
|
* for you.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @example
|
||||||
|
* ```jsx
|
||||||
|
*import { BaseEdge } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*export function CustomEdge({ sourceX, sourceY, targetX, targetY, ...props }) {
|
||||||
|
* const [edgePath] = getStraightPath({
|
||||||
|
* sourceX,
|
||||||
|
* sourceY,
|
||||||
|
* targetX,
|
||||||
|
* targetY,
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* return <BaseEdge path={edgePath} {...props} />;
|
||||||
|
*}
|
||||||
|
*```
|
||||||
|
*
|
||||||
|
* @remarks If you want to use an edge marker with the [`<BaseEdge />`](/api-reference/components/base-edge) component,
|
||||||
|
* you can pass the `markerStart` or `markerEnd` props passed to your custom edge
|
||||||
|
* through to the [`<BaseEdge />`](/api-reference/components/base-edge) component.
|
||||||
|
* You can see all the props passed to a custom edge by looking at the [`EdgeProps`](/api-reference/types/edge-props) type.
|
||||||
|
*/
|
||||||
export function BaseEdge({
|
export function BaseEdge({
|
||||||
path,
|
path,
|
||||||
labelX,
|
labelX,
|
||||||
|
|||||||
@@ -73,4 +73,30 @@ function EdgeTextComponent({
|
|||||||
|
|
||||||
EdgeTextComponent.displayName = 'EdgeText';
|
EdgeTextComponent.displayName = 'EdgeText';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You can use the `<EdgeText />` component as a helper component to display text
|
||||||
|
* within your custom edges.
|
||||||
|
*
|
||||||
|
*@public
|
||||||
|
*
|
||||||
|
*@example
|
||||||
|
*```jsx
|
||||||
|
*import { EdgeText } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*export function CustomEdgeLabel({ label }) {
|
||||||
|
* return (
|
||||||
|
* <EdgeText
|
||||||
|
* x={100}
|
||||||
|
* y={100}
|
||||||
|
* label={label}
|
||||||
|
* labelStyle={{ fill: 'white' }}
|
||||||
|
* labelShowBg
|
||||||
|
* labelBgStyle={{ fill: 'red' }}
|
||||||
|
* labelBgPadding={[2, 4]}
|
||||||
|
* labelBgBorderRadius={2}
|
||||||
|
* />
|
||||||
|
* );
|
||||||
|
*}
|
||||||
|
*```
|
||||||
|
*/
|
||||||
export const EdgeText = memo(EdgeTextComponent);
|
export const EdgeText = memo(EdgeTextComponent);
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ function getControl({ pos, x1, y1, x2, y2 }: GetControlParams): [number, number]
|
|||||||
return [x1, 0.5 * (y1 + y2)];
|
return [x1, 0.5 * (y1 + y2)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `getSimpleBezierPath` util returns everything you need to render a simple
|
||||||
|
* bezier edge between two nodes.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export function getSimpleBezierPath({
|
export function getSimpleBezierPath({
|
||||||
sourceX,
|
sourceX,
|
||||||
sourceY,
|
sourceY,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// We distinguish between internal and exported edges
|
/*
|
||||||
// The internal edges are used directly like custom edges and always get an id, source and target props
|
* We distinguish between internal and exported edges
|
||||||
// If you import an edge from the library, the id is optional and source and target are not used at all
|
* The internal edges are used directly like custom edges and always get an id, source and target props
|
||||||
|
* If you import an edge from the library, the id is optional and source and target are not used at all
|
||||||
|
*/
|
||||||
|
|
||||||
export { SimpleBezierEdge, SimpleBezierEdgeInternal } from './SimpleBezierEdge';
|
export { SimpleBezierEdge, SimpleBezierEdgeInternal } from './SimpleBezierEdge';
|
||||||
export { SmoothStepEdge, SmoothStepEdgeInternal } from './SmoothStepEdge';
|
export { SmoothStepEdge, SmoothStepEdgeInternal } from './SmoothStepEdge';
|
||||||
|
|||||||
@@ -28,10 +28,14 @@ import { useNodeId } from '../../contexts/NodeIdContext';
|
|||||||
import { type ReactFlowState } from '../../types';
|
import { type ReactFlowState } from '../../types';
|
||||||
import { fixedForwardRef } from '../../utils';
|
import { fixedForwardRef } from '../../utils';
|
||||||
|
|
||||||
export interface HandleProps extends HandlePropsSystem, Omit<HTMLAttributes<HTMLDivElement>, 'id'> {
|
/**
|
||||||
/** Callback called when connection is made */
|
* @expand
|
||||||
onConnect?: OnConnect;
|
*/
|
||||||
}
|
export type HandleProps = HandlePropsSystem &
|
||||||
|
Omit<HTMLAttributes<HTMLDivElement>, 'id'> & {
|
||||||
|
/** Callback called when connection is made */
|
||||||
|
onConnect?: OnConnect;
|
||||||
|
};
|
||||||
|
|
||||||
const selector = (s: ReactFlowState) => ({
|
const selector = (s: ReactFlowState) => ({
|
||||||
connectOnClick: s.connectOnClick,
|
connectOnClick: s.connectOnClick,
|
||||||
@@ -228,8 +232,10 @@ function HandleComponent(
|
|||||||
connectingfrom: connectingFrom,
|
connectingfrom: connectingFrom,
|
||||||
connectingto: connectingTo,
|
connectingto: connectingTo,
|
||||||
valid,
|
valid,
|
||||||
// shows where you can start a connection from
|
/*
|
||||||
// and where you can end it while connecting
|
* shows where you can start a connection from
|
||||||
|
* and where you can end it while connecting
|
||||||
|
*/
|
||||||
connectionindicator:
|
connectionindicator:
|
||||||
isConnectable &&
|
isConnectable &&
|
||||||
(!connectionInProcess || isPossibleEndHandle) &&
|
(!connectionInProcess || isPossibleEndHandle) &&
|
||||||
@@ -248,6 +254,28 @@ function HandleComponent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Handle component is a UI element that is used to connect nodes.
|
* The `<Handle />` component is used in your [custom nodes](/learn/customization/custom-nodes)
|
||||||
|
* to define connection points.
|
||||||
|
*
|
||||||
|
*@public
|
||||||
|
*
|
||||||
|
*@example
|
||||||
|
*
|
||||||
|
*```jsx
|
||||||
|
*import { Handle, Position } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*export function CustomNode({ data }) {
|
||||||
|
* return (
|
||||||
|
* <>
|
||||||
|
* <div style={{ padding: '10px 20px' }}>
|
||||||
|
* {data.label}
|
||||||
|
* </div>
|
||||||
|
*
|
||||||
|
* <Handle type="target" position={Position.Left} />
|
||||||
|
* <Handle type="source" position={Position.Right} />
|
||||||
|
* </>
|
||||||
|
* );
|
||||||
|
*};
|
||||||
|
*```
|
||||||
*/
|
*/
|
||||||
export const Handle = memo(fixedForwardRef(HandleComponent));
|
export const Handle = memo(fixedForwardRef(HandleComponent));
|
||||||
|
|||||||
@@ -108,8 +108,10 @@ export function NodeWrapper<NodeType extends Node>({
|
|||||||
const { selectNodesOnDrag, nodeDragThreshold } = store.getState();
|
const { selectNodesOnDrag, nodeDragThreshold } = store.getState();
|
||||||
|
|
||||||
if (isSelectable && (!selectNodesOnDrag || !isDraggable || nodeDragThreshold > 0)) {
|
if (isSelectable && (!selectNodesOnDrag || !isDraggable || nodeDragThreshold > 0)) {
|
||||||
// this handler gets called by XYDrag on drag start when selectNodesOnDrag=true
|
/*
|
||||||
// here we only need to call it when selectNodesOnDrag=false
|
* this handler gets called by XYDrag on drag start when selectNodesOnDrag=true
|
||||||
|
* here we only need to call it when selectNodesOnDrag=false
|
||||||
|
*/
|
||||||
handleNodeClick({
|
handleNodeClick({
|
||||||
id,
|
id,
|
||||||
store,
|
store,
|
||||||
|
|||||||
@@ -49,8 +49,10 @@ export function useNodeObserver({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (nodeRef.current) {
|
if (nodeRef.current) {
|
||||||
// when the user programmatically changes the source or handle position, we need to update the internals
|
/*
|
||||||
// to make sure the edges are updated correctly
|
* when the user programmatically changes the source or handle position, we need to update the internals
|
||||||
|
* to make sure the edges are updated correctly
|
||||||
|
*/
|
||||||
const typeChanged = prevType.current !== nodeType;
|
const typeChanged = prevType.current !== nodeType;
|
||||||
const sourcePosChanged = prevSourcePosition.current !== node.sourcePosition;
|
const sourcePosChanged = prevSourcePosition.current !== node.sourcePosition;
|
||||||
const targetPosChanged = prevTargetPosition.current !== node.targetPosition;
|
const targetPosChanged = prevTargetPosition.current !== node.targetPosition;
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ export const builtinNodeTypes: NodeTypes = {
|
|||||||
export function getNodeInlineStyleDimensions<NodeType extends Node = Node>(
|
export function getNodeInlineStyleDimensions<NodeType extends Node = Node>(
|
||||||
node: InternalNode<NodeType>
|
node: InternalNode<NodeType>
|
||||||
): {
|
): {
|
||||||
width: number | string | undefined;
|
width: number | string | undefined;
|
||||||
height: number | string | undefined;
|
height: number | string | undefined;
|
||||||
} {
|
} {
|
||||||
if (node.internals.handleBounds === undefined) {
|
if (node.internals.handleBounds === undefined) {
|
||||||
return {
|
return {
|
||||||
width: node.width ?? node.initialWidth ?? node.style?.width,
|
width: node.width ?? node.initialWidth ?? node.style?.width,
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import { errorMessages } from '@xyflow/system';
|
|||||||
|
|
||||||
import type { ReactFlowState } from '../../types';
|
import type { ReactFlowState } from '../../types';
|
||||||
|
|
||||||
// this handler is called by
|
/*
|
||||||
// 1. the click handler when node is not draggable or selectNodesOnDrag = false
|
* this handler is called by
|
||||||
// or
|
* 1. the click handler when node is not draggable or selectNodesOnDrag = false
|
||||||
// 2. the on drag start handler when node is draggable and selectNodesOnDrag = true
|
* or
|
||||||
|
* 2. the on drag start handler when node is draggable and selectNodesOnDrag = true
|
||||||
|
*/
|
||||||
export function handleNodeClick({
|
export function handleNodeClick({
|
||||||
id,
|
id,
|
||||||
store,
|
store,
|
||||||
|
|||||||
@@ -61,9 +61,9 @@ export function NodesSelection<NodeType extends Node>({
|
|||||||
|
|
||||||
const onContextMenu = onSelectionContextMenu
|
const onContextMenu = onSelectionContextMenu
|
||||||
? (event: MouseEvent) => {
|
? (event: MouseEvent) => {
|
||||||
const selectedNodes = store.getState().nodes.filter((n) => n.selected);
|
const selectedNodes = store.getState().nodes.filter((n) => n.selected);
|
||||||
onSelectionContextMenu(event, selectedNodes);
|
onSelectionContextMenu(event, selectedNodes);
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ import type { PanelPosition } from '@xyflow/system';
|
|||||||
import { useStore } from '../../hooks/useStore';
|
import { useStore } from '../../hooks/useStore';
|
||||||
import type { ReactFlowState } from '../../types';
|
import type { ReactFlowState } from '../../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expand
|
||||||
|
*/
|
||||||
export type PanelProps = HTMLAttributes<HTMLDivElement> & {
|
export type PanelProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
/** Set position of the panel
|
/**
|
||||||
* @example 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
|
* The position of the panel
|
||||||
*/
|
*/
|
||||||
position?: PanelPosition;
|
position?: PanelPosition;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -15,6 +18,31 @@ export type PanelProps = HTMLAttributes<HTMLDivElement> & {
|
|||||||
|
|
||||||
const selector = (s: ReactFlowState) => (s.userSelectionActive ? 'none' : 'all');
|
const selector = (s: ReactFlowState) => (s.userSelectionActive ? 'none' : 'all');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `<Panel />` component helps you position content above the viewport.
|
||||||
|
* It is used internally by the [`<MiniMap />`](/api-reference/components/minimap)
|
||||||
|
* and [`<Controls />`](/api-reference/components/controls) components.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```jsx
|
||||||
|
*import { ReactFlow, Background, Panel } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*export default function Flow() {
|
||||||
|
* return (
|
||||||
|
* <ReactFlow nodes={[]} fitView>
|
||||||
|
* <Panel position="top-left">top-left</Panel>
|
||||||
|
* <Panel position="top-center">top-center</Panel>
|
||||||
|
* <Panel position="top-right">top-right</Panel>
|
||||||
|
* <Panel position="bottom-left">bottom-left</Panel>
|
||||||
|
* <Panel position="bottom-center">bottom-center</Panel>
|
||||||
|
* <Panel position="bottom-right">bottom-right</Panel>
|
||||||
|
* </ReactFlow>
|
||||||
|
* );
|
||||||
|
*}
|
||||||
|
*```
|
||||||
|
*/
|
||||||
export const Panel = forwardRef<HTMLDivElement, PanelProps>(
|
export const Panel = forwardRef<HTMLDivElement, PanelProps>(
|
||||||
({ position = 'top-left', children, className, style, ...rest }, ref) => {
|
({ position = 'top-left', children, className, style, ...rest }, ref) => {
|
||||||
const pointerEvents = useStore(selector);
|
const pointerEvents = useStore(selector);
|
||||||
@@ -33,4 +61,4 @@ export const Panel = forwardRef<HTMLDivElement, PanelProps>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
Panel.displayName = 'Panel'
|
Panel.displayName = 'Panel';
|
||||||
|
|||||||
@@ -19,6 +19,40 @@ export type ReactFlowProviderProps = {
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `<ReactFlowProvider />` component is a [context provider](https://react.dev/learn/passing-data-deeply-with-context#)
|
||||||
|
* that makes it possible to access a flow's internal state outside of the
|
||||||
|
* [`<ReactFlow />`](/api-reference/react-flow) component. Many of the hooks we
|
||||||
|
* provide rely on this component to work.
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
*import { ReactFlow, ReactFlowProvider, useNodes } from '@xyflow/react'
|
||||||
|
*
|
||||||
|
*export default function Flow() {
|
||||||
|
* return (
|
||||||
|
* <ReactFlowProvider>
|
||||||
|
* <ReactFlow nodes={...} edges={...} />
|
||||||
|
* <Sidebar />
|
||||||
|
* </ReactFlowProvider>
|
||||||
|
* );
|
||||||
|
*}
|
||||||
|
*
|
||||||
|
*function Sidebar() {
|
||||||
|
* // This hook will only work if the component it's used in is a child of a
|
||||||
|
* // <ReactFlowProvider />.
|
||||||
|
* const nodes = useNodes()
|
||||||
|
*
|
||||||
|
* return <aside>do something with nodes</aside>;
|
||||||
|
*}
|
||||||
|
*```
|
||||||
|
*
|
||||||
|
* @remarks If you're using a router and want your flow's state to persist across routes,
|
||||||
|
* it's vital that you place the `<ReactFlowProvider />` component _outside_ of
|
||||||
|
* your router. If you have multiple flows on the same page you will need to use a separate
|
||||||
|
* `<ReactFlowProvider />` for each flow.
|
||||||
|
*/
|
||||||
export function ReactFlowProvider({
|
export function ReactFlowProvider({
|
||||||
initialNodes: nodes,
|
initialNodes: nodes,
|
||||||
initialEdges: edges,
|
initialEdges: edges,
|
||||||
|
|||||||
@@ -94,9 +94,11 @@ const selector = (s: ReactFlowState) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const initPrevValues = {
|
const initPrevValues = {
|
||||||
// these are values that are also passed directly to other components
|
/*
|
||||||
// than the StoreUpdater. We can reduce the number of setStore calls
|
* these are values that are also passed directly to other components
|
||||||
// by setting the same values here as prev fields.
|
* than the StoreUpdater. We can reduce the number of setStore calls
|
||||||
|
* by setting the same values here as prev fields.
|
||||||
|
*/
|
||||||
translateExtent: infiniteExtent,
|
translateExtent: infiniteExtent,
|
||||||
nodeOrigin: defaultNodeOrigin,
|
nodeOrigin: defaultNodeOrigin,
|
||||||
minZoom: 0.5,
|
minZoom: 0.5,
|
||||||
|
|||||||
@@ -6,6 +6,31 @@ import type { ReactFlowState } from '../../types';
|
|||||||
|
|
||||||
const selector = (s: ReactFlowState) => s.domNode?.querySelector('.react-flow__viewport-portal');
|
const selector = (s: ReactFlowState) => s.domNode?.querySelector('.react-flow__viewport-portal');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `<ViewportPortal />` component can be used to add components to the same viewport
|
||||||
|
* of the flow where nodes and edges are rendered. This is useful when you want to render
|
||||||
|
* your own components that are adhere to the same coordinate system as the nodes & edges
|
||||||
|
* and are also affected by zooming and panning
|
||||||
|
* @public
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```jsx
|
||||||
|
*import React from 'react';
|
||||||
|
*import { ViewportPortal } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*export default function () {
|
||||||
|
* return (
|
||||||
|
* <ViewportPortal>
|
||||||
|
* <div
|
||||||
|
* style={{ transform: 'translate(100px, 100px)', position: 'absolute' }}
|
||||||
|
* >
|
||||||
|
* This div is positioned at [100, 100] on the flow.
|
||||||
|
* </div>
|
||||||
|
* </ViewportPortal>
|
||||||
|
* );
|
||||||
|
*}
|
||||||
|
*```
|
||||||
|
*/
|
||||||
export function ViewportPortal({ children }: { children: ReactNode }) {
|
export function ViewportPortal({ children }: { children: ReactNode }) {
|
||||||
const viewPortalDiv = useStore(selector);
|
const viewPortalDiv = useStore(selector);
|
||||||
|
|
||||||
|
|||||||
@@ -42,9 +42,11 @@ const Marker = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// when you have multiple flows on a page and you hide the first one, the other ones have no markers anymore
|
/*
|
||||||
// when they do have markers with the same ids. To prevent this the user can pass a unique id to the react flow wrapper
|
* when you have multiple flows on a page and you hide the first one, the other ones have no markers anymore
|
||||||
// that we can then use for creating our unique marker ids
|
* when they do have markers with the same ids. To prevent this the user can pass a unique id to the react flow wrapper
|
||||||
|
* that we can then use for creating our unique marker ids
|
||||||
|
*/
|
||||||
const MarkerDefinitions = ({ defaultColor, rfId }: MarkerDefinitionsProps) => {
|
const MarkerDefinitions = ({ defaultColor, rfId }: MarkerDefinitionsProps) => {
|
||||||
const edges = useStore((s) => s.edges);
|
const edges = useStore((s) => s.edges);
|
||||||
const defaultEdgeOptions = useStore((s) => s.defaultEdgeOptions);
|
const defaultEdgeOptions = useStore((s) => s.defaultEdgeOptions);
|
||||||
|
|||||||
@@ -44,29 +44,31 @@ function NodeRendererComponent<NodeType extends Node>(props: NodeRendererProps<N
|
|||||||
<div className="react-flow__nodes" style={containerStyle}>
|
<div className="react-flow__nodes" style={containerStyle}>
|
||||||
{nodeIds.map((nodeId) => {
|
{nodeIds.map((nodeId) => {
|
||||||
return (
|
return (
|
||||||
// The split of responsibilities between NodeRenderer and
|
/*
|
||||||
// NodeComponentWrapper may appear weird. However, it’s designed to
|
* The split of responsibilities between NodeRenderer and
|
||||||
// minimize the cost of updates when individual nodes change.
|
* NodeComponentWrapper may appear weird. However, it’s designed to
|
||||||
//
|
* minimize the cost of updates when individual nodes change.
|
||||||
// For example, when you’re dragging a single node, that node gets
|
*
|
||||||
// updated multiple times per second. If `NodeRenderer` were to update
|
* For example, when you’re dragging a single node, that node gets
|
||||||
// every time, it would have to re-run the `nodes.map()` loop every
|
* updated multiple times per second. If `NodeRenderer` were to update
|
||||||
// time. This gets pricey with hundreds of nodes, especially if every
|
* every time, it would have to re-run the `nodes.map()` loop every
|
||||||
// loop cycle does more than just rendering a JSX element!
|
* time. This gets pricey with hundreds of nodes, especially if every
|
||||||
//
|
* loop cycle does more than just rendering a JSX element!
|
||||||
// As a result of this choice, we took the following implementation
|
*
|
||||||
// decisions:
|
* As a result of this choice, we took the following implementation
|
||||||
// - NodeRenderer subscribes *only* to node IDs – and therefore
|
* decisions:
|
||||||
// rerender *only* when visible nodes are added or removed.
|
* - NodeRenderer subscribes *only* to node IDs – and therefore
|
||||||
// - NodeRenderer performs all operations the result of which can be
|
* rerender *only* when visible nodes are added or removed.
|
||||||
// shared between nodes (such as creating the `ResizeObserver`
|
* - NodeRenderer performs all operations the result of which can be
|
||||||
// instance, or subscribing to `selector`). This means extra prop
|
* shared between nodes (such as creating the `ResizeObserver`
|
||||||
// drilling into `NodeComponentWrapper`, but it means we need to run
|
* instance, or subscribing to `selector`). This means extra prop
|
||||||
// these operations only once – instead of once per node.
|
* drilling into `NodeComponentWrapper`, but it means we need to run
|
||||||
// - Any operations that you’d normally write inside `nodes.map` are
|
* these operations only once – instead of once per node.
|
||||||
// moved into `NodeComponentWrapper`. This ensures they are
|
* - Any operations that you’d normally write inside `nodes.map` are
|
||||||
// memorized – so if `NodeRenderer` *has* to rerender, it only
|
* moved into `NodeComponentWrapper`. This ensures they are
|
||||||
// needs to regenerate the list of nodes, nothing else.
|
* memorized – so if `NodeRenderer` *has* to rerender, it only
|
||||||
|
* needs to regenerate the list of nodes, nothing else.
|
||||||
|
*/
|
||||||
<NodeWrapper<NodeType>
|
<NodeWrapper<NodeType>
|
||||||
key={nodeId}
|
key={nodeId}
|
||||||
id={nodeId}
|
id={nodeId}
|
||||||
|
|||||||
@@ -233,8 +233,10 @@ export function Pane({
|
|||||||
(event.target as Partial<Element>)?.releasePointerCapture?.(event.pointerId);
|
(event.target as Partial<Element>)?.releasePointerCapture?.(event.pointerId);
|
||||||
const { userSelectionRect } = store.getState();
|
const { userSelectionRect } = store.getState();
|
||||||
|
|
||||||
// We only want to trigger click functions when in selection mode if
|
/*
|
||||||
// the user did not move the mouse.
|
* We only want to trigger click functions when in selection mode if
|
||||||
|
* the user did not move the mouse.
|
||||||
|
*/
|
||||||
if (!userSelectionActive && userSelectionRect && event.target === container.current) {
|
if (!userSelectionActive && userSelectionRect && event.target === container.current) {
|
||||||
onClick?.(event);
|
onClick?.(event);
|
||||||
}
|
}
|
||||||
@@ -246,8 +248,10 @@ export function Pane({
|
|||||||
});
|
});
|
||||||
onSelectionEnd?.(event);
|
onSelectionEnd?.(event);
|
||||||
|
|
||||||
// If the user kept holding the selectionKey during the selection,
|
/*
|
||||||
// we need to reset the selectionInProgress, so the next click event is not prevented
|
* If the user kept holding the selectionKey during the selection,
|
||||||
|
* we need to reset the selectionInProgress, so the next click event is not prevented
|
||||||
|
*/
|
||||||
if (selectionKeyPressed || selectionOnDrag) {
|
if (selectionKeyPressed || selectionOnDrag) {
|
||||||
selectionInProgress.current = false;
|
selectionInProgress.current = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,10 @@ export function Wrapper({
|
|||||||
const isWrapped = useContext(StoreContext);
|
const isWrapped = useContext(StoreContext);
|
||||||
|
|
||||||
if (isWrapped) {
|
if (isWrapped) {
|
||||||
// we need to wrap it with a fragment because it's not allowed for children to be a ReactNode
|
/*
|
||||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051
|
* we need to wrap it with a fragment because it's not allowed for children to be a ReactNode
|
||||||
|
* https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18051
|
||||||
|
*/
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -312,4 +312,24 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `<ReactFlow />` component is the heart of your React Flow application.
|
||||||
|
* It renders your nodes and edges and handles user interaction
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
*import { ReactFlow } from '@xyflow/react'
|
||||||
|
*
|
||||||
|
*export default function Flow() {
|
||||||
|
* return (<ReactFlow
|
||||||
|
* nodes={...}
|
||||||
|
* edges={...}
|
||||||
|
* onNodesChange={...}
|
||||||
|
* ...
|
||||||
|
* />);
|
||||||
|
*}
|
||||||
|
*```
|
||||||
|
*/
|
||||||
export default fixedForwardRef(ReactFlow);
|
export default fixedForwardRef(ReactFlow);
|
||||||
|
|||||||
@@ -4,6 +4,34 @@ export const NodeIdContext = createContext<string | null>(null);
|
|||||||
export const Provider = NodeIdContext.Provider;
|
export const Provider = NodeIdContext.Provider;
|
||||||
export const Consumer = NodeIdContext.Consumer;
|
export const Consumer = NodeIdContext.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You can use this hook to get the id of the node it is used inside. It is useful
|
||||||
|
* if you need the node's id deeper in the render tree but don't want to manually
|
||||||
|
* drill down the id as a prop.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @returns id of the node
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*```jsx
|
||||||
|
*import { useNodeId } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*export default function CustomNode() {
|
||||||
|
* return (
|
||||||
|
* <div>
|
||||||
|
* <span>This node has an id of </span>
|
||||||
|
* <NodeIdDisplay />
|
||||||
|
* </div>
|
||||||
|
* );
|
||||||
|
*}
|
||||||
|
*
|
||||||
|
*function NodeIdDisplay() {
|
||||||
|
* const nodeId = useNodeId();
|
||||||
|
*
|
||||||
|
* return <span>{nodeId}</span>;
|
||||||
|
*}
|
||||||
|
*```
|
||||||
|
*/
|
||||||
export const useNodeId = (): string | null => {
|
export const useNodeId = (): string | null => {
|
||||||
const nodeId = useContext(NodeIdContext);
|
const nodeId = useContext(NodeIdContext);
|
||||||
return nodeId;
|
return nodeId;
|
||||||
|
|||||||
@@ -24,9 +24,28 @@ function getSelector<NodeType extends Node = Node, SelectorReturn = ConnectionSt
|
|||||||
return storeSelector;
|
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
|
* @public
|
||||||
|
* @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
|
* @returns ConnectionState
|
||||||
*/
|
*/
|
||||||
export function useConnection<NodeType extends Node = Node, SelectorReturn = ConnectionState<InternalNode<NodeType>>>(
|
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;
|
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
|
* @public
|
||||||
* @returns An array of edges
|
* @returns An array of edges
|
||||||
|
*
|
||||||
|
* @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[] {
|
export function useEdges<EdgeType extends Edge = Edge>(): EdgeType[] {
|
||||||
const edges = useStore(edgesSelector, shallow) as EdgeType[];
|
const edges = useStore(edgesSelector, shallow) as EdgeType[];
|
||||||
|
|||||||
@@ -5,11 +5,31 @@ import { useStore } from './useStore';
|
|||||||
import type { InternalNode, Node } from '../types';
|
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
|
* @public
|
||||||
* @param id - id of the node
|
* @param id - id of the node
|
||||||
* @returns array with visible node ids
|
* @returns array with visible node ids
|
||||||
|
*
|
||||||
|
* @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 {
|
export function useInternalNode<NodeType extends Node = Node>(id: string): InternalNode<NodeType> | undefined {
|
||||||
const node = useStore(
|
const node = useStore(
|
||||||
|
|||||||
@@ -13,18 +13,38 @@ export type UseKeyPressOptions = {
|
|||||||
const defaultDoc = typeof document !== 'undefined' ? document : null;
|
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
|
* @public
|
||||||
* @param param.keyCode - The key code (string or array of strings) to use
|
* @param param.keyCode - The key code (string or array of strings) to use
|
||||||
* @param param.options - Options
|
* @param param.options - Options
|
||||||
* @returns boolean
|
* @returns boolean
|
||||||
|
*
|
||||||
|
* @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(
|
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'
|
* the keycode can be a string 'a' or an array of strings ['a', 'a+d']
|
||||||
// an array means different possibilites. Explainer: ['a', 'd+s'] here the
|
* a string means a single key 'a' or a combination when '+' is used 'a+d'
|
||||||
// user can use the single key 'a' or the combination 'd' + 's'
|
* 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 {
|
||||||
@@ -36,20 +56,24 @@ export function useKeyPress(
|
|||||||
// we need to remember the pressed keys in order to support combinations
|
// we need to remember the pressed keys in order to support combinations
|
||||||
const pressedKeys = useRef<PressedKeys>(new Set([]));
|
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']
|
* keyCodes = array with single keys [['a']] or key combinations [['a', 's']]
|
||||||
// used to check if we store event.code or event.key. When the code is in the list of keysToWatch
|
* keysToWatch = array with all keys flattened ['a', 'd', 'ShiftLeft']
|
||||||
// we use the code otherwise the key. Explainer: When you press the left "command" key, the code is "MetaLeft"
|
* used to check if we store event.code or event.key. When the code is in the list of keysToWatch
|
||||||
// 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 use the code otherwise the key. Explainer: When you press the left "command" key, the code is "MetaLeft"
|
||||||
// we can't find it in the list of keysToWatch.
|
* 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]>(() => {
|
const [keyCodes, keysToWatch] = useMemo<[Array<Keys>, Keys]>(() => {
|
||||||
if (keyCode !== null) {
|
if (keyCode !== null) {
|
||||||
const keyCodeArr = Array.isArray(keyCode) ? keyCode : [keyCode];
|
const keyCodeArr = Array.isArray(keyCode) ? keyCode : [keyCode];
|
||||||
const keys = keyCodeArr
|
const keys = keyCodeArr
|
||||||
.filter((kc) => typeof kc === 'string')
|
.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++'
|
* we first replace all '+' with '\n' which we will use to split the keys on
|
||||||
// in the end we simply split on '\n' to get the key array
|
* 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'));
|
.map((kc) => kc.replace('+', '\n').replace('\n\n', '\n+').split('\n'));
|
||||||
const keysFlat = keys.reduce((res: Keys, item) => res.concat(...item), []);
|
const keysFlat = keys.reduce((res: Keys, item) => res.concat(...item), []);
|
||||||
|
|
||||||
@@ -133,12 +157,16 @@ export function useKeyPress(
|
|||||||
function isMatchingKey(keyCodes: Array<Keys>, pressedKeys: PressedKeys, isUp: boolean): boolean {
|
function isMatchingKey(keyCodes: Array<Keys>, pressedKeys: PressedKeys, isUp: boolean): boolean {
|
||||||
return (
|
return (
|
||||||
keyCodes
|
keyCodes
|
||||||
// we only want to compare same sizes of keyCode definitions
|
/*
|
||||||
// and pressed keys. When the user specified 'Meta' as a key somewhere
|
* we only want to compare same sizes of keyCode definitions
|
||||||
// this would also be truthy without this filter when user presses 'Meta' + 'r'
|
* 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)
|
.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)))
|
.some((keys) => keys.every((k) => pressedKeys.has(k)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ export function useMoveSelectedNodes() {
|
|||||||
const nodeUpdates = new Map();
|
const nodeUpdates = new Map();
|
||||||
const isSelected = selectedAndDraggable(nodesDraggable);
|
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 xVelo = snapToGrid ? snapGrid[0] : 5;
|
||||||
const yVelo = snapToGrid ? snapGrid[1] : 5;
|
const yVelo = snapToGrid ? snapGrid[1] : 5;
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ type UseNodeConnectionsParams = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
* @public
|
||||||
* @param param.id - node id - optional if called inside a custom node
|
* @param param.id - node id - optional if called inside a custom node
|
||||||
@@ -31,6 +31,22 @@ type UseNodeConnectionsParams = {
|
|||||||
* @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
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```jsx
|
||||||
|
*import { useNodeConnections } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*export default function () {
|
||||||
|
* const connections = useNodeConnections({
|
||||||
|
* type: 'target',
|
||||||
|
* handleId: 'my-handle',
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* return (
|
||||||
|
* <div>There are currently {connections.length} incoming connections!</div>
|
||||||
|
* );
|
||||||
|
*}
|
||||||
|
*```
|
||||||
*/
|
*/
|
||||||
export function useNodeConnections({
|
export function useNodeConnections({
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -6,10 +6,23 @@ import type { Node, ReactFlowState } from '../types';
|
|||||||
const nodesSelector = (state: ReactFlowState) => state.nodes;
|
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
|
* @public
|
||||||
* @returns An array of nodes
|
* @returns An array of nodes
|
||||||
|
*
|
||||||
|
* @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[] {
|
export function useNodes<NodeType extends Node = Node>(): NodeType[] {
|
||||||
const nodes = useStore(nodesSelector, shallow) as NodeType[];
|
const nodes = useStore(nodesSelector, shallow) as NodeType[];
|
||||||
|
|||||||
@@ -5,12 +5,24 @@ import { useStore } from '../hooks/useStore';
|
|||||||
import type { Node } from '../types';
|
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
|
* @public
|
||||||
* @param nodeId - The id (or ids) of the node to get the data from
|
* @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>(
|
export function useNodesData<NodeType extends Node = Node>(
|
||||||
nodeId: string
|
nodeId: string
|
||||||
|
|||||||
@@ -4,11 +4,41 @@ import { applyNodeChanges, applyEdgeChanges } from '../utils/changes';
|
|||||||
import type { Node, Edge, OnNodesChange, OnEdgesChange } from '../types';
|
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
|
* @public
|
||||||
* @param initialNodes
|
* @param initialNodes
|
||||||
* @returns an array [nodes, setNodes, onNodesChange]
|
* @returns an array [nodes, setNodes, onNodesChange]
|
||||||
|
* @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>(
|
export function useNodesState<NodeType extends Node>(
|
||||||
initialNodes: NodeType[]
|
initialNodes: NodeType[]
|
||||||
@@ -23,11 +53,41 @@ 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
|
* @public
|
||||||
* @param initialEdges
|
* @param initialEdges
|
||||||
* @returns an array [edges, setEdges, onEdgesChange]
|
* @returns an array [edges, setEdges, onEdgesChange]
|
||||||
|
* @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>(
|
export function useEdgesState<EdgeType extends Edge = Edge>(
|
||||||
initialEdges: EdgeType[]
|
initialEdges: EdgeType[]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import { nodeHasDimensions } from '@xyflow/system';
|
||||||
|
|
||||||
import { useStore } from './useStore';
|
import { useStore } from './useStore';
|
||||||
import type { ReactFlowState } from '../types';
|
import type { ReactFlowState } from '../types';
|
||||||
import { nodeHasDimensions } from '@xyflow/system';
|
|
||||||
|
|
||||||
export type UseNodesInitializedOptions = {
|
export type UseNodesInitializedOptions = {
|
||||||
includeHiddenNodes?: boolean;
|
includeHiddenNodes?: boolean;
|
||||||
@@ -22,18 +23,44 @@ const selector = (options: UseNodesInitializedOptions) => (s: ReactFlowState) =>
|
|||||||
return true;
|
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
|
* @public
|
||||||
* @param options.includeHiddenNodes - defaults to false
|
* @param options.includeHiddenNodes - defaults to false
|
||||||
* @returns boolean indicating whether all nodes are initialized
|
* @returns boolean indicating whether all nodes are initialized
|
||||||
|
*
|
||||||
|
* @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));
|
const initialized = useStore(selector(options));
|
||||||
|
|
||||||
return initialized;
|
return initialized;
|
||||||
|
|||||||
@@ -8,10 +8,42 @@ export type UseOnSelectionChangeOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
* @public
|
||||||
* @param params.onChange - The handler to register
|
* @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) {
|
export function useOnSelectionChange({ onChange }: UseOnSelectionChangeOptions) {
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
|
|||||||
@@ -10,12 +10,30 @@ export type UseOnViewportChangeOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
* @public
|
||||||
* @param params.onStart - gets called when the viewport starts changing
|
* @param params.onStart - gets called when the viewport starts changing
|
||||||
* @param params.onChange - gets called when the viewport changes
|
* @param params.onChange - gets called when the viewport changes
|
||||||
* @param params.onEnd - gets called when the viewport stops changing
|
* @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) {
|
export function useOnViewportChange({ onStart, onChange, onEnd }: UseOnViewportChangeOptions) {
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
|
|||||||
@@ -20,10 +20,33 @@ import type { ReactFlowInstance, Node, Edge, InternalNode, ReactFlowState, Gener
|
|||||||
const selector = (s: ReactFlowState) => !!s.panZoom;
|
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
|
* @public
|
||||||
* @returns ReactFlowInstance
|
* @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<
|
export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(): ReactFlowInstance<
|
||||||
NodeType,
|
NodeType,
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import type { Edge, Node, ReactFlowState } from '../types';
|
|||||||
const zustandErrorMessage = errorMessages['error001']();
|
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
|
* @public
|
||||||
* @param selector
|
* @param selector
|
||||||
@@ -17,8 +19,13 @@ const zustandErrorMessage = errorMessages['error001']();
|
|||||||
* @returns The selected state slice
|
* @returns The selected state slice
|
||||||
*
|
*
|
||||||
* @example
|
* @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>(
|
function useStore<StateSlice = unknown>(
|
||||||
selector: (state: ReactFlowState) => StateSlice,
|
selector: (state: ReactFlowState) => StateSlice,
|
||||||
@@ -33,6 +40,20 @@ function useStore<StateSlice = unknown>(
|
|||||||
return useZustandStore(store, selector, equalityFn);
|
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>() {
|
function useStoreApi<NodeType extends Node = Node, EdgeType extends Edge = Edge>() {
|
||||||
const store = useContext(StoreContext) as UseBoundStoreWithEqualityFn<
|
const store = useContext(StoreContext) as UseBoundStoreWithEqualityFn<
|
||||||
StoreApi<ReactFlowState<NodeType, EdgeType>>
|
StoreApi<ReactFlowState<NodeType, EdgeType>>
|
||||||
|
|||||||
@@ -4,10 +4,48 @@ import type { UpdateNodeInternals, InternalNodeUpdate } from '@xyflow/system';
|
|||||||
import { useStoreApi } from '../hooks/useStore';
|
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
|
* @public
|
||||||
* @returns function for updating node internals
|
* @returns function for updating node internals
|
||||||
|
*
|
||||||
|
* @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 {
|
export function useUpdateNodeInternals(): UpdateNodeInternals {
|
||||||
const store = useStoreApi();
|
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
|
* @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 {
|
export function useViewport(): Viewport {
|
||||||
const viewport = useStore(viewportSelector, shallow);
|
const viewport = useStore(viewportSelector, shallow);
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import type { Node, ReactFlowState } from '../types';
|
|||||||
const selector = (onlyRenderVisible: boolean) => (s: ReactFlowState) => {
|
const selector = (onlyRenderVisible: boolean) => (s: ReactFlowState) => {
|
||||||
return onlyRenderVisible
|
return onlyRenderVisible
|
||||||
? getNodesInside<Node>(s.nodeLookup, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true).map(
|
? 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());
|
: Array.from(s.nodeLookup.keys());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -47,12 +47,14 @@ const createStore = ({
|
|||||||
...getInitialState({ nodes, edges, width, height, fitView, nodeOrigin, nodeExtent, defaultNodes, defaultEdges }),
|
...getInitialState({ nodes, edges, width, height, fitView, nodeOrigin, nodeExtent, defaultNodes, defaultEdges }),
|
||||||
setNodes: (nodes: Node[]) => {
|
setNodes: (nodes: Node[]) => {
|
||||||
const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect } = get();
|
const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect } = get();
|
||||||
// setNodes() is called exclusively in response to user actions:
|
/*
|
||||||
// - either when the `<ReactFlow nodes>` prop is updated in the controlled ReactFlow setup,
|
* setNodes() is called exclusively in response to user actions:
|
||||||
// - or when the user calls something like `reactFlowInstance.setNodes()` in an uncontrolled ReactFlow setup.
|
* - either when the `<ReactFlow nodes>` prop is updated in the controlled ReactFlow setup,
|
||||||
//
|
* - or when the user calls something like `reactFlowInstance.setNodes()` in an uncontrolled ReactFlow setup.
|
||||||
// When this happens, we take the note objects passed by the user and extend them with fields
|
*
|
||||||
// relevant for internal React Flow operations.
|
* When this happens, we take the note objects passed by the user and extend them with fields
|
||||||
|
* relevant for internal React Flow operations.
|
||||||
|
*/
|
||||||
adoptUserNodes(nodes, nodeLookup, parentLookup, {
|
adoptUserNodes(nodes, nodeLookup, parentLookup, {
|
||||||
nodeOrigin,
|
nodeOrigin,
|
||||||
nodeExtent,
|
nodeExtent,
|
||||||
@@ -81,9 +83,11 @@ const createStore = ({
|
|||||||
set({ hasDefaultEdges: true });
|
set({ hasDefaultEdges: true });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Every node gets registerd at a ResizeObserver. Whenever a node
|
/*
|
||||||
// changes its dimensions, this function is called to measure the
|
* Every node gets registerd at a ResizeObserver. Whenever a node
|
||||||
// new dimensions and update the nodes.
|
* changes its dimensions, this function is called to measure the
|
||||||
|
* new dimensions and update the nodes.
|
||||||
|
*/
|
||||||
updateNodeInternals: (updates, params = { triggerFitView: true }) => {
|
updateNodeInternals: (updates, params = { triggerFitView: true }) => {
|
||||||
const {
|
const {
|
||||||
triggerNodeChanges,
|
triggerNodeChanges,
|
||||||
@@ -125,11 +129,13 @@ const createStore = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// here we are cirmumventing the onNodesChange handler
|
/*
|
||||||
// in order to be able to display nodes even if the user
|
* here we are cirmumventing the onNodesChange handler
|
||||||
// has not provided an onNodesChange handler.
|
* in order to be able to display nodes even if the user
|
||||||
// Nodes are only rendered if they have a width and height
|
* has not provided an onNodesChange handler.
|
||||||
// attribute which they get from this handler.
|
* Nodes are only rendered if they have a width and height
|
||||||
|
* attribute which they get from this handler.
|
||||||
|
*/
|
||||||
set({ fitViewDone: nextFitViewDone });
|
set({ fitViewDone: nextFitViewDone });
|
||||||
} else {
|
} else {
|
||||||
// we always want to trigger useStore calls whenever updateNodeInternals is called
|
// we always want to trigger useStore calls whenever updateNodeInternals is called
|
||||||
@@ -155,9 +161,9 @@ const createStore = ({
|
|||||||
type: 'position',
|
type: 'position',
|
||||||
position: expandParent
|
position: expandParent
|
||||||
? {
|
? {
|
||||||
x: Math.max(0, dragItem.position.x),
|
x: Math.max(0, dragItem.position.x),
|
||||||
y: Math.max(0, dragItem.position.y),
|
y: Math.max(0, dragItem.position.y),
|
||||||
}
|
}
|
||||||
: dragItem.position,
|
: dragItem.position,
|
||||||
dragging,
|
dragging,
|
||||||
};
|
};
|
||||||
@@ -248,8 +254,10 @@ const createStore = ({
|
|||||||
const nodeChanges = nodesToUnselect.map((n) => {
|
const nodeChanges = nodesToUnselect.map((n) => {
|
||||||
const internalNode = nodeLookup.get(n.id);
|
const internalNode = nodeLookup.get(n.id);
|
||||||
if (internalNode) {
|
if (internalNode) {
|
||||||
// we need to unselect the internal node that was selected previously before we
|
/*
|
||||||
// send the change to the user to prevent it to be selected while dragging the new node
|
* we need to unselect the internal node that was selected previously before we
|
||||||
|
* send the change to the user to prevent it to be selected while dragging the new node
|
||||||
|
*/
|
||||||
internalNode.selected = false;
|
internalNode.selected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,8 +350,10 @@ const createStore = ({
|
|||||||
options
|
options
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
// we can't call an asnychronous function in updateNodeInternals
|
/*
|
||||||
// for that we created this sync version of fitView
|
* we can't call an asnychronous function in updateNodeInternals
|
||||||
|
* for that we created this sync version of fitView
|
||||||
|
*/
|
||||||
fitViewSync: (options?: FitViewOptions): boolean => {
|
fitViewSync: (options?: FitViewOptions): boolean => {
|
||||||
const { panZoom, width, height, minZoom, maxZoom, nodeLookup } = get();
|
const { panZoom, width, height, minZoom, maxZoom, nodeLookup } = get();
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,8 @@ import type {
|
|||||||
*/
|
*/
|
||||||
export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends Edge = Edge>
|
export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends Edge = Edge>
|
||||||
extends Omit<HTMLAttributes<HTMLDivElement>, 'onError'> {
|
extends Omit<HTMLAttributes<HTMLDivElement>, 'onError'> {
|
||||||
/** An array of nodes to render in a controlled flow.
|
/**
|
||||||
|
* An array of nodes to render in a controlled flow.
|
||||||
* @example
|
* @example
|
||||||
* const nodes = [
|
* const nodes = [
|
||||||
* {
|
* {
|
||||||
@@ -64,7 +65,8 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
|||||||
* ];
|
* ];
|
||||||
*/
|
*/
|
||||||
nodes?: NodeType[];
|
nodes?: NodeType[];
|
||||||
/** An array of edges to render in a controlled flow.
|
/**
|
||||||
|
* An array of edges to render in a controlled flow.
|
||||||
* @example
|
* @example
|
||||||
* const edges = [
|
* const edges = [
|
||||||
* {
|
* {
|
||||||
@@ -79,7 +81,8 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
|||||||
defaultNodes?: NodeType[];
|
defaultNodes?: NodeType[];
|
||||||
/** The initial edges to render in an uncontrolled flow. */
|
/** The initial edges to render in an uncontrolled flow. */
|
||||||
defaultEdges?: EdgeType[];
|
defaultEdges?: EdgeType[];
|
||||||
/** Defaults to be applied to all new edges that are added to the flow.
|
/**
|
||||||
|
* Defaults to be applied to all new edges that are added to the flow.
|
||||||
*
|
*
|
||||||
* Properties on a new edge will override these defaults if they exist.
|
* Properties on a new edge will override these defaults if they exist.
|
||||||
* @example
|
* @example
|
||||||
@@ -132,7 +135,8 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
|||||||
onReconnect?: OnReconnect<EdgeType>;
|
onReconnect?: OnReconnect<EdgeType>;
|
||||||
onReconnectStart?: (event: ReactMouseEvent, edge: EdgeType, handleType: HandleType) => void;
|
onReconnectStart?: (event: ReactMouseEvent, edge: EdgeType, handleType: HandleType) => void;
|
||||||
onReconnectEnd?: (event: MouseEvent | TouchEvent, edge: EdgeType, handleType: HandleType) => void;
|
onReconnectEnd?: (event: MouseEvent | TouchEvent, edge: EdgeType, handleType: HandleType) => void;
|
||||||
/** This event handler is called when a Node is updated
|
/**
|
||||||
|
* This event handler is called when a Node is updated
|
||||||
* @example // Use NodesState hook to create edges and get onNodesChange handler
|
* @example // Use NodesState hook to create edges and get onNodesChange handler
|
||||||
* import ReactFlow, { useNodesState } from '@xyflow/react';
|
* import ReactFlow, { useNodesState } from '@xyflow/react';
|
||||||
* const [edges, setNodes, onNodesChange] = useNodesState(initialNodes);
|
* const [edges, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||||
@@ -149,7 +153,8 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
|||||||
* return (<ReactFlow onNodeChange={onNodeChange} {...rest} />)
|
* return (<ReactFlow onNodeChange={onNodeChange} {...rest} />)
|
||||||
*/
|
*/
|
||||||
onNodesChange?: OnNodesChange<NodeType>;
|
onNodesChange?: OnNodesChange<NodeType>;
|
||||||
/** This event handler is called when a Edge is updated
|
/**
|
||||||
|
* This event handler is called when a Edge is updated
|
||||||
* @example // Use EdgesState hook to create edges and get onEdgesChange handler
|
* @example // Use EdgesState hook to create edges and get onEdgesChange handler
|
||||||
* import ReactFlow, { useEdgesState } from '@xyflow/react';
|
* import ReactFlow, { useEdgesState } from '@xyflow/react';
|
||||||
* const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
* const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||||
@@ -181,7 +186,8 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
|||||||
onSelectionStart?: (event: ReactMouseEvent) => void;
|
onSelectionStart?: (event: ReactMouseEvent) => void;
|
||||||
onSelectionEnd?: (event: ReactMouseEvent) => void;
|
onSelectionEnd?: (event: ReactMouseEvent) => void;
|
||||||
onSelectionContextMenu?: (event: ReactMouseEvent, nodes: NodeType[]) => void;
|
onSelectionContextMenu?: (event: ReactMouseEvent, nodes: NodeType[]) => void;
|
||||||
/** When a connection line is completed and two nodes are connected by the user, this event fires with the new connection.
|
/**
|
||||||
|
* When a connection line is completed and two nodes are connected by the user, this event fires with the new connection.
|
||||||
*
|
*
|
||||||
* You can use the addEdge utility to convert the connection to a complete edge.
|
* You can use the addEdge utility to convert the connection to a complete edge.
|
||||||
* @example // Use helper function to update edges onConnect
|
* @example // Use helper function to update edges onConnect
|
||||||
@@ -223,17 +229,20 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
|||||||
onPaneMouseMove?: (event: ReactMouseEvent) => void;
|
onPaneMouseMove?: (event: ReactMouseEvent) => void;
|
||||||
/** This event handler gets called when mouse leaves the pane */
|
/** This event handler gets called when mouse leaves the pane */
|
||||||
onPaneMouseLeave?: (event: ReactMouseEvent) => void;
|
onPaneMouseLeave?: (event: ReactMouseEvent) => void;
|
||||||
/** Distance that the mouse can move between mousedown/up that will trigger a click
|
/**
|
||||||
|
* Distance that the mouse can move between mousedown/up that will trigger a click
|
||||||
* @default 0
|
* @default 0
|
||||||
*/
|
*/
|
||||||
paneClickDistance?: number;
|
paneClickDistance?: number;
|
||||||
/** Distance that the mouse can move between mousedown/up that will trigger a click
|
/**
|
||||||
|
* Distance that the mouse can move between mousedown/up that will trigger a click
|
||||||
* @default 0
|
* @default 0
|
||||||
*/
|
*/
|
||||||
nodeClickDistance?: number;
|
nodeClickDistance?: number;
|
||||||
/** This handler gets called before the user deletes nodes or edges and provides a way to abort the deletion by returning false. */
|
/** This handler gets called before the user deletes nodes or edges and provides a way to abort the deletion by returning false. */
|
||||||
onBeforeDelete?: OnBeforeDelete<NodeType, EdgeType>;
|
onBeforeDelete?: OnBeforeDelete<NodeType, EdgeType>;
|
||||||
/** Custom node types to be available in a flow.
|
/**
|
||||||
|
* Custom node types to be available in a flow.
|
||||||
*
|
*
|
||||||
* React Flow matches a node's type to a component in the nodeTypes object.
|
* React Flow matches a node's type to a component in the nodeTypes object.
|
||||||
* @example
|
* @example
|
||||||
@@ -242,7 +251,8 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
|||||||
* const nodeTypes = { nameOfNodeType: CustomNode };
|
* const nodeTypes = { nameOfNodeType: CustomNode };
|
||||||
*/
|
*/
|
||||||
nodeTypes?: NodeTypes;
|
nodeTypes?: NodeTypes;
|
||||||
/** Custom edge types to be available in a flow.
|
/**
|
||||||
|
* Custom edge types to be available in a flow.
|
||||||
*
|
*
|
||||||
* React Flow matches an edge's type to a component in the edgeTypes object.
|
* React Flow matches an edge's type to a component in the edgeTypes object.
|
||||||
* @example
|
* @example
|
||||||
@@ -251,7 +261,8 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
|||||||
* const edgeTypes = { nameOfEdgeType: CustomEdge };
|
* const edgeTypes = { nameOfEdgeType: CustomEdge };
|
||||||
*/
|
*/
|
||||||
edgeTypes?: EdgeTypes;
|
edgeTypes?: EdgeTypes;
|
||||||
/** The type of edge path to use for connection lines.
|
/**
|
||||||
|
* The type of edge path to use for connection lines.
|
||||||
*
|
*
|
||||||
* Although created edges can be of any type, React Flow needs to know what type of path to render for the connection line before the edge is created!
|
* Although created edges can be of any type, React Flow needs to know what type of path to render for the connection line before the edge is created!
|
||||||
*/
|
*/
|
||||||
@@ -262,17 +273,20 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
|||||||
connectionLineComponent?: ConnectionLineComponent<NodeType>;
|
connectionLineComponent?: ConnectionLineComponent<NodeType>;
|
||||||
/** Styles to be applied to the container of the connection line */
|
/** Styles to be applied to the container of the connection line */
|
||||||
connectionLineContainerStyle?: CSSProperties;
|
connectionLineContainerStyle?: CSSProperties;
|
||||||
/** 'strict' connection mode will only allow you to connect source handles to target handles.
|
/**
|
||||||
|
* 'strict' connection mode will only allow you to connect source handles to target handles.
|
||||||
*
|
*
|
||||||
* 'loose' connection mode will allow you to connect handles of any type to one another.
|
* 'loose' connection mode will allow you to connect handles of any type to one another.
|
||||||
* @default 'strict'
|
* @default 'strict'
|
||||||
*/
|
*/
|
||||||
connectionMode?: ConnectionMode;
|
connectionMode?: ConnectionMode;
|
||||||
/** Pressing down this key deletes all selected nodes & edges.
|
/**
|
||||||
|
* Pressing down this key deletes all selected nodes & edges.
|
||||||
* @default 'Backspace'
|
* @default 'Backspace'
|
||||||
*/
|
*/
|
||||||
deleteKeyCode?: KeyCode | null;
|
deleteKeyCode?: KeyCode | null;
|
||||||
/** If a key is set, you can pan the viewport while that key is held down even if panOnScroll is set to false.
|
/**
|
||||||
|
* If a key is set, you can pan the viewport while that key is held down even if panOnScroll is set to false.
|
||||||
*
|
*
|
||||||
* By setting this prop to null you can disable this functionality.
|
* By setting this prop to null you can disable this functionality.
|
||||||
* @default 'Space'
|
* @default 'Space'
|
||||||
@@ -280,91 +294,110 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
|||||||
selectionKeyCode?: KeyCode | null;
|
selectionKeyCode?: KeyCode | null;
|
||||||
/** Select multiple elements with a selection box, without pressing down selectionKey */
|
/** Select multiple elements with a selection box, without pressing down selectionKey */
|
||||||
selectionOnDrag?: boolean;
|
selectionOnDrag?: boolean;
|
||||||
/** When set to "partial", when the user creates a selection box by click and dragging nodes that are only partially in the box are still selected.
|
/**
|
||||||
|
* When set to "partial", when the user creates a selection box by click and dragging nodes that are only partially in the box are still selected.
|
||||||
* @default 'full'
|
* @default 'full'
|
||||||
*/
|
*/
|
||||||
selectionMode?: SelectionMode;
|
selectionMode?: SelectionMode;
|
||||||
/** If a key is set, you can pan the viewport while that key is held down even if panOnScroll is set to false.
|
/**
|
||||||
|
* If a key is set, you can pan the viewport while that key is held down even if panOnScroll is set to false.
|
||||||
*
|
*
|
||||||
* By setting this prop to null you can disable this functionality.
|
* By setting this prop to null you can disable this functionality.
|
||||||
* @default 'Space'
|
* @default 'Space'
|
||||||
*/
|
*/
|
||||||
panActivationKeyCode?: KeyCode | null;
|
panActivationKeyCode?: KeyCode | null;
|
||||||
/** Pressing down this key you can select multiple elements by clicking.
|
/**
|
||||||
|
* Pressing down this key you can select multiple elements by clicking.
|
||||||
* @default 'Meta' for macOS, "Ctrl" for other systems
|
* @default 'Meta' for macOS, "Ctrl" for other systems
|
||||||
*/
|
*/
|
||||||
multiSelectionKeyCode?: KeyCode | null;
|
multiSelectionKeyCode?: KeyCode | null;
|
||||||
/** If a key is set, you can zoom the viewport while that key is held down even if panOnScroll is set to false.
|
/**
|
||||||
|
* If a key is set, you can zoom the viewport while that key is held down even if panOnScroll is set to false.
|
||||||
*
|
*
|
||||||
* By setting this prop to null you can disable this functionality.
|
* By setting this prop to null you can disable this functionality.
|
||||||
* @default 'Meta' for macOS, "Ctrl" for other systems
|
* @default 'Meta' for macOS, "Ctrl" for other systems
|
||||||
* */
|
*
|
||||||
|
*/
|
||||||
zoomActivationKeyCode?: KeyCode | null;
|
zoomActivationKeyCode?: KeyCode | null;
|
||||||
/** Set this prop to make the flow snap to the grid */
|
/** Set this prop to make the flow snap to the grid */
|
||||||
snapToGrid?: boolean;
|
snapToGrid?: boolean;
|
||||||
/** Grid all nodes will snap to
|
/**
|
||||||
|
* Grid all nodes will snap to
|
||||||
* @example [20, 20]
|
* @example [20, 20]
|
||||||
*/
|
*/
|
||||||
snapGrid?: SnapGrid;
|
snapGrid?: SnapGrid;
|
||||||
/** You can enable this optimisation to instruct Svelte Flow to only render nodes and edges that would be visible in the viewport.
|
/**
|
||||||
|
* You can enable this optimisation to instruct Svelte Flow to only render nodes and edges that would be visible in the viewport.
|
||||||
*
|
*
|
||||||
* This might improve performance when you have a large number of nodes and edges but also adds an overhead.
|
* This might improve performance when you have a large number of nodes and edges but also adds an overhead.
|
||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
onlyRenderVisibleElements?: boolean;
|
onlyRenderVisibleElements?: boolean;
|
||||||
/** Controls if all nodes should be draggable
|
/**
|
||||||
|
* Controls if all nodes should be draggable
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
nodesDraggable?: boolean;
|
nodesDraggable?: boolean;
|
||||||
/** Controls if all nodes should be connectable to each other
|
/**
|
||||||
|
* Controls if all nodes should be connectable to each other
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
nodesConnectable?: boolean;
|
nodesConnectable?: boolean;
|
||||||
/** Controls if all nodes should be focusable
|
/**
|
||||||
|
* Controls if all nodes should be focusable
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
nodesFocusable?: boolean;
|
nodesFocusable?: boolean;
|
||||||
/** Defines nodes relative position to its coordinates
|
/**
|
||||||
|
* Defines nodes relative position to its coordinates
|
||||||
* @example
|
* @example
|
||||||
* [0, 0] // default, top left
|
* [0, 0] // default, top left
|
||||||
* [0.5, 0.5] // center
|
* [0.5, 0.5] // center
|
||||||
* [1, 1] // bottom right
|
* [1, 1] // bottom right
|
||||||
*/
|
*/
|
||||||
nodeOrigin?: NodeOrigin;
|
nodeOrigin?: NodeOrigin;
|
||||||
/** Controls if all edges should be focusable
|
/**
|
||||||
|
* Controls if all edges should be focusable
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
edgesFocusable?: boolean;
|
edgesFocusable?: boolean;
|
||||||
/** Controls if all edges should be updateable
|
/**
|
||||||
|
* Controls if all edges should be updateable
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
edgesReconnectable?: boolean;
|
edgesReconnectable?: boolean;
|
||||||
/** Controls if all elements should (nodes & edges) be selectable
|
/**
|
||||||
|
* Controls if all elements should (nodes & edges) be selectable
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
elementsSelectable?: boolean;
|
elementsSelectable?: boolean;
|
||||||
/** If true, nodes get selected on drag
|
/**
|
||||||
|
* If true, nodes get selected on drag
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
selectNodesOnDrag?: boolean;
|
selectNodesOnDrag?: boolean;
|
||||||
/** Enableing this prop allows users to pan the viewport by clicking and dragging.
|
/**
|
||||||
|
* Enableing this prop allows users to pan the viewport by clicking and dragging.
|
||||||
*
|
*
|
||||||
* You can also set this prop to an array of numbers to limit which mouse buttons can activate panning.
|
* You can also set this prop to an array of numbers to limit which mouse buttons can activate panning.
|
||||||
* @example [0, 2] // allows panning with the left and right mouse buttons
|
* @example [0, 2] // allows panning with the left and right mouse buttons
|
||||||
* [0, 1, 2, 3, 4] // allows panning with all mouse buttons
|
* [0, 1, 2, 3, 4] // allows panning with all mouse buttons
|
||||||
*/
|
*/
|
||||||
panOnDrag?: boolean | number[];
|
panOnDrag?: boolean | number[];
|
||||||
/** Minimum zoom level
|
/**
|
||||||
|
* Minimum zoom level
|
||||||
* @default 0.5
|
* @default 0.5
|
||||||
*/
|
*/
|
||||||
minZoom?: number;
|
minZoom?: number;
|
||||||
/** Maximum zoom level
|
/**
|
||||||
|
* Maximum zoom level
|
||||||
* @default 2
|
* @default 2
|
||||||
*/
|
*/
|
||||||
maxZoom?: number;
|
maxZoom?: number;
|
||||||
/** Controlled viewport to be used instead of internal one */
|
/** Controlled viewport to be used instead of internal one */
|
||||||
viewport?: Viewport;
|
viewport?: Viewport;
|
||||||
/** Sets the initial position and zoom of the viewport.
|
/**
|
||||||
|
* Sets the initial position and zoom of the viewport.
|
||||||
*
|
*
|
||||||
* If a default viewport is provided but fitView is enabled, the default viewport will be ignored.
|
* If a default viewport is provided but fitView is enabled, the default viewport will be ignored.
|
||||||
* @example
|
* @example
|
||||||
@@ -378,23 +411,27 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
|||||||
* Gets called when the viewport changes.
|
* Gets called when the viewport changes.
|
||||||
*/
|
*/
|
||||||
onViewportChange?: (viewport: Viewport) => void;
|
onViewportChange?: (viewport: Viewport) => void;
|
||||||
/** By default the viewport extends infinitely. You can use this prop to set a boundary.
|
/**
|
||||||
|
* By default the viewport extends infinitely. You can use this prop to set a boundary.
|
||||||
*
|
*
|
||||||
* The first pair of coordinates is the top left boundary and the second pair is the bottom right.
|
* The first pair of coordinates is the top left boundary and the second pair is the bottom right.
|
||||||
* @example [[-1000, -10000], [1000, 1000]]
|
* @example [[-1000, -10000], [1000, 1000]]
|
||||||
*/
|
*/
|
||||||
translateExtent?: CoordinateExtent;
|
translateExtent?: CoordinateExtent;
|
||||||
/** Disabling this prop will allow the user to scroll the page even when their pointer is over the flow.
|
/**
|
||||||
|
* Disabling this prop will allow the user to scroll the page even when their pointer is over the flow.
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
preventScrolling?: boolean;
|
preventScrolling?: boolean;
|
||||||
/** By default nodes can be placed on an infinite flow. You can use this prop to set a boundary.
|
/**
|
||||||
|
* By default nodes can be placed on an infinite flow. You can use this prop to set a boundary.
|
||||||
*
|
*
|
||||||
* The first pair of coordinates is the top left boundary and the second pair is the bottom right.
|
* The first pair of coordinates is the top left boundary and the second pair is the bottom right.
|
||||||
* @example [[-1000, -10000], [1000, 1000]]
|
* @example [[-1000, -10000], [1000, 1000]]
|
||||||
*/
|
*/
|
||||||
nodeExtent?: CoordinateExtent;
|
nodeExtent?: CoordinateExtent;
|
||||||
/** Color of edge markers
|
/**
|
||||||
|
* Color of edge markers
|
||||||
* @example "#b1b1b7"
|
* @example "#b1b1b7"
|
||||||
*/
|
*/
|
||||||
defaultMarkerColor?: string;
|
defaultMarkerColor?: string;
|
||||||
@@ -402,17 +439,20 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
|||||||
zoomOnScroll?: boolean;
|
zoomOnScroll?: boolean;
|
||||||
/** Controls if the viewport should zoom by pinching on a touch screen */
|
/** Controls if the viewport should zoom by pinching on a touch screen */
|
||||||
zoomOnPinch?: boolean;
|
zoomOnPinch?: boolean;
|
||||||
/** Controls if the viewport should pan by scrolling inside the container
|
/**
|
||||||
|
* Controls if the viewport should pan by scrolling inside the container
|
||||||
*
|
*
|
||||||
* Can be limited to a specific direction with panOnScrollMode
|
* Can be limited to a specific direction with panOnScrollMode
|
||||||
*/
|
*/
|
||||||
panOnScroll?: boolean;
|
panOnScroll?: boolean;
|
||||||
/** Controls how fast viewport should be panned on scroll.
|
/**
|
||||||
|
* Controls how fast viewport should be panned on scroll.
|
||||||
*
|
*
|
||||||
* Use togther with panOnScroll prop.
|
* Use togther with panOnScroll prop.
|
||||||
*/
|
*/
|
||||||
panOnScrollSpeed?: number;
|
panOnScrollSpeed?: number;
|
||||||
/** This prop is used to limit the direction of panning when panOnScroll is enabled.
|
/**
|
||||||
|
* This prop is used to limit the direction of panning when panOnScroll is enabled.
|
||||||
*
|
*
|
||||||
* The "free" option allows panning in any direction.
|
* The "free" option allows panning in any direction.
|
||||||
* @default "free"
|
* @default "free"
|
||||||
@@ -427,7 +467,8 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
|||||||
noPanClassName?: string;
|
noPanClassName?: string;
|
||||||
/** If set, initial viewport will show all nodes & edges */
|
/** If set, initial viewport will show all nodes & edges */
|
||||||
fitView?: boolean;
|
fitView?: boolean;
|
||||||
/** Options to be used in combination with fitView
|
/**
|
||||||
|
* Options to be used in combination with fitView
|
||||||
* @example
|
* @example
|
||||||
* const fitViewOptions = {
|
* const fitViewOptions = {
|
||||||
* padding: 0.1,
|
* padding: 0.1,
|
||||||
@@ -439,30 +480,35 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
|||||||
* };
|
* };
|
||||||
*/
|
*/
|
||||||
fitViewOptions?: FitViewOptions;
|
fitViewOptions?: FitViewOptions;
|
||||||
/**The connectOnClick option lets you click or tap on a source handle to start a connection
|
/**
|
||||||
|
*The connectOnClick option lets you click or tap on a source handle to start a connection
|
||||||
* and then click on a target handle to complete the connection.
|
* and then click on a target handle to complete the connection.
|
||||||
*
|
*
|
||||||
* If you set this option to false, users will need to drag the connection line to the target
|
* If you set this option to false, users will need to drag the connection line to the target
|
||||||
* handle to create a connection.
|
* handle to create a connection.
|
||||||
*/
|
*/
|
||||||
connectOnClick?: boolean;
|
connectOnClick?: boolean;
|
||||||
/** Set position of the attribution
|
/**
|
||||||
|
* Set position of the attribution
|
||||||
* @default 'bottom-right'
|
* @default 'bottom-right'
|
||||||
* @example 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
|
* @example 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
|
||||||
*/
|
*/
|
||||||
attributionPosition?: PanelPosition;
|
attributionPosition?: PanelPosition;
|
||||||
/** By default, we render a small attribution in the corner of your flows that links back to the project.
|
/**
|
||||||
|
* By default, we render a small attribution in the corner of your flows that links back to the project.
|
||||||
*
|
*
|
||||||
* Anyone is free to remove this attribution whether they're a Pro subscriber or not
|
* Anyone is free to remove this attribution whether they're a Pro subscriber or not
|
||||||
* but we ask that you take a quick look at our {@link https://reactflow.dev/learn/troubleshooting/remove-attribution | removing attribution guide}
|
* but we ask that you take a quick look at our {@link https://reactflow.dev/learn/troubleshooting/remove-attribution | removing attribution guide}
|
||||||
* before doing so.
|
* before doing so.
|
||||||
*/
|
*/
|
||||||
proOptions?: ProOptions;
|
proOptions?: ProOptions;
|
||||||
/** Enabling this option will raise the z-index of nodes when they are selected.
|
/**
|
||||||
|
* Enabling this option will raise the z-index of nodes when they are selected.
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
elevateNodesOnSelect?: boolean;
|
elevateNodesOnSelect?: boolean;
|
||||||
/** Enabling this option will raise the z-index of edges when they are selected.
|
/**
|
||||||
|
* Enabling this option will raise the z-index of edges when they are selected.
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
elevateEdgesOnSelect?: boolean;
|
elevateEdgesOnSelect?: boolean;
|
||||||
@@ -471,36 +517,43 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
|||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
disableKeyboardA11y?: boolean;
|
disableKeyboardA11y?: boolean;
|
||||||
/** You can enable this prop to automatically pan the viewport while dragging a node.
|
/**
|
||||||
|
* You can enable this prop to automatically pan the viewport while dragging a node.
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
autoPanOnNodeDrag?: boolean;
|
autoPanOnNodeDrag?: boolean;
|
||||||
/** You can enable this prop to automatically pan the viewport while dragging a node.
|
/**
|
||||||
|
* You can enable this prop to automatically pan the viewport while dragging a node.
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
autoPanOnConnect?: boolean;
|
autoPanOnConnect?: boolean;
|
||||||
/** The speed at which the viewport pans while dragging a node or a selection box.
|
/**
|
||||||
|
* The speed at which the viewport pans while dragging a node or a selection box.
|
||||||
* @default 15
|
* @default 15
|
||||||
*/
|
*/
|
||||||
autoPanSpeed?: number;
|
autoPanSpeed?: number;
|
||||||
/** You can enable this prop to automatically pan the viewport while making a new connection.
|
/**
|
||||||
|
* You can enable this prop to automatically pan the viewport while making a new connection.
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
connectionRadius?: number;
|
connectionRadius?: number;
|
||||||
/** Ocassionally something may happen that causes Svelte Flow to throw an error.
|
/**
|
||||||
|
* Ocassionally something may happen that causes Svelte Flow to throw an error.
|
||||||
*
|
*
|
||||||
* Instead of exploding your application, we log a message to the console and then call this event handler.
|
* Instead of exploding your application, we log a message to the console and then call this event handler.
|
||||||
* You might use it for additional logging or to show a message to the user.
|
* You might use it for additional logging or to show a message to the user.
|
||||||
*/
|
*/
|
||||||
onError?: OnError;
|
onError?: OnError;
|
||||||
/** This callback can be used to validate a new connection
|
/**
|
||||||
|
* This callback can be used to validate a new connection
|
||||||
*
|
*
|
||||||
* If you return false, the edge will not be added to your flow.
|
* If you return false, the edge will not be added to your flow.
|
||||||
* If you have custom connection logic its preferred to use this callback over the isValidConnection prop on the handle component for performance reasons.
|
* If you have custom connection logic its preferred to use this callback over the isValidConnection prop on the handle component for performance reasons.
|
||||||
* @default (connection: Connection) => true
|
* @default (connection: Connection) => true
|
||||||
*/
|
*/
|
||||||
isValidConnection?: IsValidConnection<EdgeType>;
|
isValidConnection?: IsValidConnection<EdgeType>;
|
||||||
/** With a threshold greater than zero you can control the distinction between node drag and click events.
|
/**
|
||||||
|
* With a threshold greater than zero you can control the distinction between node drag and click events.
|
||||||
*
|
*
|
||||||
* If threshold equals 1, you need to drag the node 1 pixel before a drag event is fired.
|
* If threshold equals 1, you need to drag the node 1 pixel before a drag event is fired.
|
||||||
* @default 1
|
* @default 1
|
||||||
@@ -510,12 +563,14 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
|||||||
width?: number;
|
width?: number;
|
||||||
/** Sets a fixed height for the flow */
|
/** Sets a fixed height for the flow */
|
||||||
height?: number;
|
height?: number;
|
||||||
/** Controls color scheme used for styling the flow
|
/**
|
||||||
|
* Controls color scheme used for styling the flow
|
||||||
* @default 'system'
|
* @default 'system'
|
||||||
* @example 'system' | 'light' | 'dark'
|
* @example 'system' | 'light' | 'dark'
|
||||||
*/
|
*/
|
||||||
colorMode?: ColorMode;
|
colorMode?: ColorMode;
|
||||||
/** If set true, some debug information will be logged to the console like which events are fired.
|
/**
|
||||||
|
* If set true, some debug information will be logged to the console like which events are fired.
|
||||||
*
|
*
|
||||||
* @default undefined
|
* @default undefined
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ import type {
|
|||||||
|
|
||||||
import { EdgeTypes, InternalNode, Node } from '.';
|
import { EdgeTypes, InternalNode, Node } from '.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inline
|
||||||
|
*/
|
||||||
export type EdgeLabelOptions = {
|
export type EdgeLabelOptions = {
|
||||||
label?: string | ReactNode;
|
label?: string | ReactNode;
|
||||||
labelStyle?: CSSProperties;
|
labelStyle?: CSSProperties;
|
||||||
@@ -29,7 +32,8 @@ export type EdgeLabelOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Edge type is mainly used for the `edges` that get passed to the ReactFlow component
|
* An `Edge` is the complete description with everything React Flow needs
|
||||||
|
* to know in order to render it.
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type Edge<
|
export type Edge<
|
||||||
@@ -91,6 +95,11 @@ export type EdgeWrapperProps<EdgeType extends Edge = Edge> = {
|
|||||||
disableKeyboardA11y?: boolean;
|
disableKeyboardA11y?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Many properties on an [`Edge`](/api-reference/types/edge) are optional. When a new edge is created,
|
||||||
|
* the properties that are not provided will be filled in with the default values
|
||||||
|
* passed to the `defaultEdgeOptions` prop of the [`<ReactFlow />`](/api-reference/react-flow#defaultedgeoptions) component.
|
||||||
|
*/
|
||||||
export type DefaultEdgeOptions = DefaultEdgeOptionsBase<Edge>;
|
export type DefaultEdgeOptions = DefaultEdgeOptionsBase<Edge>;
|
||||||
|
|
||||||
export type EdgeTextProps = SVGAttributes<SVGElement> &
|
export type EdgeTextProps = SVGAttributes<SVGElement> &
|
||||||
@@ -100,8 +109,10 @@ export type EdgeTextProps = SVGAttributes<SVGElement> &
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom edge component props
|
* When you implement a custom edge it is wrapped in a component that enables some
|
||||||
|
* basic functionality. The `EdgeProps` type is the props that are passed to this.
|
||||||
* @public
|
* @public
|
||||||
|
* @expand
|
||||||
*/
|
*/
|
||||||
export type EdgeProps<EdgeType extends Edge = Edge> = Pick<
|
export type EdgeProps<EdgeType extends Edge = Edge> = Pick<
|
||||||
EdgeType,
|
EdgeType,
|
||||||
@@ -121,6 +132,7 @@ export type EdgeProps<EdgeType extends Edge = Edge> = Pick<
|
|||||||
/**
|
/**
|
||||||
* BaseEdge component props
|
* BaseEdge component props
|
||||||
* @public
|
* @public
|
||||||
|
* @expand
|
||||||
*/
|
*/
|
||||||
export type BaseEdgeProps = Omit<SVGAttributes<SVGPathElement>, 'd'> &
|
export type BaseEdgeProps = Omit<SVGAttributes<SVGPathElement>, 'd'> &
|
||||||
EdgeLabelOptions & {
|
EdgeLabelOptions & {
|
||||||
@@ -137,6 +149,7 @@ export type BaseEdgeProps = Omit<SVGAttributes<SVGPathElement>, 'd'> &
|
|||||||
/**
|
/**
|
||||||
* Helper type for edge components that get exported by the library
|
* Helper type for edge components that get exported by the library
|
||||||
* @public
|
* @public
|
||||||
|
* @expand
|
||||||
*/
|
*/
|
||||||
export type EdgeComponentProps = EdgePosition &
|
export type EdgeComponentProps = EdgePosition &
|
||||||
EdgeLabelOptions & {
|
EdgeLabelOptions & {
|
||||||
@@ -156,35 +169,47 @@ export type EdgeComponentWithPathOptions<PathOptions> = EdgeComponentProps & {
|
|||||||
/**
|
/**
|
||||||
* BezierEdge component props
|
* BezierEdge component props
|
||||||
* @public
|
* @public
|
||||||
|
* @expand
|
||||||
*/
|
*/
|
||||||
export type BezierEdgeProps = EdgeComponentWithPathOptions<BezierPathOptions>;
|
export type BezierEdgeProps = EdgeComponentWithPathOptions<BezierPathOptions>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SmoothStepEdge component props
|
* SmoothStepEdge component props
|
||||||
* @public
|
* @public
|
||||||
|
* @expand
|
||||||
*/
|
*/
|
||||||
export type SmoothStepEdgeProps = EdgeComponentWithPathOptions<SmoothStepPathOptions>;
|
export type SmoothStepEdgeProps = EdgeComponentWithPathOptions<SmoothStepPathOptions>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* StepEdge component props
|
* StepEdge component props
|
||||||
* @public
|
* @public
|
||||||
|
* @expand
|
||||||
*/
|
*/
|
||||||
export type StepEdgeProps = EdgeComponentWithPathOptions<StepPathOptions>;
|
export type StepEdgeProps = EdgeComponentWithPathOptions<StepPathOptions>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* StraightEdge component props
|
* StraightEdge component props
|
||||||
* @public
|
* @public
|
||||||
|
* @expand
|
||||||
*/
|
*/
|
||||||
export type StraightEdgeProps = Omit<EdgeComponentProps, 'sourcePosition' | 'targetPosition'>;
|
export type StraightEdgeProps = Omit<EdgeComponentProps, 'sourcePosition' | 'targetPosition'>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SimpleBezier component props
|
* SimpleBezier component props
|
||||||
* @public
|
* @public
|
||||||
|
* @expand
|
||||||
*/
|
*/
|
||||||
export type SimpleBezierEdgeProps = EdgeComponentProps;
|
export type SimpleBezierEdgeProps = EdgeComponentProps;
|
||||||
|
|
||||||
export type OnReconnect<EdgeType extends Edge = Edge> = (oldEdge: EdgeType, newConnection: Connection) => void;
|
export type OnReconnect<EdgeType extends Edge = Edge> = (oldEdge: EdgeType, newConnection: Connection) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If you want to render a custom component for connection lines, you can set the
|
||||||
|
* `connectionLineComponent` prop on the [`<ReactFlow />`](/api-reference/react-flow#connection-connectionLineComponent)
|
||||||
|
* component. The `ConnectionLineComponentProps` are passed to your custom component.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export type ConnectionLineComponentProps<NodeType extends Node = Node> = {
|
export type ConnectionLineComponentProps<NodeType extends Node = Node> = {
|
||||||
connectionLineStyle?: CSSProperties;
|
connectionLineStyle?: CSSProperties;
|
||||||
connectionLineType: ConnectionLineType;
|
connectionLineType: ConnectionLineType;
|
||||||
|
|||||||
@@ -18,11 +18,44 @@ import {
|
|||||||
|
|
||||||
import type { Node, Edge, ReactFlowInstance, EdgeProps, NodeProps } from '.';
|
import type { Node, Edge, ReactFlowInstance, EdgeProps, NodeProps } from '.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This type can be used to type the `onNodesChange` function with a custom node type.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* const onNodesChange: OnNodesChange<MyNodeType> = useCallback((changes) => {
|
||||||
|
* setNodes((nodes) => applyNodeChanges(nodes, changes));
|
||||||
|
* },[]);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export type OnNodesChange<NodeType extends Node = Node> = (changes: NodeChange<NodeType>[]) => void;
|
export type OnNodesChange<NodeType extends Node = Node> = (changes: NodeChange<NodeType>[]) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This type can be used to type the `onEdgesChange` function with a custom edge type.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* const onEdgesChange: OnEdgesChange<MyEdgeType> = useCallback((changes) => {
|
||||||
|
* setEdges((edges) => applyEdgeChanges(edges, changes));
|
||||||
|
* },[]);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export type OnEdgesChange<EdgeType extends Edge = Edge> = (changes: EdgeChange<EdgeType>[]) => void;
|
export type OnEdgesChange<EdgeType extends Edge = Edge> = (changes: EdgeChange<EdgeType>[]) => void;
|
||||||
|
|
||||||
export type OnNodesDelete<NodeType extends Node = Node> = (nodes: NodeType[]) => void;
|
export type OnNodesDelete<NodeType extends Node = Node> = (nodes: NodeType[]) => void;
|
||||||
export type OnEdgesDelete<EdgeType extends Edge = Edge> = (edges: EdgeType[]) => void;
|
export type OnEdgesDelete<EdgeType extends Edge = Edge> = (edges: EdgeType[]) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This type can be used to type the `onDelete` function with a custom node and edge type.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export type OnDelete<NodeType extends Node = Node, EdgeType extends Edge = Edge> = (params: {
|
export type OnDelete<NodeType extends Node = Node, EdgeType extends Edge = Edge> = (params: {
|
||||||
nodes: NodeType[];
|
nodes: NodeType[];
|
||||||
edges: EdgeType[];
|
edges: EdgeType[];
|
||||||
@@ -39,6 +72,7 @@ export type NodeTypes = Record<
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type EdgeTypes = Record<
|
export type EdgeTypes = Record<
|
||||||
string,
|
string,
|
||||||
ComponentType<
|
ComponentType<
|
||||||
@@ -64,12 +98,23 @@ export type OnSelectionChangeParams = {
|
|||||||
export type OnSelectionChangeFunc = (params: OnSelectionChangeParams) => void;
|
export type OnSelectionChangeFunc = (params: OnSelectionChangeParams) => void;
|
||||||
|
|
||||||
export type FitViewParams<NodeType extends Node = Node> = FitViewParamsBase<NodeType>;
|
export type FitViewParams<NodeType extends Node = Node> = FitViewParamsBase<NodeType>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When calling [`fitView`](/api-reference/types/react-flow-instance#fitview) these options
|
||||||
|
* can be used to customize the behaviour. For example, the `duration` option can be used to
|
||||||
|
* transform the viewport smoothly over a given amount of time.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export type FitViewOptions<NodeType extends Node = Node> = FitViewOptionsBase<NodeType>;
|
export type FitViewOptions<NodeType extends Node = Node> = FitViewOptionsBase<NodeType>;
|
||||||
export type FitView = (fitViewOptions?: FitViewOptions) => Promise<boolean>;
|
export type FitView = (fitViewOptions?: FitViewOptions) => Promise<boolean>;
|
||||||
export type OnInit<NodeType extends Node = Node, EdgeType extends Edge = Edge> = (
|
export type OnInit<NodeType extends Node = Node, EdgeType extends Edge = Edge> = (
|
||||||
reactFlowInstance: ReactFlowInstance<NodeType, EdgeType>
|
reactFlowInstance: ReactFlowInstance<NodeType, EdgeType>
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inline
|
||||||
|
*/
|
||||||
export type ViewportHelperFunctions = {
|
export type ViewportHelperFunctions = {
|
||||||
/**
|
/**
|
||||||
* Zooms viewport in by 1.2.
|
* Zooms viewport in by 1.2.
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ export type DeleteElementsOptions = {
|
|||||||
edges?: (Edge | { id: Edge['id'] })[];
|
edges?: (Edge | { id: Edge['id'] })[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inline
|
||||||
|
*/
|
||||||
export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
|
export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
|
||||||
/**
|
/**
|
||||||
* Returns nodes.
|
* Returns nodes.
|
||||||
@@ -217,6 +220,14 @@ export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge =
|
|||||||
}) => NodeConnection[];
|
}) => NodeConnection[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `ReactFlowInstance` provides a collection of methods to query and manipulate
|
||||||
|
* the internal state of your flow. You can get an instance by using the
|
||||||
|
* [`useReactFlow`](/api-reference/hooks/use-react-flow) hook or attaching a listener
|
||||||
|
* to the [`onInit`](/api-reference/react-flow#event-oninit) event.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export type ReactFlowInstance<NodeType extends Node = Node, EdgeType extends Edge = Edge> = GeneralHelpers<
|
export type ReactFlowInstance<NodeType extends Node = Node, EdgeType extends Edge = Edge> = GeneralHelpers<
|
||||||
NodeType,
|
NodeType,
|
||||||
EdgeType
|
EdgeType
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import type { CoordinateExtent, NodeBase, OnError, NodeProps as NodePropsBase, I
|
|||||||
import { NodeTypes } from './general';
|
import { NodeTypes } from './general';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The node data structure that gets used for the nodes prop.
|
* The `Node` type represents everything React Flow needs to know about a given node.
|
||||||
|
* Whenever you want to update a certain attribute of a node, you need to create a new
|
||||||
|
* node object.
|
||||||
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type Node<
|
export type Node<
|
||||||
@@ -18,9 +21,10 @@ export type Node<
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The node data structure that gets used for internal nodes.
|
* The `InternalNode` type is identical to the base [`Node`](/api-references/types/node)
|
||||||
* There are some data structures added under node.internal
|
* type but is extended with some additional properties used internally.
|
||||||
* that are needed for tracking some properties
|
* Some functions and callbacks that return nodes may return an `InternalNode`.
|
||||||
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type InternalNode<NodeType extends Node = Node> = InternalNodeBase<NodeType>;
|
export type InternalNode<NodeType extends Node = Node> = InternalNodeBase<NodeType>;
|
||||||
@@ -56,8 +60,46 @@ export type NodeWrapperProps<NodeType extends Node> = {
|
|||||||
nodeClickDistance?: number;
|
nodeClickDistance?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `BuiltInNode` type represents the built-in node types that are available in React Flow.
|
||||||
|
* You can use this type to extend your custom node type if you still want ot use the built-in ones.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* type CustomNode = Node<{ value: number }, 'custom'>;
|
||||||
|
* type MyAppNode = CustomNode | BuiltInNode;
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export type BuiltInNode =
|
export type BuiltInNode =
|
||||||
| Node<{ label: string }, 'input' | 'output' | 'default'>
|
| Node<{ label: string }, 'input' | 'output' | 'default'>
|
||||||
| Node<Record<string, never>, 'group'>;
|
| Node<Record<string, never>, 'group'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When you implement a [custom node](/learn/customization/custom-nodes) it is
|
||||||
|
* wrapped in a component that enables basic functionality like selection and
|
||||||
|
* dragging. Your custom node receives `NodeProps` as props.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
*import { useState } from 'react';
|
||||||
|
*import { NodeProps, Node } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*export type CounterNode = Node<{ initialCount?: number }, 'counter'>;
|
||||||
|
*
|
||||||
|
*export default function CounterNode(props: NodeProps<CounterNode>) {
|
||||||
|
* const [count, setCount] = useState(props.data?.initialCount ?? 0);
|
||||||
|
*
|
||||||
|
* return (
|
||||||
|
* <div>
|
||||||
|
* <p>Count: {count}</p>
|
||||||
|
* <button className="nodrag" onClick={() => setCount(count + 1)}>
|
||||||
|
* Increment
|
||||||
|
* </button>
|
||||||
|
* </div>
|
||||||
|
* );
|
||||||
|
*}
|
||||||
|
*```
|
||||||
|
*/
|
||||||
export type NodeProps<NodeType extends Node = Node> = NodePropsBase<NodeType>;
|
export type NodeProps<NodeType extends Node = Node> = NodePropsBase<NodeType>;
|
||||||
|
|||||||
@@ -11,13 +11,17 @@ import {
|
|||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
import type { Node, Edge, InternalNode } from '../types';
|
import type { Node, Edge, InternalNode } from '../types';
|
||||||
|
|
||||||
// This function applies changes to nodes or edges that are triggered by React Flow internally.
|
/*
|
||||||
// When you drag a node for example, React Flow will send a position change update.
|
* This function applies changes to nodes or edges that are triggered by React Flow internally.
|
||||||
// This function then applies the changes and returns the updated elements.
|
* When you drag a node for example, React Flow will send a position change update.
|
||||||
|
* This function then applies the changes and returns the updated elements.
|
||||||
|
*/
|
||||||
function applyChanges(changes: any[], elements: any[]): any[] {
|
function applyChanges(changes: any[], elements: any[]): any[] {
|
||||||
const updatedElements: any[] = [];
|
const updatedElements: any[] = [];
|
||||||
// By storing a map of changes for each element, we can a quick lookup as we
|
/*
|
||||||
// iterate over the elements array!
|
* By storing a map of changes for each element, we can a quick lookup as we
|
||||||
|
* iterate over the elements array!
|
||||||
|
*/
|
||||||
const changesMap = new Map<any, any[]>();
|
const changesMap = new Map<any, any[]>();
|
||||||
const addItemChanges: any[] = [];
|
const addItemChanges: any[] = [];
|
||||||
|
|
||||||
@@ -26,15 +30,19 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
|||||||
addItemChanges.push(change);
|
addItemChanges.push(change);
|
||||||
continue;
|
continue;
|
||||||
} else if (change.type === 'remove' || change.type === 'replace') {
|
} else if (change.type === 'remove' || change.type === 'replace') {
|
||||||
// For a 'remove' change we can safely ignore any other changes queued for
|
/*
|
||||||
// the same element, it's going to be removed anyway!
|
* For a 'remove' change we can safely ignore any other changes queued for
|
||||||
|
* the same element, it's going to be removed anyway!
|
||||||
|
*/
|
||||||
changesMap.set(change.id, [change]);
|
changesMap.set(change.id, [change]);
|
||||||
} else {
|
} else {
|
||||||
const elementChanges = changesMap.get(change.id);
|
const elementChanges = changesMap.get(change.id);
|
||||||
|
|
||||||
if (elementChanges) {
|
if (elementChanges) {
|
||||||
// If we have some changes queued already, we can do a mutable update of
|
/*
|
||||||
// that array and save ourselves some copying.
|
* If we have some changes queued already, we can do a mutable update of
|
||||||
|
* that array and save ourselves some copying.
|
||||||
|
*/
|
||||||
elementChanges.push(change);
|
elementChanges.push(change);
|
||||||
} else {
|
} else {
|
||||||
changesMap.set(change.id, [change]);
|
changesMap.set(change.id, [change]);
|
||||||
@@ -45,8 +53,10 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
|||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
const changes = changesMap.get(element.id);
|
const changes = changesMap.get(element.id);
|
||||||
|
|
||||||
// When there are no changes for an element we can just push it unmodified,
|
/*
|
||||||
// no need to copy it.
|
* When there are no changes for an element we can just push it unmodified,
|
||||||
|
* no need to copy it.
|
||||||
|
*/
|
||||||
if (!changes) {
|
if (!changes) {
|
||||||
updatedElements.push(element);
|
updatedElements.push(element);
|
||||||
continue;
|
continue;
|
||||||
@@ -62,9 +72,11 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For other types of changes, we want to start with a shallow copy of the
|
/**
|
||||||
// object so React knows this element has changed. Sequential changes will
|
* For other types of changes, we want to start with a shallow copy of the
|
||||||
/// each _mutate_ this object, so there's only ever one copy.
|
* object so React knows this element has changed. Sequential changes will
|
||||||
|
* each _mutate_ this object, so there's only ever one copy.
|
||||||
|
*/
|
||||||
const updatedElement = { ...element };
|
const updatedElement = { ...element };
|
||||||
|
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
@@ -74,8 +86,10 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
|||||||
updatedElements.push(updatedElement);
|
updatedElements.push(updatedElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we need to wait for all changes to be applied before adding new items
|
/*
|
||||||
// to be able to add them at the correct index
|
* we need to wait for all changes to be applied before adding new items
|
||||||
|
* to be able to add them at the correct index
|
||||||
|
*/
|
||||||
if (addItemChanges.length) {
|
if (addItemChanges.length) {
|
||||||
addItemChanges.forEach((change) => {
|
addItemChanges.forEach((change) => {
|
||||||
if (change.index !== undefined) {
|
if (change.index !== undefined) {
|
||||||
@@ -133,22 +147,33 @@ function applyChange(change: any, element: any): any {
|
|||||||
/**
|
/**
|
||||||
* Drop in function that applies node changes to an array of nodes.
|
* Drop in function that applies node changes to an array of nodes.
|
||||||
* @public
|
* @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 changes - Array of changes to apply
|
||||||
* @param nodes - Array of nodes to apply the changes to
|
* @param nodes - Array of nodes to apply the changes to
|
||||||
* @returns Array of updated nodes
|
* @returns Array of updated nodes
|
||||||
* @example
|
* @example
|
||||||
* const onNodesChange = useCallback(
|
*```tsx
|
||||||
(changes) => {
|
*import { useState, useCallback } from 'react';
|
||||||
setNodes((oldNodes) => applyNodeChanges(changes, oldNodes));
|
*import { ReactFlow, applyNodeChanges, type Node, type Edge, type OnNodesChange } from '@xyflow/react';
|
||||||
},
|
*
|
||||||
[setNodes],
|
*export default function Flow() {
|
||||||
);
|
* const [nodes, setNodes] = useState<Node[]>([]);
|
||||||
|
* const [edges, setEdges] = useState<Edge[]>([]);
|
||||||
return (
|
* const onNodesChange: OnNodesChange = useCallback(
|
||||||
<ReactFLow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
|
* (changes) => {
|
||||||
);
|
* setNodes((oldNodes) => applyNodeChanges(changes, oldNodes));
|
||||||
|
* },
|
||||||
|
* [setNodes],
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* return (
|
||||||
|
* <ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
|
||||||
|
* );
|
||||||
|
*}
|
||||||
|
*```
|
||||||
|
* @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.
|
||||||
*/
|
*/
|
||||||
export function applyNodeChanges<NodeType extends Node = Node>(
|
export function applyNodeChanges<NodeType extends Node = Node>(
|
||||||
changes: NodeChange<NodeType>[],
|
changes: NodeChange<NodeType>[],
|
||||||
@@ -160,22 +185,33 @@ export function applyNodeChanges<NodeType extends Node = Node>(
|
|||||||
/**
|
/**
|
||||||
* Drop in function that applies edge changes to an array of edges.
|
* Drop in function that applies edge changes to an array of edges.
|
||||||
* @public
|
* @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 changes - Array of changes to apply
|
||||||
* @param edges - Array of edge to apply the changes to
|
* @param edges - Array of edge to apply the changes to
|
||||||
* @returns Array of updated edges
|
* @returns Array of updated edges
|
||||||
* @example
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
*import { useState, useCallback } from 'react';
|
||||||
|
*import { ReactFlow, applyEdgeChanges } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*export default function Flow() {
|
||||||
|
* const [nodes, setNodes] = useState([]);
|
||||||
|
* const [edges, setEdges] = useState([]);
|
||||||
* const onEdgesChange = useCallback(
|
* const onEdgesChange = useCallback(
|
||||||
(changes) => {
|
* (changes) => {
|
||||||
setEdges((oldEdges) => applyEdgeChanges(changes, oldEdges));
|
* setEdges((oldEdges) => applyEdgeChanges(changes, oldEdges));
|
||||||
},
|
* },
|
||||||
[setEdges],
|
* [setEdges],
|
||||||
);
|
* );
|
||||||
|
*
|
||||||
return (
|
* return (
|
||||||
<ReactFlow nodes={nodes} edges={edges} onEdgesChange={onEdgesChange} />
|
* <ReactFlow nodes={nodes} edges={edges} onEdgesChange={onEdgesChange} />
|
||||||
);
|
* );
|
||||||
|
*}
|
||||||
|
*```
|
||||||
|
* @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.
|
||||||
*/
|
*/
|
||||||
export function applyEdgeChanges<EdgeType extends Edge = Edge>(
|
export function applyEdgeChanges<EdgeType extends Edge = Edge>(
|
||||||
changes: EdgeChange<EdgeType>[],
|
changes: EdgeChange<EdgeType>[],
|
||||||
@@ -205,9 +241,11 @@ export function getSelectionChanges(
|
|||||||
// we don't want to set all items to selected=false on the first selection
|
// we don't want to set all items to selected=false on the first selection
|
||||||
if (!(item.selected === undefined && !willBeSelected) && item.selected !== willBeSelected) {
|
if (!(item.selected === undefined && !willBeSelected) && item.selected !== willBeSelected) {
|
||||||
if (mutateItem) {
|
if (mutateItem) {
|
||||||
// this hack is needed for nodes. When the user dragged a node, it's selected.
|
/*
|
||||||
// When another node gets dragged, we need to deselect the previous one,
|
* this hack is needed for nodes. When the user dragged a node, it's selected.
|
||||||
// in order to have only one selected node at a time - the onNodesChange callback comes too late here :/
|
* When another node gets dragged, we need to deselect the previous one,
|
||||||
|
* in order to have only one selected node at a time - the onNodesChange callback comes too late here :/
|
||||||
|
*/
|
||||||
item.selected = willBeSelected;
|
item.selected = willBeSelected;
|
||||||
}
|
}
|
||||||
changes.push(createSelectionChange(item.id, willBeSelected));
|
changes.push(createSelectionChange(item.id, willBeSelected));
|
||||||
|
|||||||
@@ -4,21 +4,45 @@ import { isNodeBase, isEdgeBase } from '@xyflow/system';
|
|||||||
import type { Edge, Node } from '../types';
|
import type { Edge, Node } from '../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test whether an object is useable as a Node
|
* Test whether an object is useable as an [`Node`](/api-reference/types/node).
|
||||||
|
* In TypeScript this is a type guard that will narrow the type of whatever you pass in to
|
||||||
|
* [`Node`](/api-reference/types/node) if it returns `true`.
|
||||||
|
*
|
||||||
* @public
|
* @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
|
* @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
|
* @param element - The element to test
|
||||||
* @returns A boolean indicating whether the element is an Node
|
* @returns A boolean indicating whether the element is an Node
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
*import { isNode } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*if (isNode(node)) {
|
||||||
|
* // ..
|
||||||
|
*}
|
||||||
|
*```
|
||||||
*/
|
*/
|
||||||
export const isNode = <NodeType extends Node = Node>(element: unknown): element is NodeType =>
|
export const isNode = <NodeType extends Node = Node>(element: unknown): element is NodeType =>
|
||||||
isNodeBase<NodeType>(element);
|
isNodeBase<NodeType>(element);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test whether an object is useable as an Edge
|
* Test whether an object is useable as an [`Edge`](/api-reference/types/edge).
|
||||||
|
* In TypeScript this is a type guard that will narrow the type of whatever you pass in to
|
||||||
|
* [`Edge`](/api-reference/types/edge) if it returns `true`.
|
||||||
|
*
|
||||||
* @public
|
* @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
|
* @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
|
* @param element - The element to test
|
||||||
* @returns A boolean indicating whether the element is an Edge
|
* @returns A boolean indicating whether the element is an Edge
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
*import { isEdge } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*if (isEdge(edge)) {
|
||||||
|
* // ..
|
||||||
|
*}
|
||||||
|
*```
|
||||||
*/
|
*/
|
||||||
export const isEdge = <EdgeType extends Edge = Edge>(element: unknown): element is EdgeType =>
|
export const isEdge = <EdgeType extends Edge = Edge>(element: unknown): element is EdgeType =>
|
||||||
isEdgeBase<EdgeType>(element);
|
isEdgeBase<EdgeType>(element);
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import type {
|
|||||||
import type { Node } from '$lib/types';
|
import type { Node } from '$lib/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Edge type is mainly used for the `edges` that get passed to the SvelteFlow component.
|
* An `Edge` is the complete description with everything React Flow needs
|
||||||
|
*to know in order to render it.
|
||||||
|
* @public
|
||||||
*/
|
*/
|
||||||
export type Edge<
|
export type Edge<
|
||||||
EdgeData extends Record<string, unknown> = Record<string, unknown>,
|
EdgeData extends Record<string, unknown> = Record<string, unknown>,
|
||||||
|
|||||||
@@ -42,7 +42,10 @@ export type NodeReplaceChange<NodeType extends NodeBase = NodeBase> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Union type of all possible node changes.
|
* The [`onNodesChange`](/api-reference/react-flow#on-nodes-change) callback takes
|
||||||
|
*an array of `NodeChange` objects that you should use to update your flow's state.
|
||||||
|
*The `NodeChange` type is a union of six different object types that represent that
|
||||||
|
*various ways an node can change in a flow.
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type NodeChange<NodeType extends NodeBase = NodeBase> =
|
export type NodeChange<NodeType extends NodeBase = NodeBase> =
|
||||||
@@ -67,6 +70,14 @@ export type EdgeReplaceChange<EdgeType extends EdgeBase = EdgeBase> = {
|
|||||||
type: 'replace';
|
type: 'replace';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [`onEdgesChange`](/api-reference/react-flow#on-edges-change) callback takes
|
||||||
|
*an array of `EdgeChange` objects that you should use to update your flow's state.
|
||||||
|
*The `EdgeChange` type is a union of four different object types that represent that
|
||||||
|
*various ways an edge can change in a flow.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export type EdgeChange<EdgeType extends EdgeBase = EdgeBase> =
|
export type EdgeChange<EdgeType extends EdgeBase = EdgeBase> =
|
||||||
| EdgeSelectionChange
|
| EdgeSelectionChange
|
||||||
| EdgeRemoveChange
|
| EdgeRemoveChange
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ export type EdgeBase<
|
|||||||
source: string;
|
source: string;
|
||||||
/** Id of target node */
|
/** Id of target node */
|
||||||
target: string;
|
target: string;
|
||||||
/** Id of source handle
|
/**
|
||||||
|
* Id of source handle
|
||||||
* only needed if there are multiple handles per node
|
* only needed if there are multiple handles per node
|
||||||
*/
|
*/
|
||||||
sourceHandle?: string | null;
|
sourceHandle?: string | null;
|
||||||
/** Id of target handle
|
/**
|
||||||
|
* Id of target handle
|
||||||
* only needed if there are multiple handles per node
|
* only needed if there are multiple handles per node
|
||||||
*/
|
*/
|
||||||
targetHandle?: string | null;
|
targetHandle?: string | null;
|
||||||
@@ -27,11 +29,13 @@ export type EdgeBase<
|
|||||||
/** Arbitrary data passed to an edge */
|
/** Arbitrary data passed to an edge */
|
||||||
data?: EdgeData;
|
data?: EdgeData;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
/** Set the marker on the beginning of an edge
|
/**
|
||||||
|
* Set the marker on the beginning of an edge
|
||||||
* @example 'arrow', 'arrowclosed' or custom marker
|
* @example 'arrow', 'arrowclosed' or custom marker
|
||||||
*/
|
*/
|
||||||
markerStart?: EdgeMarkerType;
|
markerStart?: EdgeMarkerType;
|
||||||
/** Set the marker on the end of an edge
|
/**
|
||||||
|
* Set the marker on the end of an edge
|
||||||
* @example 'arrow', 'arrowclosed' or custom marker
|
* @example 'arrow', 'arrowclosed' or custom marker
|
||||||
*/
|
*/
|
||||||
markerEnd?: EdgeMarkerType;
|
markerEnd?: EdgeMarkerType;
|
||||||
@@ -54,11 +58,24 @@ export type BezierPathOptions = {
|
|||||||
curvature?: number;
|
curvature?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inline
|
||||||
|
*/
|
||||||
export type DefaultEdgeOptionsBase<EdgeType extends EdgeBase> = Omit<
|
export type DefaultEdgeOptionsBase<EdgeType extends EdgeBase> = Omit<
|
||||||
EdgeType,
|
EdgeType,
|
||||||
'id' | 'source' | 'target' | 'sourceHandle' | 'targetHandle' | 'selected'
|
'id' | 'source' | 'target' | 'sourceHandle' | 'targetHandle' | 'selected'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If you set the `connectionLineType` prop on your [`<ReactFlow />`](/api-reference/react-flow#connection-connectionLineType)
|
||||||
|
*component, it will dictate the style of connection line rendered when creating
|
||||||
|
*new edges.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* @remarks If you choose to render a custom connection line component, this value will be
|
||||||
|
*passed to your component as part of its [`ConnectionLineComponentProps`](/api-reference/types/connection-line-component-props).
|
||||||
|
*/
|
||||||
export enum ConnectionLineType {
|
export enum ConnectionLineType {
|
||||||
Bezier = 'default',
|
Bezier = 'default',
|
||||||
Straight = 'straight',
|
Straight = 'straight',
|
||||||
@@ -67,6 +84,13 @@ export enum ConnectionLineType {
|
|||||||
SimpleBezier = 'simplebezier',
|
SimpleBezier = 'simplebezier',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edges can optionally have markers at the start and end of an edge. The `EdgeMarker`
|
||||||
|
*type is used to configure those markers! Check the docs for [`MarkerType`](/api-reference/types/marker-type)
|
||||||
|
*for details on what types of edge marker are available.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export type EdgeMarker = {
|
export type EdgeMarker = {
|
||||||
type: MarkerType;
|
type: MarkerType;
|
||||||
color?: string;
|
color?: string;
|
||||||
@@ -79,6 +103,12 @@ export type EdgeMarker = {
|
|||||||
|
|
||||||
export type EdgeMarkerType = string | EdgeMarker;
|
export type EdgeMarkerType = string | EdgeMarker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edges may optionally have a marker on either end. The MarkerType type enumerates
|
||||||
|
* the options available to you when configuring a given marker.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export enum MarkerType {
|
export enum MarkerType {
|
||||||
Arrow = 'arrow',
|
Arrow = 'arrow',
|
||||||
ArrowClosed = 'arrowclosed',
|
ArrowClosed = 'arrowclosed',
|
||||||
@@ -88,6 +118,9 @@ export type MarkerProps = EdgeMarker & {
|
|||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inline
|
||||||
|
*/
|
||||||
export type EdgePosition = {
|
export type EdgePosition = {
|
||||||
sourceX: number;
|
sourceX: number;
|
||||||
sourceY: number;
|
sourceY: number;
|
||||||
|
|||||||
@@ -26,6 +26,13 @@ export type SetViewport = (viewport: Viewport, options?: ViewportHelperFunctionO
|
|||||||
export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => Promise<boolean>;
|
export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => Promise<boolean>;
|
||||||
export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => Promise<boolean>;
|
export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Connection` type is the basic minimal description of an [`Edge`](/api-reference/types/edge)
|
||||||
|
* between two nodes. The [`addEdge`](/api-reference/utils/add-edge) util can be used to upgrade
|
||||||
|
* a `Connection` to an [`Edge`](/api-reference/types/edge).
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export type Connection = {
|
export type Connection = {
|
||||||
source: string;
|
source: string;
|
||||||
target: string;
|
target: string;
|
||||||
@@ -33,15 +40,28 @@ export type Connection = {
|
|||||||
targetHandle: string | null;
|
targetHandle: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: remove in next version
|
/**
|
||||||
|
* The `HandleConnection` type is an extention of a basic [Connection](/api-reference/types/connection) that includes the `edgeId`.
|
||||||
|
*/
|
||||||
export type HandleConnection = Connection & {
|
export type HandleConnection = Connection & {
|
||||||
edgeId: string;
|
edgeId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `NodeConnection` type is an extention of a basic [Connection](/api-reference/types/connection) that includes the `edgeId`.
|
||||||
|
*
|
||||||
|
*/
|
||||||
export type NodeConnection = Connection & {
|
export type NodeConnection = Connection & {
|
||||||
edgeId: string;
|
edgeId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `ConnectionMode` is used to set the mode of connection between nodes.
|
||||||
|
* The `Strict` mode is the default one and only allows source to target edges.
|
||||||
|
* `Loose` mode allows source to source and target to target edges as well.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export enum ConnectionMode {
|
export enum ConnectionMode {
|
||||||
Strict = 'strict',
|
Strict = 'strict',
|
||||||
Loose = 'loose',
|
Loose = 'loose',
|
||||||
@@ -59,6 +79,9 @@ export type OnConnectEnd = (event: MouseEvent | TouchEvent, connectionState: Fin
|
|||||||
|
|
||||||
export type IsValidConnection = (edge: EdgeBase | Connection) => boolean;
|
export type IsValidConnection = (edge: EdgeBase | Connection) => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inline
|
||||||
|
*/
|
||||||
export type FitViewParamsBase<NodeType extends NodeBase> = {
|
export type FitViewParamsBase<NodeType extends NodeBase> = {
|
||||||
nodes: Map<string, InternalNodeBase<NodeType>>;
|
nodes: Map<string, InternalNodeBase<NodeType>>;
|
||||||
width: number;
|
width: number;
|
||||||
@@ -68,6 +91,9 @@ export type FitViewParamsBase<NodeType extends NodeBase> = {
|
|||||||
maxZoom: number;
|
maxZoom: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inline
|
||||||
|
*/
|
||||||
export type FitViewOptionsBase<NodeType extends NodeBase = NodeBase> = {
|
export type FitViewOptionsBase<NodeType extends NodeBase = NodeBase> = {
|
||||||
padding?: number;
|
padding?: number;
|
||||||
includeHiddenNodes?: boolean;
|
includeHiddenNodes?: boolean;
|
||||||
@@ -77,6 +103,17 @@ export type FitViewOptionsBase<NodeType extends NodeBase = NodeBase> = {
|
|||||||
nodes?: (NodeType | { id: string })[];
|
nodes?: (NodeType | { id: string })[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internally, React Flow maintains a coordinate system that is independent of the
|
||||||
|
* rest of the page. The `Viewport` type tells you where in that system your flow
|
||||||
|
* is currently being display at and how zoomed in or out it is.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @remarks A `Transform` has the same properties as the viewport, but they represent
|
||||||
|
* different things. Make sure you don't get them muddled up or things will start
|
||||||
|
* to look weird!
|
||||||
|
*
|
||||||
|
*/
|
||||||
export type Viewport = {
|
export type Viewport = {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
@@ -87,12 +124,23 @@ export type KeyCode = string | Array<string>;
|
|||||||
|
|
||||||
export type SnapGrid = [number, number];
|
export type SnapGrid = [number, number];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This enum is used to set the different modes of panning the viewport when the
|
||||||
|
* user scrolls. The `Free` mode allows the user to pan in any direction by scrolling
|
||||||
|
* with a device like a trackpad. The `Vertical` and `Horizontal` modes restrict
|
||||||
|
* scroll panning to only the vertical or horizontal axis, respectively.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export enum PanOnScrollMode {
|
export enum PanOnScrollMode {
|
||||||
Free = 'free',
|
Free = 'free',
|
||||||
Vertical = 'vertical',
|
Vertical = 'vertical',
|
||||||
Horizontal = 'horizontal',
|
Horizontal = 'horizontal',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inline
|
||||||
|
*/
|
||||||
export type ViewportHelperFunctionOptions = {
|
export type ViewportHelperFunctionOptions = {
|
||||||
duration?: number;
|
duration?: number;
|
||||||
};
|
};
|
||||||
@@ -113,6 +161,14 @@ export type D3ZoomHandler = (this: Element, event: any, d: unknown) => void;
|
|||||||
|
|
||||||
export type UpdateNodeInternals = (nodeId: string | string[]) => void;
|
export type UpdateNodeInternals = (nodeId: string | string[]) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This type is mostly used to help position things on top of the flow viewport. For
|
||||||
|
* example both the [`<MiniMap />`](/api-reference/components/minimap) and
|
||||||
|
* [`<Controls />`](/api-reference/components/controls) components take a `position`
|
||||||
|
* prop of this type.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export type PanelPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
export type PanelPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
||||||
|
|
||||||
export type ProOptions = {
|
export type ProOptions = {
|
||||||
@@ -174,6 +230,13 @@ export type ConnectionInProgress<NodeType extends InternalNodeBase = InternalNod
|
|||||||
toPosition: Position;
|
toPosition: Position;
|
||||||
toNode: NodeType | null;
|
toNode: NodeType | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `ConnectionState` type bundles all information about an ongoing connection.
|
||||||
|
* It is returned by the [`useConnection`](/api-reference/hooks/use-connection) hook.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export type ConnectionState<NodeType extends InternalNodeBase = InternalNodeBase> =
|
export type ConnectionState<NodeType extends InternalNodeBase = InternalNodeBase> =
|
||||||
| ConnectionInProgress<NodeType>
|
| ConnectionInProgress<NodeType>
|
||||||
| NoConnection;
|
| NoConnection;
|
||||||
|
|||||||
@@ -14,11 +14,13 @@ export type Handle = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type HandleProps = {
|
export type HandleProps = {
|
||||||
/** Type of the handle
|
/**
|
||||||
|
* Type of the handle
|
||||||
* @example HandleType.Source, HandleType.Target
|
* @example HandleType.Source, HandleType.Target
|
||||||
*/
|
*/
|
||||||
type: HandleType;
|
type: HandleType;
|
||||||
/** Position of the handle
|
/**
|
||||||
|
* Position of the handle
|
||||||
* @example Position.TopLeft, Position.TopRight,
|
* @example Position.TopLeft, Position.TopRight,
|
||||||
* Position.BottomLeft, Position.BottomRight
|
* Position.BottomLeft, Position.BottomRight
|
||||||
*/
|
*/
|
||||||
@@ -29,11 +31,13 @@ export type HandleProps = {
|
|||||||
isConnectableStart?: boolean;
|
isConnectableStart?: boolean;
|
||||||
/** Should you be able to connect to this handle */
|
/** Should you be able to connect to this handle */
|
||||||
isConnectableEnd?: boolean;
|
isConnectableEnd?: boolean;
|
||||||
/** Callback if connection is valid
|
/**
|
||||||
|
* Callback if connection is valid
|
||||||
* @remarks connection becomes an edge if isValidConnection returns true
|
* @remarks connection becomes an edge if isValidConnection returns true
|
||||||
*/
|
*/
|
||||||
isValidConnection?: IsValidConnection;
|
isValidConnection?: IsValidConnection;
|
||||||
/** Id of the handle
|
/**
|
||||||
|
* Id of the handle
|
||||||
* @remarks optional if there is only one handle of this type
|
* @remarks optional if there is only one handle of this type
|
||||||
*/
|
*/
|
||||||
id?: string | null;
|
id?: string | null;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Optional } from '../utils/types';
|
|||||||
/**
|
/**
|
||||||
* Framework independent node data structure.
|
* Framework independent node data structure.
|
||||||
*
|
*
|
||||||
|
* @inline
|
||||||
* @typeParam NodeData - type of the node data
|
* @typeParam NodeData - type of the node data
|
||||||
* @typeParam NodeType - type of the node
|
* @typeParam NodeType - type of the node
|
||||||
*/
|
*/
|
||||||
@@ -13,7 +14,8 @@ export type NodeBase<
|
|||||||
> = {
|
> = {
|
||||||
/** Unique id of a node */
|
/** Unique id of a node */
|
||||||
id: string;
|
id: string;
|
||||||
/** Position of a node on the pane
|
/**
|
||||||
|
* Position of a node on the pane
|
||||||
* @example { x: 0, y: 0 }
|
* @example { x: 0, y: 0 }
|
||||||
*/
|
*/
|
||||||
position: XYPosition;
|
position: XYPosition;
|
||||||
@@ -21,11 +23,13 @@ export type NodeBase<
|
|||||||
data: NodeData;
|
data: NodeData;
|
||||||
/** Type of node defined in nodeTypes */
|
/** Type of node defined in nodeTypes */
|
||||||
type?: NodeType;
|
type?: NodeType;
|
||||||
/** Only relevant for default, source, target nodeType. controls source position
|
/**
|
||||||
|
* Only relevant for default, source, target nodeType. controls source position
|
||||||
* @example 'right', 'left', 'top', 'bottom'
|
* @example 'right', 'left', 'top', 'bottom'
|
||||||
*/
|
*/
|
||||||
sourcePosition?: Position;
|
sourcePosition?: Position;
|
||||||
/** Only relevant for default, source, target nodeType. controls target position
|
/**
|
||||||
|
* Only relevant for default, source, target nodeType. controls target position
|
||||||
* @example 'right', 'left', 'top', 'bottom'
|
* @example 'right', 'left', 'top', 'bottom'
|
||||||
*/
|
*/
|
||||||
targetPosition?: Position;
|
targetPosition?: Position;
|
||||||
@@ -45,13 +49,15 @@ export type NodeBase<
|
|||||||
/** Parent node id, used for creating sub-flows */
|
/** Parent node id, used for creating sub-flows */
|
||||||
parentId?: string;
|
parentId?: string;
|
||||||
zIndex?: number;
|
zIndex?: number;
|
||||||
/** Boundary a node can be moved in
|
/**
|
||||||
|
* Boundary a node can be moved in
|
||||||
* @example 'parent' or [[0, 0], [100, 100]]
|
* @example 'parent' or [[0, 0], [100, 100]]
|
||||||
*/
|
*/
|
||||||
extent?: 'parent' | CoordinateExtent;
|
extent?: 'parent' | CoordinateExtent;
|
||||||
expandParent?: boolean;
|
expandParent?: boolean;
|
||||||
ariaLabel?: string;
|
ariaLabel?: string;
|
||||||
/** Origin of the node relative to it's position
|
/**
|
||||||
|
* Origin of the node relative to it's position
|
||||||
* @example
|
* @example
|
||||||
* [0.5, 0.5] // centers the node
|
* [0.5, 0.5] // centers the node
|
||||||
* [0, 0] // top left
|
* [0, 0] // top left
|
||||||
@@ -73,8 +79,10 @@ export type InternalNodeBase<NodeType extends NodeBase = NodeBase> = NodeType &
|
|||||||
internals: {
|
internals: {
|
||||||
positionAbsolute: XYPosition;
|
positionAbsolute: XYPosition;
|
||||||
z: number;
|
z: number;
|
||||||
/** Holds a reference to the original node object provided by the user.
|
/**
|
||||||
* Used as an optimization to avoid certain operations. */
|
* Holds a reference to the original node object provided by the user.
|
||||||
|
* Used as an optimization to avoid certain operations.
|
||||||
|
*/
|
||||||
userNode: NodeType;
|
userNode: NodeType;
|
||||||
handleBounds?: NodeHandleBounds;
|
handleBounds?: NodeHandleBounds;
|
||||||
bounds?: NodeBounds;
|
bounds?: NodeBounds;
|
||||||
@@ -82,7 +90,7 @@ export type InternalNodeBase<NodeType extends NodeBase = NodeBase> = NodeType &
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The node data structure that gets used for the nodes prop.
|
* The node data structure that gets used for the custom nodes props.
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@@ -134,10 +142,22 @@ export type NodeDragItem = {
|
|||||||
expandParent?: boolean;
|
expandParent?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The origin of a Node determines how it is placed relative to its own coordinates.
|
||||||
|
* `[0, 0]` places it at the top left corner, `[0.5, 0.5]` right in the center and
|
||||||
|
* `[1, 1]` at the bottom right of its position.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export type NodeOrigin = [number, number];
|
export type NodeOrigin = [number, number];
|
||||||
|
|
||||||
export type OnSelectionDrag = (event: MouseEvent, nodes: NodeBase[]) => void;
|
export type OnSelectionDrag = (event: MouseEvent, nodes: NodeBase[]) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type for the handles of a node
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export type NodeHandle = Omit<Optional<Handle, 'width' | 'height'>, 'nodeId'>;
|
export type NodeHandle = Omit<Optional<Handle, 'width' | 'height'>, 'nodeId'>;
|
||||||
|
|
||||||
export type Align = 'center' | 'start' | 'end';
|
export type Align = 'center' | 'start' | 'end';
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* While [`PanelPosition`](/api-reference/types/panel-position) can be used to place a
|
||||||
|
* component in the corners of a container, the `Position` enum is less precise and used
|
||||||
|
* primarily in relation to edges and handles.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export enum Position {
|
export enum Position {
|
||||||
Left = 'left',
|
Left = 'left',
|
||||||
Top = 'top',
|
Top = 'top',
|
||||||
@@ -12,6 +19,11 @@ export const oppositePosition = {
|
|||||||
[Position.Bottom]: Position.Top,
|
[Position.Bottom]: Position.Top,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All positions are stored in an object with x and y coordinates.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export type XYPosition = {
|
export type XYPosition = {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
@@ -33,4 +45,14 @@ export type Box = XYPosition & {
|
|||||||
|
|
||||||
export type Transform = [number, number, number];
|
export type Transform = [number, number, number];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A coordinate extent represents two points in a coordinate system: one in the top
|
||||||
|
* left corner and one in the bottom right corner. It is used to represent the
|
||||||
|
* bounds of nodes in the flow or the bounds of the viewport.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* @remarks Props that expect a `CoordinateExtent` usually default to `[[-∞, -∞], [+∞, +∞]]`
|
||||||
|
* to represent an unbounded extent.
|
||||||
|
*/
|
||||||
export type CoordinateExtent = [[number, number], [number, number]];
|
export type CoordinateExtent = [[number, number], [number, number]];
|
||||||
|
|||||||
@@ -61,9 +61,11 @@ export const getEventPosition = (event: MouseEvent | TouchEvent, bounds?: DOMRec
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// The handle bounds are calculated relative to the node element.
|
/*
|
||||||
// We store them in the internals object of the node in order to avoid
|
* The handle bounds are calculated relative to the node element.
|
||||||
// unnecessary recalculations.
|
* We store them in the internals object of the node in order to avoid
|
||||||
|
* unnecessary recalculations.
|
||||||
|
*/
|
||||||
export const getHandleBounds = (
|
export const getHandleBounds = (
|
||||||
type: 'source' | 'target',
|
type: 'source' | 'target',
|
||||||
nodeElement: HTMLDivElement,
|
nodeElement: HTMLDivElement,
|
||||||
|
|||||||
@@ -38,8 +38,10 @@ export function getBezierEdgeCenter({
|
|||||||
targetControlX: number;
|
targetControlX: number;
|
||||||
targetControlY: number;
|
targetControlY: number;
|
||||||
}): [number, number, number, number] {
|
}): [number, number, number, number] {
|
||||||
// cubic bezier t=0.5 mid point, not the actual mid point, but easy to calculate
|
/*
|
||||||
// https://stackoverflow.com/questions/67516101/how-to-find-distance-mid-point-of-bezier-curve
|
* cubic bezier t=0.5 mid point, not the actual mid point, but easy to calculate
|
||||||
|
* https://stackoverflow.com/questions/67516101/how-to-find-distance-mid-point-of-bezier-curve
|
||||||
|
*/
|
||||||
const centerX = sourceX * 0.125 + sourceControlX * 0.375 + targetControlX * 0.375 + targetX * 0.125;
|
const centerX = sourceX * 0.125 + sourceControlX * 0.375 + targetControlX * 0.375 + targetX * 0.125;
|
||||||
const centerY = sourceY * 0.125 + sourceControlY * 0.375 + targetControlY * 0.375 + targetY * 0.125;
|
const centerY = sourceY * 0.125 + sourceControlY * 0.375 + targetControlY * 0.375 + targetY * 0.125;
|
||||||
const offsetX = Math.abs(centerX - sourceX);
|
const offsetX = Math.abs(centerX - sourceX);
|
||||||
@@ -70,7 +72,9 @@ function getControlWithCurvature({ pos, x1, y1, x2, y2, c }: GetControlWithCurva
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a bezier path from source to target handle
|
* The `getBezierPath` util returns everything you need to render a bezier edge
|
||||||
|
*between two nodes.
|
||||||
|
* @public
|
||||||
* @param params.sourceX - The x position of the source handle
|
* @param params.sourceX - The x position of the source handle
|
||||||
* @param params.sourceY - The y 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.sourcePosition - The position of the source handle (default: Position.Bottom)
|
||||||
@@ -80,17 +84,22 @@ function getControlWithCurvature({ pos, x1, y1, x2, y2, c }: GetControlWithCurva
|
|||||||
* @param params.curvature - The curvature of the bezier edge
|
* @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
|
* @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
|
* @example
|
||||||
|
* ```js
|
||||||
* const source = { x: 0, y: 20 };
|
* const source = { x: 0, y: 20 };
|
||||||
const target = { x: 150, y: 100 };
|
* const target = { x: 150, y: 100 };
|
||||||
|
*
|
||||||
const [path, labelX, labelY, offsetX, offsetY] = getBezierPath({
|
* const [path, labelX, labelY, offsetX, offsetY] = getBezierPath({
|
||||||
sourceX: source.x,
|
* sourceX: source.x,
|
||||||
sourceY: source.y,
|
* sourceY: source.y,
|
||||||
sourcePosition: Position.Right,
|
* sourcePosition: Position.Right,
|
||||||
targetX: target.x,
|
* targetX: target.x,
|
||||||
targetY: target.y,
|
* targetY: target.y,
|
||||||
targetPosition: Position.Left,
|
* targetPosition: Position.Left,
|
||||||
});
|
*});
|
||||||
|
*```
|
||||||
|
*
|
||||||
|
* @remarks This function returns a tuple (aka a fixed-size array) to make it easier to
|
||||||
|
*work with multiple edge paths at once.
|
||||||
*/
|
*/
|
||||||
export function getBezierPath({
|
export function getBezierPath({
|
||||||
sourceX,
|
sourceX,
|
||||||
|
|||||||
@@ -90,12 +90,16 @@ const connectionExists = (edge: EdgeBase, edges: EdgeBase[]) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This util is a convenience function to add a new Edge to an array of edges
|
* This util is a convenience function to add a new Edge to an array of edges. It also performs some validation to make sure you don't add an invalid edge or duplicate an existing one.
|
||||||
* @remarks It also performs some validation to make sure you don't add an invalid edge or duplicate an existing one.
|
|
||||||
* @public
|
* @public
|
||||||
* @param edgeParams - Either an Edge or a Connection you want to add
|
* @param edgeParams - Either an Edge or a Connection you want to add
|
||||||
* @param edges - The array of all current edges
|
* @param edges - The array of all current edges
|
||||||
* @returns A new array of edges with the new edge added
|
* @returns A new array of edges with the new edge added
|
||||||
|
*
|
||||||
|
* @remarks If an edge with the same `target` and `source` already exists (and the same
|
||||||
|
*`targetHandle` and `sourceHandle` if those are set), then this util won't add
|
||||||
|
*a new edge even if the `id` property is different.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
export const addEdge = <EdgeType extends EdgeBase>(
|
export const addEdge = <EdgeType extends EdgeBase>(
|
||||||
edgeParams: EdgeType | Connection,
|
edgeParams: EdgeType | Connection,
|
||||||
@@ -137,12 +141,21 @@ export type ReconnectEdgeOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A handy utility to reconnect an existing edge with new properties
|
* A handy utility to update an existing [`Edge`](/api-reference/types/edge) with new properties.
|
||||||
|
*This searches your edge array for an edge with a matching `id` and updates its
|
||||||
|
*properties with the connection you provide.
|
||||||
|
* @public
|
||||||
* @param oldEdge - The edge you want to update
|
* @param oldEdge - The edge you want to update
|
||||||
* @param newConnection - The new connection you want to update the edge with
|
* @param newConnection - The new connection you want to update the edge with
|
||||||
* @param edges - The array of all current edges
|
* @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
|
* @param options.shouldReplaceId - should the id of the old edge be replaced with the new connection id
|
||||||
* @returns the updated edges array
|
* @returns the updated edges array
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
*const onReconnect = useCallback(
|
||||||
|
* (oldEdge: Edge, newConnection: Connection) => setEdges((els) => reconnectEdge(oldEdge, newConnection, els)),[]);
|
||||||
|
*```
|
||||||
*/
|
*/
|
||||||
export const reconnectEdge = <EdgeType extends EdgeBase>(
|
export const reconnectEdge = <EdgeType extends EdgeBase>(
|
||||||
oldEdge: EdgeType,
|
oldEdge: EdgeType,
|
||||||
|
|||||||
@@ -38,8 +38,10 @@ const getDirection = ({
|
|||||||
|
|
||||||
const distance = (a: XYPosition, b: XYPosition) => Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2));
|
const distance = (a: XYPosition, b: XYPosition) => Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2));
|
||||||
|
|
||||||
// ith this function we try to mimic a orthogonal edge routing behaviour
|
/*
|
||||||
// It's not as good as a real orthogonal edge routing but it's faster and good enough as a default for step and smooth step edges
|
* ith this function we try to mimic a orthogonal edge routing behaviour
|
||||||
|
* It's not as good as a real orthogonal edge routing but it's faster and good enough as a default for step and smooth step edges
|
||||||
|
*/
|
||||||
function getPoints({
|
function getPoints({
|
||||||
source,
|
source,
|
||||||
sourcePosition = Position.Bottom,
|
sourcePosition = Position.Bottom,
|
||||||
@@ -83,16 +85,20 @@ function getPoints({
|
|||||||
if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) {
|
if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) {
|
||||||
centerX = center.x ?? defaultCenterX;
|
centerX = center.x ?? defaultCenterX;
|
||||||
centerY = center.y ?? defaultCenterY;
|
centerY = center.y ?? defaultCenterY;
|
||||||
// --->
|
/*
|
||||||
// |
|
* --->
|
||||||
// >---
|
* |
|
||||||
|
* >---
|
||||||
|
*/
|
||||||
const verticalSplit: XYPosition[] = [
|
const verticalSplit: XYPosition[] = [
|
||||||
{ x: centerX, y: sourceGapped.y },
|
{ x: centerX, y: sourceGapped.y },
|
||||||
{ x: centerX, y: targetGapped.y },
|
{ x: centerX, y: targetGapped.y },
|
||||||
];
|
];
|
||||||
// |
|
/*
|
||||||
// ---
|
* |
|
||||||
// |
|
* ---
|
||||||
|
* |
|
||||||
|
*/
|
||||||
const horizontalSplit: XYPosition[] = [
|
const horizontalSplit: XYPosition[] = [
|
||||||
{ x: sourceGapped.x, y: centerY },
|
{ x: sourceGapped.x, y: centerY },
|
||||||
{ x: targetGapped.x, y: centerY },
|
{ x: targetGapped.x, y: centerY },
|
||||||
@@ -191,7 +197,10 @@ function getBend(a: XYPosition, b: XYPosition, c: XYPosition, size: number): str
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a smooth step path from source to target handle
|
* The `getSmoothStepPath` util returns everything you need to render a stepped path
|
||||||
|
*between two nodes. The `borderRadius` property can be used to choose how rounded
|
||||||
|
*the corners of those steps are.
|
||||||
|
* @public
|
||||||
* @param params.sourceX - The x position of the source handle
|
* @param params.sourceX - The x position of the source handle
|
||||||
* @param params.sourceY - The y 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.sourcePosition - The position of the source handle (default: Position.Bottom)
|
||||||
@@ -200,17 +209,20 @@ function getBend(a: XYPosition, b: XYPosition, c: XYPosition, size: number): str
|
|||||||
* @param params.targetPosition - The position of the target handle (default: Position.Top)
|
* @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
|
* @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
|
* @example
|
||||||
|
* ```js
|
||||||
* const source = { x: 0, y: 20 };
|
* const source = { x: 0, y: 20 };
|
||||||
const target = { x: 150, y: 100 };
|
* const target = { x: 150, y: 100 };
|
||||||
|
*
|
||||||
const [path, labelX, labelY, offsetX, offsetY] = getSmoothStepPath({
|
* const [path, labelX, labelY, offsetX, offsetY] = getSmoothStepPath({
|
||||||
sourceX: source.x,
|
* sourceX: source.x,
|
||||||
sourceY: source.y,
|
* sourceY: source.y,
|
||||||
sourcePosition: Position.Right,
|
* sourcePosition: Position.Right,
|
||||||
targetX: target.x,
|
* targetX: target.x,
|
||||||
targetY: target.y,
|
* targetY: target.y,
|
||||||
targetPosition: Position.Left,
|
* targetPosition: Position.Left,
|
||||||
});
|
* });
|
||||||
|
* ```
|
||||||
|
* @remarks This function returns a tuple (aka a fixed-size array) to make it easier to work with multiple edge paths at once.
|
||||||
*/
|
*/
|
||||||
export function getSmoothStepPath({
|
export function getSmoothStepPath({
|
||||||
sourceX,
|
sourceX,
|
||||||
|
|||||||
@@ -8,24 +8,28 @@ export type GetStraightPathParams = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a straight path from source to target handle
|
* Calculates the straight line path between two points.
|
||||||
|
* @public
|
||||||
* @param params.sourceX - The x position of the source handle
|
* @param params.sourceX - The x position of the source handle
|
||||||
* @param params.sourceY - The y 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.targetX - The x position of the target handle
|
||||||
* @param params.targetY - The y 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
|
* @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
|
* @example
|
||||||
|
* ```js
|
||||||
* const source = { x: 0, y: 20 };
|
* const source = { x: 0, y: 20 };
|
||||||
const target = { x: 150, y: 100 };
|
* const target = { x: 150, y: 100 };
|
||||||
|
*
|
||||||
const [path, labelX, labelY, offsetX, offsetY] = getStraightPath({
|
* const [path, labelX, labelY, offsetX, offsetY] = getStraightPath({
|
||||||
sourceX: source.x,
|
* sourceX: source.x,
|
||||||
sourceY: source.y,
|
* sourceY: source.y,
|
||||||
sourcePosition: Position.Right,
|
* sourcePosition: Position.Right,
|
||||||
targetX: target.x,
|
* targetX: target.x,
|
||||||
targetY: target.y,
|
* targetY: target.y,
|
||||||
targetPosition: Position.Left,
|
* targetPosition: Position.Left,
|
||||||
});
|
* });
|
||||||
|
* ```
|
||||||
|
* @remarks This function returns a tuple (aka a fixed-size array) to make it easier to work with multiple edge paths at once.
|
||||||
*/
|
*/
|
||||||
export function getStraightPath({
|
export function getStraightPath({
|
||||||
sourceX,
|
sourceX,
|
||||||
|
|||||||
@@ -186,8 +186,8 @@ export const rendererPointToPoint = ({ x, y }: XYPosition, [tx, ty, tScale]: Tra
|
|||||||
* @returns A transforned {@link Viewport} that encloses the given bounds which you can pass to e.g. {@link setViewport}
|
* @returns A transforned {@link Viewport} that encloses the given bounds which you can pass to e.g. {@link setViewport}
|
||||||
* @example
|
* @example
|
||||||
* const { x, y, zoom } = getViewportForBounds(
|
* const { x, y, zoom } = getViewportForBounds(
|
||||||
{ x: 0, y: 0, width: 100, height: 100},
|
*{ x: 0, y: 0, width: 100, height: 100},
|
||||||
1200, 800, 0.5, 2);
|
*1200, 800, 0.5, 2);
|
||||||
*/
|
*/
|
||||||
export const getViewportForBounds = (
|
export const getViewportForBounds = (
|
||||||
bounds: Rect,
|
bounds: Rect,
|
||||||
|
|||||||
@@ -54,12 +54,27 @@ export const isInternalNodeBase = <NodeType extends InternalNodeBase = InternalN
|
|||||||
): element is NodeType => 'id' in element && 'internals' in element && !('source' in element) && !('target' in element);
|
): element is NodeType => 'id' in element && 'internals' in element && !('source' in element) && !('target' in element);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pass in a node, and get connected nodes where edge.source === node.id
|
* This util is used to tell you what nodes, if any, are connected to the given node
|
||||||
|
* as the _target_ of an edge.
|
||||||
* @public
|
* @public
|
||||||
* @param node - The node to get the connected nodes from
|
* @param node - The node to get the connected nodes from
|
||||||
* @param nodes - The array of all nodes
|
* @param nodes - The array of all nodes
|
||||||
* @param edges - The array of all edges
|
* @param edges - The array of all edges
|
||||||
* @returns An array of nodes that are connected over eges where the source is the given node
|
* @returns An array of nodes that are connected over eges where the source is the given node
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
*import { getOutgoers } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*const nodes = [];
|
||||||
|
*const edges = [];
|
||||||
|
*
|
||||||
|
*const outgoers = getOutgoers(
|
||||||
|
* { id: '1', position: { x: 0, y: 0 }, data: { label: 'node' } },
|
||||||
|
* nodes,
|
||||||
|
* edges,
|
||||||
|
*);
|
||||||
|
*```
|
||||||
*/
|
*/
|
||||||
export const getOutgoers = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
|
export const getOutgoers = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
|
||||||
node: NodeType | { id: string },
|
node: NodeType | { id: string },
|
||||||
@@ -81,12 +96,27 @@ export const getOutgoers = <NodeType extends NodeBase = NodeBase, EdgeType exten
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pass in a node, and get connected nodes where edge.target === node.id
|
* This util is used to tell you what nodes, if any, are connected to the given node
|
||||||
|
* as the _source_ of an edge.
|
||||||
* @public
|
* @public
|
||||||
* @param node - The node to get the connected nodes from
|
* @param node - The node to get the connected nodes from
|
||||||
* @param nodes - The array of all nodes
|
* @param nodes - The array of all nodes
|
||||||
* @param edges - The array of all edges
|
* @param edges - The array of all edges
|
||||||
* @returns An array of nodes that are connected over eges where the target is the given node
|
* @returns An array of nodes that are connected over eges where the target is the given node
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
*import { getIncomers } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*const nodes = [];
|
||||||
|
*const edges = [];
|
||||||
|
*
|
||||||
|
*const incomers = getIncomers(
|
||||||
|
* { id: '1', position: { x: 0, y: 0 }, data: { label: 'node' } },
|
||||||
|
* nodes,
|
||||||
|
* edges,
|
||||||
|
*);
|
||||||
|
*```
|
||||||
*/
|
*/
|
||||||
export const getIncomers = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
|
export const getIncomers = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
|
||||||
node: NodeType | { id: string },
|
node: NodeType | { id: string },
|
||||||
@@ -124,12 +154,40 @@ export type GetNodesBoundsParams<NodeType extends NodeBase = NodeBase> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal function for determining a bounding box that contains all given nodes in an array.
|
* Returns the bounding box that contains all the given nodes in an array. This can
|
||||||
|
* be useful when combined with [`getViewportForBounds`](/api-reference/utils/get-viewport-for-bounds)
|
||||||
|
* to calculate the correct transform to fit the given nodes in a viewport.
|
||||||
* @public
|
* @public
|
||||||
* @remarks Useful when combined with {@link getViewportForBounds} to calculate the correct transform to fit the given nodes in a viewport.
|
* @remarks Useful when combined with {@link getViewportForBounds} to calculate the correct transform to fit the given nodes in a viewport.
|
||||||
* @param nodes - Nodes to calculate the bounds for
|
* @param nodes - Nodes to calculate the bounds for
|
||||||
* @param params.nodeOrigin - Origin of the nodes: [0, 0] - top left, [0.5, 0.5] - center
|
* @param params.nodeOrigin - Origin of the nodes: [0, 0] - top left, [0.5, 0.5] - center
|
||||||
* @returns Bounding box enclosing all nodes
|
* @returns Bounding box enclosing all nodes
|
||||||
|
*
|
||||||
|
* @remarks This function was previously called `getRectOfNodes`
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
*import { getNodesBounds } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*const nodes = [
|
||||||
|
* {
|
||||||
|
* id: 'a',
|
||||||
|
* position: { x: 0, y: 0 },
|
||||||
|
* data: { label: 'a' },
|
||||||
|
* width: 50,
|
||||||
|
* height: 25,
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* id: 'b',
|
||||||
|
* position: { x: 100, y: 100 },
|
||||||
|
* data: { label: 'b' },
|
||||||
|
* width: 50,
|
||||||
|
* height: 25,
|
||||||
|
* },
|
||||||
|
*];
|
||||||
|
*
|
||||||
|
*const bounds = getNodesBounds(nodes);
|
||||||
|
*```
|
||||||
*/
|
*/
|
||||||
export const getNodesBounds = <NodeType extends NodeBase = NodeBase>(
|
export const getNodesBounds = <NodeType extends NodeBase = NodeBase>(
|
||||||
nodes: (NodeType | InternalNodeBase<NodeType> | string)[],
|
nodes: (NodeType | InternalNodeBase<NodeType> | string)[],
|
||||||
@@ -238,10 +296,30 @@ export const getNodesInside = <NodeType extends NodeBase = NodeBase>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all connecting edges for a given set of nodes
|
* This utility filters an array of edges, keeping only those where either the source or target
|
||||||
|
* node is present in the given array of nodes.
|
||||||
|
* @public
|
||||||
* @param nodes - Nodes you want to get the connected edges for
|
* @param nodes - Nodes you want to get the connected edges for
|
||||||
* @param edges - All edges
|
* @param edges - All edges
|
||||||
* @returns Array of edges that connect any of the given nodes with each other
|
* @returns Array of edges that connect any of the given nodes with each other
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
*import { getConnectedEdges } from '@xyflow/react';
|
||||||
|
*
|
||||||
|
*const nodes = [
|
||||||
|
* { id: 'a', position: { x: 0, y: 0 } },
|
||||||
|
* { id: 'b', position: { x: 100, y: 0 } },
|
||||||
|
*];
|
||||||
|
*
|
||||||
|
*const edges = [
|
||||||
|
* { id: 'a->c', source: 'a', target: 'c' },
|
||||||
|
* { id: 'c->d', source: 'c', target: 'd' },
|
||||||
|
*];
|
||||||
|
*
|
||||||
|
*const connectedEdges = getConnectedEdges(nodes, edges);
|
||||||
|
* // => [{ id: 'a->c', source: 'a', target: 'c' }]
|
||||||
|
*```
|
||||||
*/
|
*/
|
||||||
export const getConnectedEdges = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
|
export const getConnectedEdges = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
|
||||||
nodes: NodeType[],
|
nodes: NodeType[],
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ export function getNodeToolbarTransform(
|
|||||||
alignmentOffset = 1;
|
alignmentOffset = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// position === Position.Top
|
/*
|
||||||
// we set the x any y position of the toolbar based on the nodes position
|
* position === Position.Top
|
||||||
|
* we set the x any y position of the toolbar based on the nodes position
|
||||||
|
*/
|
||||||
let pos = [
|
let pos = [
|
||||||
(nodeRect.x + nodeRect.width * alignmentOffset) * viewport.zoom + viewport.x,
|
(nodeRect.x + nodeRect.width * alignmentOffset) * viewport.zoom + viewport.x,
|
||||||
nodeRect.y * viewport.zoom + viewport.y - offset,
|
nodeRect.y * viewport.zoom + viewport.y - offset,
|
||||||
|
|||||||
@@ -276,8 +276,10 @@ export function handleExpandParent(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// We move all child nodes in the oppsite direction
|
/*
|
||||||
// so the x,y changes of the parent do not move the children
|
* We move all child nodes in the oppsite direction
|
||||||
|
* so the x,y changes of the parent do not move the children
|
||||||
|
*/
|
||||||
parentLookup.get(parentId)?.forEach((childNode) => {
|
parentLookup.get(parentId)?.forEach((childNode) => {
|
||||||
if (!children.some((child) => child.id === childNode.id)) {
|
if (!children.some((child) => child.id === childNode.id)) {
|
||||||
changes.push({
|
changes.push({
|
||||||
@@ -472,9 +474,11 @@ function addConnectionToLookup(
|
|||||||
nodeId: string,
|
nodeId: string,
|
||||||
handleId: string | null
|
handleId: string | null
|
||||||
) {
|
) {
|
||||||
// We add the connection to the connectionLookup at the following keys
|
/*
|
||||||
// 1. nodeId, 2. nodeId-type, 3. nodeId-type-handleId
|
* We add the connection to the connectionLookup at the following keys
|
||||||
// If the key already exists, we add the connection to the existing map
|
* 1. nodeId, 2. nodeId-type, 3. nodeId-type-handleId
|
||||||
|
* If the key already exists, we add the connection to the existing map
|
||||||
|
*/
|
||||||
let key = nodeId;
|
let key = nodeId;
|
||||||
const nodeMap = connectionLookup.get(key) || new Map();
|
const nodeMap = connectionLookup.get(key) || new Map();
|
||||||
connectionLookup.set(key, nodeMap.set(connectionKey, connection));
|
connectionLookup.set(key, nodeMap.set(connectionKey, connection));
|
||||||
|
|||||||
@@ -140,8 +140,10 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
|||||||
|
|
||||||
for (const [id, dragItem] of dragItems) {
|
for (const [id, dragItem] of dragItems) {
|
||||||
if (!nodeLookup.has(id)) {
|
if (!nodeLookup.has(id)) {
|
||||||
// if the node is not in the nodeLookup anymore, it was probably deleted while dragging
|
/*
|
||||||
// and we don't need to update it anymore
|
* if the node is not in the nodeLookup anymore, it was probably deleted while dragging
|
||||||
|
* and we don't need to update it anymore
|
||||||
|
*/
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,8 +152,10 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
|||||||
nextPosition = snapPosition(nextPosition, snapGrid);
|
nextPosition = snapPosition(nextPosition, snapGrid);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there is selection with multiple nodes and a node extent is set, we need to adjust the node extent for each node
|
/*
|
||||||
// based on its position so that the node stays at it's position relative to the selection.
|
* if there is selection with multiple nodes and a node extent is set, we need to adjust the node extent for each node
|
||||||
|
* based on its position so that the node stays at it's position relative to the selection.
|
||||||
|
*/
|
||||||
let adjustedNodeExtent: CoordinateExtent = [
|
let adjustedNodeExtent: CoordinateExtent = [
|
||||||
[nodeExtent[0][0], nodeExtent[0][1]],
|
[nodeExtent[0][0], nodeExtent[0][1]],
|
||||||
[nodeExtent[1][0], nodeExtent[1][1]],
|
[nodeExtent[1][0], nodeExtent[1][1]],
|
||||||
|
|||||||
@@ -74,9 +74,11 @@ export function getDragItems<NodeType extends NodeBase>(
|
|||||||
return dragItems;
|
return dragItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns two params:
|
/*
|
||||||
// 1. the dragged node (or the first of the list, if we are dragging a node selection)
|
* returns two params:
|
||||||
// 2. array of selected nodes (for multi selections)
|
* 1. the dragged node (or the first of the list, if we are dragging a node selection)
|
||||||
|
* 2. array of selected nodes (for multi selections)
|
||||||
|
*/
|
||||||
export function getEventHandlerParams<NodeType extends InternalNodeBase>({
|
export function getEventHandlerParams<NodeType extends InternalNodeBase>({
|
||||||
nodeId,
|
nodeId,
|
||||||
dragItems,
|
dragItems,
|
||||||
@@ -112,10 +114,10 @@ export function getEventHandlerParams<NodeType extends InternalNodeBase>({
|
|||||||
!node
|
!node
|
||||||
? nodesFromDragItems[0]
|
? nodesFromDragItems[0]
|
||||||
: {
|
: {
|
||||||
...node,
|
...node,
|
||||||
position: dragItems.get(nodeId)?.position || node.position,
|
position: dragItems.get(nodeId)?.position || node.position,
|
||||||
dragging,
|
dragging,
|
||||||
},
|
},
|
||||||
nodesFromDragItems,
|
nodesFromDragItems,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,8 +165,10 @@ function onPointerDown(
|
|||||||
toNode: result.toHandle ? nodeLookup.get(result.toHandle.nodeId)! : null,
|
toNode: result.toHandle ? nodeLookup.get(result.toHandle.nodeId)! : null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// we don't want to trigger an update when the connection
|
/*
|
||||||
// is snapped to the same handle as before
|
* we don't want to trigger an update when the connection
|
||||||
|
* is snapped to the same handle as before
|
||||||
|
*/
|
||||||
if (
|
if (
|
||||||
isValid &&
|
isValid &&
|
||||||
closestHandle &&
|
closestHandle &&
|
||||||
@@ -190,8 +192,10 @@ function onPointerDown(
|
|||||||
onConnect?.(connection);
|
onConnect?.(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// it's important to get a fresh reference from the store here
|
/*
|
||||||
// in order to get the latest state of onConnectEnd
|
* it's important to get a fresh reference from the store here
|
||||||
|
* in order to get the latest state of onConnectEnd
|
||||||
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { inProgress, ...connectionState } = previousConnection;
|
const { inProgress, ...connectionState } = previousConnection;
|
||||||
const finalConnectionState = {
|
const finalConnectionState = {
|
||||||
@@ -248,8 +252,10 @@ function isValidHandle(
|
|||||||
|
|
||||||
const { x, y } = getEventPosition(event);
|
const { x, y } = getEventPosition(event);
|
||||||
const handleBelow = doc.elementFromPoint(x, y);
|
const handleBelow = doc.elementFromPoint(x, y);
|
||||||
// we always want to prioritize the handle below the mouse cursor over the closest distance handle,
|
/*
|
||||||
// because it could be that the center of another handle is closer to the mouse pointer than the handle below the cursor
|
* we always want to prioritize the handle below the mouse cursor over the closest distance handle,
|
||||||
|
* because it could be that the center of another handle is closer to the mouse pointer than the handle below the cursor
|
||||||
|
*/
|
||||||
const handleToCheck = handleBelow?.classList.contains(`${lib}-flow__handle`) ? handleBelow : handleDomNode;
|
const handleToCheck = handleBelow?.classList.contains(`${lib}-flow__handle`) ? handleBelow : handleDomNode;
|
||||||
|
|
||||||
const result: Result = {
|
const result: Result = {
|
||||||
|
|||||||
@@ -19,8 +19,10 @@ function getNodesWithinDistance(position: XYPosition, nodeLookup: NodeLookup, di
|
|||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this distance is used for the area around the user pointer
|
/*
|
||||||
// while doing a connection for finding the closest nodes
|
* this distance is used for the area around the user pointer
|
||||||
|
* while doing a connection for finding the closest nodes
|
||||||
|
*/
|
||||||
const ADDITIONAL_DISTANCE = 250;
|
const ADDITIONAL_DISTANCE = 250;
|
||||||
|
|
||||||
export function getClosestHandle(
|
export function getClosestHandle(
|
||||||
|
|||||||
@@ -114,22 +114,22 @@ export function XYPanZoom({
|
|||||||
|
|
||||||
const wheelHandler = isPanOnScroll
|
const wheelHandler = isPanOnScroll
|
||||||
? createPanOnScrollHandler({
|
? createPanOnScrollHandler({
|
||||||
zoomPanValues,
|
zoomPanValues,
|
||||||
noWheelClassName,
|
noWheelClassName,
|
||||||
d3Selection,
|
d3Selection,
|
||||||
d3Zoom: d3ZoomInstance,
|
d3Zoom: d3ZoomInstance,
|
||||||
panOnScrollMode,
|
panOnScrollMode,
|
||||||
panOnScrollSpeed,
|
panOnScrollSpeed,
|
||||||
zoomOnPinch,
|
zoomOnPinch,
|
||||||
onPanZoomStart,
|
onPanZoomStart,
|
||||||
onPanZoom,
|
onPanZoom,
|
||||||
onPanZoomEnd,
|
onPanZoomEnd,
|
||||||
})
|
})
|
||||||
: createZoomOnScrollHandler({
|
: createZoomOnScrollHandler({
|
||||||
noWheelClassName,
|
noWheelClassName,
|
||||||
preventScrolling,
|
preventScrolling,
|
||||||
d3ZoomHandler,
|
d3ZoomHandler,
|
||||||
});
|
});
|
||||||
|
|
||||||
d3Selection.on('wheel.zoom', wheelHandler, { passive: false });
|
d3Selection.on('wheel.zoom', wheelHandler, { passive: false });
|
||||||
|
|
||||||
@@ -178,9 +178,11 @@ export function XYPanZoom({
|
|||||||
});
|
});
|
||||||
d3ZoomInstance.filter(filter);
|
d3ZoomInstance.filter(filter);
|
||||||
|
|
||||||
// We cannot add zoomOnDoubleClick to the filter above because
|
/*
|
||||||
// double tapping on touch screens circumvents the filter and
|
* We cannot add zoomOnDoubleClick to the filter above because
|
||||||
// dblclick.zoom is fired on the selection directly
|
* double tapping on touch screens circumvents the filter and
|
||||||
|
* dblclick.zoom is fired on the selection directly
|
||||||
|
*/
|
||||||
if (zoomOnDoubleClick) {
|
if (zoomOnDoubleClick) {
|
||||||
d3Selection.on('dblclick.zoom', d3DblClickZoomHandler);
|
d3Selection.on('dblclick.zoom', d3DblClickZoomHandler);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -90,8 +90,10 @@ export function createPanOnScrollHandler({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// increase scroll speed in firefox
|
/*
|
||||||
// firefox: deltaMode === 1; chrome: deltaMode === 0
|
* increase scroll speed in firefox
|
||||||
|
* firefox: deltaMode === 1; chrome: deltaMode === 0
|
||||||
|
*/
|
||||||
const deltaNormalize = event.deltaMode === 1 ? 20 : 1;
|
const deltaNormalize = event.deltaMode === 1 ? 20 : 1;
|
||||||
let deltaX = panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize;
|
let deltaX = panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize;
|
||||||
let deltaY = panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize;
|
let deltaY = panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize;
|
||||||
@@ -114,9 +116,11 @@ export function createPanOnScrollHandler({
|
|||||||
|
|
||||||
clearTimeout(zoomPanValues.panScrollTimeout);
|
clearTimeout(zoomPanValues.panScrollTimeout);
|
||||||
|
|
||||||
// for pan on scroll we need to handle the event calls on our own
|
/*
|
||||||
// we can't use the start, zoom and end events from d3-zoom
|
* for pan on scroll we need to handle the event calls on our own
|
||||||
// because start and move gets called on every scroll event and not once at the beginning
|
* we can't use the start, zoom and end events from d3-zoom
|
||||||
|
* because start and move gets called on every scroll event and not once at the beginning
|
||||||
|
*/
|
||||||
if (!zoomPanValues.isPanScrolling) {
|
if (!zoomPanValues.isPanScrolling) {
|
||||||
zoomPanValues.isPanScrolling = true;
|
zoomPanValues.isPanScrolling = true;
|
||||||
|
|
||||||
|
|||||||
@@ -154,8 +154,10 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X
|
|||||||
parentExtent = parentNode && node.extent === 'parent' ? nodeToParentExtent(parentNode) : undefined;
|
parentExtent = parentNode && node.extent === 'parent' ? nodeToParentExtent(parentNode) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect all child nodes to correct their relative positions when top/left changes
|
/*
|
||||||
// Determine largest minimal extent the parent node is allowed to resize to
|
* Collect all child nodes to correct their relative positions when top/left changes
|
||||||
|
* Determine largest minimal extent the parent node is allowed to resize to
|
||||||
|
*/
|
||||||
childNodes = [];
|
childNodes = [];
|
||||||
childExtent = undefined;
|
childExtent = undefined;
|
||||||
|
|
||||||
@@ -230,8 +232,10 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X
|
|||||||
prevValues.x = change.x;
|
prevValues.x = change.x;
|
||||||
prevValues.y = change.y;
|
prevValues.y = change.y;
|
||||||
|
|
||||||
// when top/left changes, correct the relative positions of child nodes
|
/*
|
||||||
// so that they stay in the same position
|
* when top/left changes, correct the relative positions of child nodes
|
||||||
|
* so that they stay in the same position
|
||||||
|
*/
|
||||||
if (childNodes.length > 0) {
|
if (childNodes.length > 0) {
|
||||||
const xChange = x - prevX;
|
const xChange = x - prevX;
|
||||||
const yChange = y - prevY;
|
const yChange = y - prevY;
|
||||||
|
|||||||
@@ -11,10 +11,25 @@ export type ResizeParamsWithDirection = ResizeParams & {
|
|||||||
direction: number[];
|
direction: number[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to determine the control line position of the NodeResizer
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export type ControlLinePosition = 'top' | 'bottom' | 'left' | 'right';
|
export type ControlLinePosition = 'top' | 'bottom' | 'left' | 'right';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to determine the control position of the NodeResizer
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export type ControlPosition = ControlLinePosition | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
export type ControlPosition = ControlLinePosition | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to determine the variant of the resize control
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export enum ResizeControlVariant {
|
export enum ResizeControlVariant {
|
||||||
Line = 'line',
|
Line = 'line',
|
||||||
Handle = 'handle',
|
Handle = 'handle',
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint-config-prettier": "^10.0.1",
|
|
||||||
"eslint-config-turbo": "^2.4.0",
|
|
||||||
"eslint-plugin-react": "^7.37.4",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^8.23.0",
|
"@typescript-eslint/eslint-plugin": "^8.23.0",
|
||||||
"@typescript-eslint/parser": "^8.23.0",
|
"@typescript-eslint/parser": "^8.23.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1"
|
"eslint-config-prettier": "^10.0.1",
|
||||||
|
"eslint-config-turbo": "^2.4.0",
|
||||||
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"eslint-plugin-react": "^7.37.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user