@@ -90,4 +90,57 @@ function BackgroundComponent({
|
||||
|
||||
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);
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
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 {
|
||||
Lines = 'lines',
|
||||
Dots = 'dots',
|
||||
Cross = 'cross',
|
||||
}
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type BackgroundProps = {
|
||||
id?: string;
|
||||
/** Color of the pattern */
|
||||
@@ -24,7 +33,8 @@ export type BackgroundProps = {
|
||||
offset?: number | [number, number];
|
||||
/** Line width of the Line pattern */
|
||||
lineWidth?: number;
|
||||
/** Variant of the pattern
|
||||
/**
|
||||
* Variant of the pattern
|
||||
* @example BackgroundVariant.Lines, BackgroundVariant.Dots, BackgroundVariant.Cross
|
||||
* 'lines', 'dots', 'cross'
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,29 @@ import cc from 'classcat';
|
||||
|
||||
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) {
|
||||
return (
|
||||
<button type="button" className={cc(['react-flow__controls-button', className])} {...rest}>
|
||||
|
||||
@@ -125,4 +125,25 @@ function ControlsComponent({
|
||||
|
||||
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);
|
||||
|
||||
@@ -3,6 +3,9 @@ import type { PanelPosition } from '@xyflow/system';
|
||||
|
||||
import type { FitViewOptions } from '../../types';
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type ControlProps = {
|
||||
/** Show button for zoom in/out */
|
||||
showZoom?: boolean;
|
||||
@@ -20,7 +23,8 @@ export type ControlProps = {
|
||||
onFitView?: () => void;
|
||||
/** Callback when interactivity is toggled */
|
||||
onInteractiveChange?: (interactiveStatus: boolean) => void;
|
||||
/** Position of the controls on the pane
|
||||
/**
|
||||
* Position of the controls on the pane
|
||||
* @example PanelPosition.TopLeft, PanelPosition.TopRight,
|
||||
* PanelPosition.BottomLeft, PanelPosition.BottomRight
|
||||
*/
|
||||
@@ -34,4 +38,7 @@ export type ControlProps = {
|
||||
orientation?: 'horizontal' | 'vertical';
|
||||
};
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type ControlButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
@@ -44,8 +44,10 @@ function MiniMapComponent<NodeType extends Node = Node>({
|
||||
nodeClassName = '',
|
||||
nodeBorderRadius = 5,
|
||||
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,
|
||||
bgColor,
|
||||
maskColor,
|
||||
@@ -111,16 +113,16 @@ function MiniMapComponent<NodeType extends Node = Node>({
|
||||
|
||||
const onSvgClick = onClick
|
||||
? (event: MouseEvent) => {
|
||||
const [x, y] = minimapInstance.current?.pointer(event) || [0, 0];
|
||||
onClick(event, { x, y });
|
||||
}
|
||||
const [x, y] = minimapInstance.current?.pointer(event) || [0, 0];
|
||||
onClick(event, { x, y });
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const onSvgNodeClick = onNodeClick
|
||||
? useCallback((event: MouseEvent, nodeId: string) => {
|
||||
const node = store.getState().nodeLookup.get(nodeId)!;
|
||||
onNodeClick(event, node);
|
||||
}, [])
|
||||
const node = store.getState().nodeLookup.get(nodeId)!;
|
||||
onNodeClick(event, node);
|
||||
}, [])
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
@@ -176,4 +178,24 @@ function MiniMapComponent<NodeType extends Node = Node>({
|
||||
|
||||
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;
|
||||
|
||||
@@ -21,8 +21,10 @@ function MiniMapNodes<NodeType extends Node>({
|
||||
nodeClassName = '',
|
||||
nodeBorderRadius = 5,
|
||||
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,
|
||||
onClick,
|
||||
}: MiniMapNodesProps<NodeType>) {
|
||||
@@ -36,11 +38,13 @@ function MiniMapNodes<NodeType extends Node>({
|
||||
return (
|
||||
<>
|
||||
{nodeIds.map((nodeId) => (
|
||||
// The split of responsibilities between MiniMapNodes and
|
||||
// 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`.
|
||||
/*
|
||||
* The split of responsibilities between MiniMapNodes and
|
||||
* 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`.
|
||||
*/
|
||||
<NodeComponentWrapper<NodeType>
|
||||
key={nodeId}
|
||||
id={nodeId}
|
||||
|
||||
@@ -6,6 +6,9 @@ import type { Node } from '../../types';
|
||||
|
||||
export type GetMiniMapNodeAttribute<NodeType extends Node = Node> = (node: NodeType) => string;
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type MiniMapProps<NodeType extends Node = Node> = Omit<HTMLAttributes<SVGSVGElement>, 'onClick'> & {
|
||||
/** Color of nodes on minimap */
|
||||
nodeColor?: string | GetMiniMapNodeAttribute<NodeType>;
|
||||
@@ -27,7 +30,8 @@ export type MiniMapProps<NodeType extends Node = Node> = Omit<HTMLAttributes<SVG
|
||||
maskStrokeColor?: string;
|
||||
/** Stroke width of mask representing viewport */
|
||||
maskStrokeWidth?: number;
|
||||
/** Position of minimap on pane
|
||||
/**
|
||||
* Position of minimap on pane
|
||||
* @example PanelPosition.TopLeft, PanelPosition.TopRight,
|
||||
* PanelPosition.BottomLeft, PanelPosition.BottomRight
|
||||
*/
|
||||
@@ -57,6 +61,12 @@ export type MiniMapNodes<NodeType extends Node = Node> = Pick<
|
||||
onClick?: (event: MouseEvent, nodeId: string) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* The props that are passed to the MiniMapNode component
|
||||
*
|
||||
* @public
|
||||
* @expand
|
||||
*/
|
||||
export type MiniMapNodeProps = {
|
||||
id: string;
|
||||
x: number;
|
||||
|
||||
@@ -99,8 +99,10 @@ function ResizeControl({
|
||||
const parentExpandChanges = handleExpandParent([child], nodeLookup, parentLookup, nodeOrigin);
|
||||
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.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} />;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
@@ -3,6 +3,30 @@ import { ResizeControlVariant, XY_RESIZER_HANDLE_POSITIONS, XY_RESIZER_LINE_POSI
|
||||
import { NodeResizeControl } from './NodeResizeControl';
|
||||
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({
|
||||
nodeId,
|
||||
isVisible = true,
|
||||
|
||||
@@ -9,8 +9,12 @@ import type {
|
||||
OnResizeEnd,
|
||||
} from '@xyflow/system';
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type NodeResizerProps = {
|
||||
/** Id of the node it is resizing
|
||||
/**
|
||||
* Id of the node it is resizing
|
||||
* @remarks optional if used inside custom node
|
||||
*/
|
||||
nodeId?: string;
|
||||
@@ -46,6 +50,9 @@ export type NodeResizerProps = {
|
||||
onResizeEnd?: OnResizeEnd;
|
||||
};
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type ResizeControlProps = Pick<
|
||||
NodeResizerProps,
|
||||
| 'nodeId'
|
||||
@@ -60,12 +67,14 @@ export type ResizeControlProps = Pick<
|
||||
| 'onResize'
|
||||
| 'onResizeEnd'
|
||||
> & {
|
||||
/** Position of the control
|
||||
/**
|
||||
* Position of the control
|
||||
* @example ControlPosition.TopLeft, ControlPosition.TopRight,
|
||||
* ControlPosition.BottomLeft, ControlPosition.BottomRight
|
||||
*/
|
||||
position?: ControlPosition;
|
||||
/** Variant of the control
|
||||
/**
|
||||
* Variant of the control
|
||||
* @example ResizeControlVariant.Handle, ResizeControlVariant.Line
|
||||
*/
|
||||
variant?: ResizeControlVariant;
|
||||
@@ -74,6 +83,9 @@ export type ResizeControlProps = Pick<
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type ResizeControlLineProps = ResizeControlProps & {
|
||||
position?: ControlLinePosition;
|
||||
};
|
||||
|
||||
@@ -38,6 +38,41 @@ const storeSelector = (state: ReactFlowState) => ({
|
||||
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({
|
||||
nodeId,
|
||||
children,
|
||||
@@ -74,7 +109,7 @@ export function NodeToolbar({
|
||||
const isActive =
|
||||
typeof isVisible === 'boolean'
|
||||
? 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) {
|
||||
return null;
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import type { Position, Align } from '@xyflow/system';
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type NodeToolbarProps = HTMLAttributes<HTMLDivElement> & {
|
||||
/** Id of the node, or array of ids the toolbar should be displayed at */
|
||||
nodeId?: string | string[];
|
||||
/** If true, node toolbar is visible even if node is not selected */
|
||||
isVisible?: boolean;
|
||||
/** Position of the toolbar relative to the node
|
||||
/**
|
||||
* Position of the toolbar relative to the node
|
||||
* @example Position.TopLeft, Position.TopRight,
|
||||
* Position.BottomLeft, Position.BottomRight
|
||||
*/
|
||||
position?: Position;
|
||||
/** Offset the toolbar from the node */
|
||||
offset?: number;
|
||||
/** Align the toolbar relative to the node
|
||||
/**
|
||||
* Align the toolbar relative to the node
|
||||
* @example Align.Start, Align.Center, Align.End
|
||||
*/
|
||||
align?: Align;
|
||||
|
||||
@@ -30,9 +30,11 @@ export function BatchProvider<NodeType extends Node = Node, EdgeType extends Edg
|
||||
const nodeQueueHandler = useCallback((queueItems: QueueItem<NodeType>[]) => {
|
||||
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
|
||||
// array methods where we can.
|
||||
/*
|
||||
* 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
|
||||
* array methods where we can.
|
||||
*/
|
||||
let next = nodes as NodeType[];
|
||||
for (const payload of queueItems) {
|
||||
next = typeof payload === 'function' ? payload(next) : payload;
|
||||
|
||||
@@ -12,21 +12,27 @@ import { Queue, QueueItem } from './types';
|
||||
* @returns a Queue object
|
||||
*/
|
||||
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
|
||||
// queue, creating a new state to trigger the layout effect below.
|
||||
// Using a boolean dirty flag here instead would lead to issues related to
|
||||
// automatic batching. (https://github.com/xyflow/xyflow/issues/4779)
|
||||
/*
|
||||
* 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
|
||||
* queue, creating a new state to trigger the layout effect below.
|
||||
* 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));
|
||||
|
||||
// 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
|
||||
// batched together.
|
||||
/*
|
||||
* 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
|
||||
* batched together.
|
||||
*/
|
||||
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
|
||||
// rendering things one frame later than expected (we used to use `setTimeout`).
|
||||
/*
|
||||
* 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
|
||||
* rendering things one frame later than expected (we used to use `setTimeout`).
|
||||
*/
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
const queueItems = queue.get();
|
||||
|
||||
|
||||
@@ -6,6 +6,46 @@ import type { ReactFlowState } from '../../types';
|
||||
|
||||
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 }) {
|
||||
const edgeLabelRenderer = useStore(selector);
|
||||
|
||||
|
||||
@@ -136,28 +136,28 @@ export function EdgeWrapper<EdgeType extends Edge = Edge>({
|
||||
|
||||
const onEdgeDoubleClick = onDoubleClick
|
||||
? (event: React.MouseEvent) => {
|
||||
onDoubleClick(event, { ...edge });
|
||||
}
|
||||
onDoubleClick(event, { ...edge });
|
||||
}
|
||||
: undefined;
|
||||
const onEdgeContextMenu = onContextMenu
|
||||
? (event: React.MouseEvent) => {
|
||||
onContextMenu(event, { ...edge });
|
||||
}
|
||||
onContextMenu(event, { ...edge });
|
||||
}
|
||||
: undefined;
|
||||
const onEdgeMouseEnter = onMouseEnter
|
||||
? (event: React.MouseEvent) => {
|
||||
onMouseEnter(event, { ...edge });
|
||||
}
|
||||
onMouseEnter(event, { ...edge });
|
||||
}
|
||||
: undefined;
|
||||
const onEdgeMouseMove = onMouseMove
|
||||
? (event: React.MouseEvent) => {
|
||||
onMouseMove(event, { ...edge });
|
||||
}
|
||||
onMouseMove(event, { ...edge });
|
||||
}
|
||||
: undefined;
|
||||
const onEdgeMouseLeave = onMouseLeave
|
||||
? (event: React.MouseEvent) => {
|
||||
onMouseLeave(event, { ...edge });
|
||||
}
|
||||
onMouseLeave(event, { ...edge });
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
|
||||
@@ -4,6 +4,33 @@ import cc from 'classcat';
|
||||
import { EdgeText } from './EdgeText';
|
||||
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({
|
||||
path,
|
||||
labelX,
|
||||
|
||||
@@ -73,4 +73,30 @@ function EdgeTextComponent({
|
||||
|
||||
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);
|
||||
|
||||
@@ -29,6 +29,11 @@ function getControl({ pos, x1, y1, x2, y2 }: GetControlParams): [number, number]
|
||||
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({
|
||||
sourceX,
|
||||
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
|
||||
// If you import an edge from the library, the id is optional and source and target are not used at all
|
||||
/*
|
||||
* 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
|
||||
* 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 { SmoothStepEdge, SmoothStepEdgeInternal } from './SmoothStepEdge';
|
||||
|
||||
@@ -28,10 +28,14 @@ import { useNodeId } from '../../contexts/NodeIdContext';
|
||||
import { type ReactFlowState } from '../../types';
|
||||
import { fixedForwardRef } from '../../utils';
|
||||
|
||||
export interface HandleProps extends HandlePropsSystem, Omit<HTMLAttributes<HTMLDivElement>, 'id'> {
|
||||
/** Callback called when connection is made */
|
||||
onConnect?: OnConnect;
|
||||
}
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
export type HandleProps = HandlePropsSystem &
|
||||
Omit<HTMLAttributes<HTMLDivElement>, 'id'> & {
|
||||
/** Callback called when connection is made */
|
||||
onConnect?: OnConnect;
|
||||
};
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
connectOnClick: s.connectOnClick,
|
||||
@@ -228,8 +232,10 @@ function HandleComponent(
|
||||
connectingfrom: connectingFrom,
|
||||
connectingto: connectingTo,
|
||||
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:
|
||||
isConnectable &&
|
||||
(!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));
|
||||
|
||||
@@ -108,8 +108,10 @@ export function NodeWrapper<NodeType extends Node>({
|
||||
const { selectNodesOnDrag, nodeDragThreshold } = store.getState();
|
||||
|
||||
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({
|
||||
id,
|
||||
store,
|
||||
|
||||
@@ -49,8 +49,10 @@ export function useNodeObserver({
|
||||
|
||||
useEffect(() => {
|
||||
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 sourcePosChanged = prevSourcePosition.current !== node.sourcePosition;
|
||||
const targetPosChanged = prevTargetPosition.current !== node.targetPosition;
|
||||
|
||||
@@ -23,9 +23,9 @@ export const builtinNodeTypes: NodeTypes = {
|
||||
export function getNodeInlineStyleDimensions<NodeType extends Node = Node>(
|
||||
node: InternalNode<NodeType>
|
||||
): {
|
||||
width: number | string | undefined;
|
||||
height: number | string | undefined;
|
||||
} {
|
||||
width: number | string | undefined;
|
||||
height: number | string | undefined;
|
||||
} {
|
||||
if (node.internals.handleBounds === undefined) {
|
||||
return {
|
||||
width: node.width ?? node.initialWidth ?? node.style?.width,
|
||||
|
||||
@@ -4,10 +4,12 @@ import { errorMessages } from '@xyflow/system';
|
||||
|
||||
import type { ReactFlowState } from '../../types';
|
||||
|
||||
// this handler is called by
|
||||
// 1. the click handler when node is not draggable or selectNodesOnDrag = false
|
||||
// or
|
||||
// 2. the on drag start handler when node is draggable and selectNodesOnDrag = true
|
||||
/*
|
||||
* this handler is called by
|
||||
* 1. the click handler when node is not draggable or selectNodesOnDrag = false
|
||||
* or
|
||||
* 2. the on drag start handler when node is draggable and selectNodesOnDrag = true
|
||||
*/
|
||||
export function handleNodeClick({
|
||||
id,
|
||||
store,
|
||||
|
||||
@@ -61,9 +61,9 @@ export function NodesSelection<NodeType extends Node>({
|
||||
|
||||
const onContextMenu = onSelectionContextMenu
|
||||
? (event: MouseEvent) => {
|
||||
const selectedNodes = store.getState().nodes.filter((n) => n.selected);
|
||||
onSelectionContextMenu(event, selectedNodes);
|
||||
}
|
||||
const selectedNodes = store.getState().nodes.filter((n) => n.selected);
|
||||
onSelectionContextMenu(event, selectedNodes);
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
|
||||
@@ -5,9 +5,12 @@ import type { PanelPosition } from '@xyflow/system';
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
import type { ReactFlowState } from '../../types';
|
||||
|
||||
/**
|
||||
* @expand
|
||||
*/
|
||||
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;
|
||||
children: ReactNode;
|
||||
@@ -15,6 +18,31 @@ export type PanelProps = HTMLAttributes<HTMLDivElement> & {
|
||||
|
||||
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>(
|
||||
({ position = 'top-left', children, className, style, ...rest }, ref) => {
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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({
|
||||
initialNodes: nodes,
|
||||
initialEdges: edges,
|
||||
|
||||
@@ -94,9 +94,11 @@ const selector = (s: ReactFlowState) => ({
|
||||
});
|
||||
|
||||
const initPrevValues = {
|
||||
// these are values that are also passed directly to other components
|
||||
// than the StoreUpdater. We can reduce the number of setStore calls
|
||||
// by setting the same values here as prev fields.
|
||||
/*
|
||||
* these are values that are also passed directly to other components
|
||||
* than the StoreUpdater. We can reduce the number of setStore calls
|
||||
* by setting the same values here as prev fields.
|
||||
*/
|
||||
translateExtent: infiniteExtent,
|
||||
nodeOrigin: defaultNodeOrigin,
|
||||
minZoom: 0.5,
|
||||
|
||||
@@ -6,6 +6,31 @@ import type { ReactFlowState } from '../../types';
|
||||
|
||||
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 }) {
|
||||
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
|
||||
// that we can then use for creating our unique marker ids
|
||||
/*
|
||||
* 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
|
||||
* that we can then use for creating our unique marker ids
|
||||
*/
|
||||
const MarkerDefinitions = ({ defaultColor, rfId }: MarkerDefinitionsProps) => {
|
||||
const edges = useStore((s) => s.edges);
|
||||
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}>
|
||||
{nodeIds.map((nodeId) => {
|
||||
return (
|
||||
// The split of responsibilities between NodeRenderer and
|
||||
// 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
|
||||
// every time, it would have to re-run the `nodes.map()` loop every
|
||||
// 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:
|
||||
// - NodeRenderer subscribes *only* to node IDs – and therefore
|
||||
// rerender *only* when visible nodes are added or removed.
|
||||
// - NodeRenderer performs all operations the result of which can be
|
||||
// shared between nodes (such as creating the `ResizeObserver`
|
||||
// instance, or subscribing to `selector`). This means extra prop
|
||||
// drilling into `NodeComponentWrapper`, but it means we need to run
|
||||
// these operations only once – instead of once per node.
|
||||
// - Any operations that you’d normally write inside `nodes.map` are
|
||||
// moved into `NodeComponentWrapper`. This ensures they are
|
||||
// memorized – so if `NodeRenderer` *has* to rerender, it only
|
||||
// needs to regenerate the list of nodes, nothing else.
|
||||
/*
|
||||
* The split of responsibilities between NodeRenderer and
|
||||
* 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
|
||||
* every time, it would have to re-run the `nodes.map()` loop every
|
||||
* 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:
|
||||
* - NodeRenderer subscribes *only* to node IDs – and therefore
|
||||
* rerender *only* when visible nodes are added or removed.
|
||||
* - NodeRenderer performs all operations the result of which can be
|
||||
* shared between nodes (such as creating the `ResizeObserver`
|
||||
* instance, or subscribing to `selector`). This means extra prop
|
||||
* drilling into `NodeComponentWrapper`, but it means we need to run
|
||||
* these operations only once – instead of once per node.
|
||||
* - Any operations that you’d normally write inside `nodes.map` are
|
||||
* moved into `NodeComponentWrapper`. This ensures they are
|
||||
* memorized – so if `NodeRenderer` *has* to rerender, it only
|
||||
* needs to regenerate the list of nodes, nothing else.
|
||||
*/
|
||||
<NodeWrapper<NodeType>
|
||||
key={nodeId}
|
||||
id={nodeId}
|
||||
|
||||
@@ -233,8 +233,10 @@ export function Pane({
|
||||
(event.target as Partial<Element>)?.releasePointerCapture?.(event.pointerId);
|
||||
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) {
|
||||
onClick?.(event);
|
||||
}
|
||||
@@ -246,8 +248,10 @@ export function Pane({
|
||||
});
|
||||
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) {
|
||||
selectionInProgress.current = false;
|
||||
}
|
||||
|
||||
@@ -31,8 +31,10 @@ export function Wrapper({
|
||||
const isWrapped = useContext(StoreContext);
|
||||
|
||||
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}</>;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -4,6 +4,34 @@ export const NodeIdContext = createContext<string | null>(null);
|
||||
export const Provider = NodeIdContext.Provider;
|
||||
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 => {
|
||||
const nodeId = useContext(NodeIdContext);
|
||||
return nodeId;
|
||||
|
||||
@@ -24,9 +24,28 @@ function getSelector<NodeType extends Node = Node, SelectorReturn = ConnectionSt
|
||||
return storeSelector;
|
||||
}
|
||||
/**
|
||||
* Hook for accessing the connection state.
|
||||
* The `useConnection` hook returns the current connection when there is an active
|
||||
* connection interaction. If no connection interaction is active, it returns null
|
||||
* for every property. A typical use case for this hook is to colorize handles
|
||||
* based on a certain condition (e.g. if the connection is valid or not).
|
||||
*
|
||||
* @public
|
||||
* @example
|
||||
*
|
||||
* ```tsx
|
||||
*import { useConnection } from '@xyflow/react';
|
||||
*
|
||||
*function App() {
|
||||
* const connection = useConnection();
|
||||
*
|
||||
* return (
|
||||
* <div> {connection ? `Someone is trying to make a connection from ${connection.fromNode} to this one.` : 'There are currently no incoming connections!'}
|
||||
*
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @returns ConnectionState
|
||||
*/
|
||||
export function useConnection<NodeType extends Node = Node, SelectorReturn = ConnectionState<InternalNode<NodeType>>>(
|
||||
|
||||
@@ -6,10 +6,22 @@ import type { Edge, ReactFlowState } from '../types';
|
||||
const edgesSelector = (state: ReactFlowState) => state.edges;
|
||||
|
||||
/**
|
||||
* Hook for getting the current edges from the store.
|
||||
* This hook returns an array of the current edges. Components that use this hook
|
||||
* will re-render **whenever any edge changes**.
|
||||
*
|
||||
* @public
|
||||
* @returns An array of edges
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
*import { useEdges } from '@xyflow/react';
|
||||
*
|
||||
*export default function () {
|
||||
* const edges = useEdges();
|
||||
*
|
||||
* return <div>There are currently {edges.length} edges!</div>;
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useEdges<EdgeType extends Edge = Edge>(): EdgeType[] {
|
||||
const edges = useStore(edgesSelector, shallow) as EdgeType[];
|
||||
|
||||
@@ -5,11 +5,31 @@ import { useStore } from './useStore';
|
||||
import type { InternalNode, Node } from '../types';
|
||||
|
||||
/**
|
||||
* Hook for getting an internal node by id
|
||||
* This hook returns the internal representation of a specific node.
|
||||
* Components that use this hook will re-render **whenever the node changes**,
|
||||
* including when a node is selected or moved.
|
||||
*
|
||||
* @public
|
||||
* @param id - id of the node
|
||||
* @returns array with visible node ids
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
*import { useInternalNode } from '@xyflow/react';
|
||||
*
|
||||
*export default function () {
|
||||
* const internalNode = useInternalNode('node-1');
|
||||
* const absolutePosition = internalNode.internals.positionAbsolute;
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* The absolute position of the node is at:
|
||||
* <p>x: {absolutePosition.x}</p>
|
||||
* <p>y: {absolutePosition.y}</p>
|
||||
* </div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useInternalNode<NodeType extends Node = Node>(id: string): InternalNode<NodeType> | undefined {
|
||||
const node = useStore(
|
||||
|
||||
@@ -13,18 +13,38 @@ export type UseKeyPressOptions = {
|
||||
const defaultDoc = typeof document !== 'undefined' ? document : null;
|
||||
|
||||
/**
|
||||
* Hook for handling key events.
|
||||
* This hook lets you listen for specific key codes and tells you whether they are
|
||||
* currently pressed or not.
|
||||
*
|
||||
* @public
|
||||
* @param param.keyCode - The key code (string or array of strings) to use
|
||||
* @param param.options - Options
|
||||
* @returns boolean
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
*import { useKeyPress } from '@xyflow/react';
|
||||
*
|
||||
*export default function () {
|
||||
* const spacePressed = useKeyPress('Space');
|
||||
* const cmdAndSPressed = useKeyPress(['Meta+s', 'Strg+s']);
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* {spacePressed && <p>Space pressed!</p>}
|
||||
* {cmdAndSPressed && <p>Cmd + S pressed!</p>}
|
||||
* </div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useKeyPress(
|
||||
// the keycode can be a string 'a' or an array of strings ['a', 'a+d']
|
||||
// a string means a single key 'a' or a combination when '+' is used 'a+d'
|
||||
// an array means different possibilites. Explainer: ['a', 'd+s'] here the
|
||||
// user can use the single key 'a' or the combination 'd' + 's'
|
||||
/*
|
||||
* the keycode can be a string 'a' or an array of strings ['a', 'a+d']
|
||||
* a string means a single key 'a' or a combination when '+' is used 'a+d'
|
||||
* an array means different possibilites. Explainer: ['a', 'd+s'] here the
|
||||
* user can use the single key 'a' or the combination 'd' + 's'
|
||||
*/
|
||||
keyCode: KeyCode | null = null,
|
||||
options: UseKeyPressOptions = { target: defaultDoc, actInsideInputWithModifier: true }
|
||||
): boolean {
|
||||
@@ -36,20 +56,24 @@ export function useKeyPress(
|
||||
// we need to remember the pressed keys in order to support combinations
|
||||
const pressedKeys = useRef<PressedKeys>(new Set([]));
|
||||
|
||||
// keyCodes = array with single keys [['a']] or key combinations [['a', 's']]
|
||||
// keysToWatch = array with all keys flattened ['a', 'd', 'ShiftLeft']
|
||||
// used to check if we store event.code or event.key. When the code is in the list of keysToWatch
|
||||
// we use the code otherwise the key. Explainer: When you press the left "command" key, the code is "MetaLeft"
|
||||
// and the key is "Meta". We want users to be able to pass keys and codes so we assume that the key is meant when
|
||||
// we can't find it in the list of keysToWatch.
|
||||
/*
|
||||
* keyCodes = array with single keys [['a']] or key combinations [['a', 's']]
|
||||
* keysToWatch = array with all keys flattened ['a', 'd', 'ShiftLeft']
|
||||
* used to check if we store event.code or event.key. When the code is in the list of keysToWatch
|
||||
* we use the code otherwise the key. Explainer: When you press the left "command" key, the code is "MetaLeft"
|
||||
* and the key is "Meta". We want users to be able to pass keys and codes so we assume that the key is meant when
|
||||
* we can't find it in the list of keysToWatch.
|
||||
*/
|
||||
const [keyCodes, keysToWatch] = useMemo<[Array<Keys>, Keys]>(() => {
|
||||
if (keyCode !== null) {
|
||||
const keyCodeArr = Array.isArray(keyCode) ? keyCode : [keyCode];
|
||||
const keys = keyCodeArr
|
||||
.filter((kc) => typeof kc === 'string')
|
||||
// we first replace all '+' with '\n' which we will use to split the keys on
|
||||
// then we replace '\n\n' with '\n+', this way we can also support the combination 'key++'
|
||||
// in the end we simply split on '\n' to get the key array
|
||||
/*
|
||||
* we first replace all '+' with '\n' which we will use to split the keys on
|
||||
* then we replace '\n\n' with '\n+', this way we can also support the combination 'key++'
|
||||
* in the end we simply split on '\n' to get the key array
|
||||
*/
|
||||
.map((kc) => kc.replace('+', '\n').replace('\n\n', '\n+').split('\n'));
|
||||
const keysFlat = keys.reduce((res: Keys, item) => res.concat(...item), []);
|
||||
|
||||
@@ -133,12 +157,16 @@ export function useKeyPress(
|
||||
function isMatchingKey(keyCodes: Array<Keys>, pressedKeys: PressedKeys, isUp: boolean): boolean {
|
||||
return (
|
||||
keyCodes
|
||||
// we only want to compare same sizes of keyCode definitions
|
||||
// and pressed keys. When the user specified 'Meta' as a key somewhere
|
||||
// this would also be truthy without this filter when user presses 'Meta' + 'r'
|
||||
/*
|
||||
* we only want to compare same sizes of keyCode definitions
|
||||
* and pressed keys. When the user specified 'Meta' as a key somewhere
|
||||
* this would also be truthy without this filter when user presses 'Meta' + 'r'
|
||||
*/
|
||||
.filter((keys) => isUp || keys.length === pressedKeys.size)
|
||||
// since we want to support multiple possibilities only one of the
|
||||
// combinations need to be part of the pressed keys
|
||||
/*
|
||||
* since we want to support multiple possibilities only one of the
|
||||
* combinations need to be part of the pressed keys
|
||||
*/
|
||||
.some((keys) => keys.every((k) => pressedKeys.has(k)))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,8 +22,10 @@ export function useMoveSelectedNodes() {
|
||||
const nodeUpdates = new Map();
|
||||
const isSelected = selectedAndDraggable(nodesDraggable);
|
||||
|
||||
// by default a node moves 5px on each key press
|
||||
// if snap grid is enabled, we use that for the velocity
|
||||
/*
|
||||
* by default a node moves 5px on each key press
|
||||
* if snap grid is enabled, we use that for the velocity
|
||||
*/
|
||||
const xVelo = snapToGrid ? snapGrid[0] : 5;
|
||||
const yVelo = snapToGrid ? snapGrid[1] : 5;
|
||||
|
||||
|
||||
@@ -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
|
||||
* @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.onDisconnect - gets called when a connection is removed
|
||||
* @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({
|
||||
id,
|
||||
|
||||
@@ -6,10 +6,23 @@ import type { Node, ReactFlowState } from '../types';
|
||||
const nodesSelector = (state: ReactFlowState) => state.nodes;
|
||||
|
||||
/**
|
||||
* Hook for getting the current nodes from the store.
|
||||
* This hook returns an array of the current nodes. Components that use this hook
|
||||
* will re-render **whenever any node changes**, including when a node is selected
|
||||
* or moved.
|
||||
*
|
||||
* @public
|
||||
* @returns An array of nodes
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useNodes } from '@xyflow/react';
|
||||
*
|
||||
*export default function() {
|
||||
* const nodes = useNodes();
|
||||
*
|
||||
* return <div>There are currently {nodes.length} nodes!</div>;
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useNodes<NodeType extends Node = Node>(): NodeType[] {
|
||||
const nodes = useStore(nodesSelector, shallow) as NodeType[];
|
||||
|
||||
@@ -5,12 +5,24 @@ import { useStore } from '../hooks/useStore';
|
||||
import type { Node } from '../types';
|
||||
|
||||
/**
|
||||
* Hook for receiving data of one or multiple nodes
|
||||
* This hook lets you subscribe to changes of a specific nodes `data` object.
|
||||
*
|
||||
* @public
|
||||
* @param nodeId - The id (or ids) of the node to get the data from
|
||||
* @param guard - Optional guard function to narrow down the node type
|
||||
* @returns An object (or array of object) with {id, type, data} representing each node
|
||||
*
|
||||
* @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>(
|
||||
nodeId: string
|
||||
|
||||
@@ -4,11 +4,41 @@ import { applyNodeChanges, applyEdgeChanges } from '../utils/changes';
|
||||
import type { Node, Edge, OnNodesChange, OnEdgesChange } from '../types';
|
||||
|
||||
/**
|
||||
* Hook for managing the state of nodes - should only be used for prototyping / simple use cases.
|
||||
* This hook makes it easy to prototype a controlled flow where you manage the
|
||||
* state of nodes and edges outside the `ReactFlowInstance`. You can think of it
|
||||
* like React's `useState` hook with an additional helper callback.
|
||||
*
|
||||
* @public
|
||||
* @param initialNodes
|
||||
* @returns an array [nodes, setNodes, onNodesChange]
|
||||
* @example
|
||||
*
|
||||
*```tsx
|
||||
*import { ReactFlow, useNodesState, useEdgesState } from '@xyflow/react';
|
||||
*
|
||||
*const initialNodes = [];
|
||||
*const initialEdges = [];
|
||||
*
|
||||
*export default function () {
|
||||
* const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
* const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
*
|
||||
* return (
|
||||
* <ReactFlow
|
||||
* nodes={nodes}
|
||||
* edges={edges}
|
||||
* onNodesChange={onNodesChange}
|
||||
* onEdgesChange={onEdgesChange}
|
||||
* />
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*
|
||||
* @remarks This hook was created to make prototyping easier and our documentation
|
||||
* examples clearer. Although it is OK to use this hook in production, in
|
||||
* practice you may want to use a more sophisticated state management solution
|
||||
* like Zustand {@link https://reactflow.dev/docs/guides/state-management/} instead.
|
||||
*
|
||||
*/
|
||||
export function useNodesState<NodeType extends Node>(
|
||||
initialNodes: NodeType[]
|
||||
@@ -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
|
||||
* @param initialEdges
|
||||
* @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>(
|
||||
initialEdges: EdgeType[]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { nodeHasDimensions } from '@xyflow/system';
|
||||
|
||||
import { useStore } from './useStore';
|
||||
import type { ReactFlowState } from '../types';
|
||||
import { nodeHasDimensions } from '@xyflow/system';
|
||||
|
||||
export type UseNodesInitializedOptions = {
|
||||
includeHiddenNodes?: boolean;
|
||||
@@ -22,18 +23,44 @@ const selector = (options: UseNodesInitializedOptions) => (s: ReactFlowState) =>
|
||||
return true;
|
||||
};
|
||||
|
||||
const defaultOptions = {
|
||||
includeHiddenNodes: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook which returns true when all nodes are initialized.
|
||||
* This hook tells you whether all the nodes in a flow have been measured and given
|
||||
*a width and height. When you add a node to the flow, this hook will return
|
||||
*`false` and then `true` again once the node has been measured.
|
||||
*
|
||||
* @public
|
||||
* @param options.includeHiddenNodes - defaults to false
|
||||
* @returns boolean indicating whether all nodes are initialized
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useReactFlow, useNodesInitialized } from '@xyflow/react';
|
||||
*import { useEffect, useState } from 'react';
|
||||
*
|
||||
*const options = {
|
||||
* includeHiddenNodes: false,
|
||||
*};
|
||||
*
|
||||
*export default function useLayout() {
|
||||
* const { getNodes } = useReactFlow();
|
||||
* const nodesInitialized = useNodesInitialized(options);
|
||||
* const [layoutedNodes, setLayoutedNodes] = useState(getNodes());
|
||||
*
|
||||
* useEffect(() => {
|
||||
* if (nodesInitialized) {
|
||||
* setLayoutedNodes(yourLayoutingFunction(getNodes()));
|
||||
* }
|
||||
* }, [nodesInitialized]);
|
||||
*
|
||||
* return layoutedNodes;
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useNodesInitialized(options: UseNodesInitializedOptions = defaultOptions): boolean {
|
||||
export function useNodesInitialized(
|
||||
options: UseNodesInitializedOptions = {
|
||||
includeHiddenNodes: false,
|
||||
}
|
||||
): boolean {
|
||||
const initialized = useStore(selector(options));
|
||||
|
||||
return initialized;
|
||||
|
||||
@@ -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
|
||||
* @param params.onChange - The handler to register
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useState } from 'react';
|
||||
*import { ReactFlow, useOnSelectionChange } from '@xyflow/react';
|
||||
*
|
||||
*function SelectionDisplay() {
|
||||
* const [selectedNodes, setSelectedNodes] = useState([]);
|
||||
* const [selectedEdges, setSelectedEdges] = useState([]);
|
||||
*
|
||||
* // the passed handler has to be memoized, otherwise the hook will not work correctly
|
||||
* const onChange = useCallback(({ nodes, edges }) => {
|
||||
* setSelectedNodes(nodes.map((node) => node.id));
|
||||
* setSelectedEdges(edges.map((edge) => edge.id));
|
||||
* }, []);
|
||||
*
|
||||
* useOnSelectionChange({
|
||||
* onChange,
|
||||
* });
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <p>Selected nodes: {selectedNodes.join(', ')}</p>
|
||||
* <p>Selected edges: {selectedEdges.join(', ')}</p>
|
||||
* </div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*
|
||||
* @remarks You need to memoize the passed `onChange` handler, otherwise the hook will not work correctly.
|
||||
*/
|
||||
export function useOnSelectionChange({ onChange }: UseOnSelectionChangeOptions) {
|
||||
const store = useStoreApi();
|
||||
|
||||
@@ -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
|
||||
* @param params.onStart - gets called when the viewport starts changing
|
||||
* @param params.onChange - gets called when the viewport changes
|
||||
* @param params.onEnd - gets called when the viewport stops changing
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useCallback } from 'react';
|
||||
*import { useOnViewportChange } from '@xyflow/react';
|
||||
*
|
||||
*function ViewportChangeLogger() {
|
||||
* useOnViewportChange({
|
||||
* onStart: (viewport: Viewport) => console.log('start', viewport),
|
||||
* onChange: (viewport: Viewport) => console.log('change', viewport),
|
||||
* onEnd: (viewport: Viewport) => console.log('end', viewport),
|
||||
* });
|
||||
*
|
||||
* return null;
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useOnViewportChange({ onStart, onChange, onEnd }: UseOnViewportChangeOptions) {
|
||||
const store = useStoreApi();
|
||||
|
||||
@@ -20,10 +20,33 @@ import type { ReactFlowInstance, Node, Edge, InternalNode, ReactFlowState, Gener
|
||||
const selector = (s: ReactFlowState) => !!s.panZoom;
|
||||
|
||||
/**
|
||||
* Hook for accessing the ReactFlow instance.
|
||||
* This hook returns a ReactFlowInstance that can be used to update nodes and edges, manipulate the viewport, or query the current state of the flow.
|
||||
*
|
||||
* @public
|
||||
* @returns ReactFlowInstance
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useCallback, useState } from 'react';
|
||||
*import { useReactFlow } from '@xyflow/react';
|
||||
*
|
||||
*export function NodeCounter() {
|
||||
* const reactFlow = useReactFlow();
|
||||
* const [count, setCount] = useState(0);
|
||||
* const countNodes = useCallback(() => {
|
||||
* setCount(reactFlow.getNodes().length);
|
||||
* // you need to pass it as a dependency if you are using it with useEffect or useCallback
|
||||
* // because at the first render, it's not initialized yet and some functions might not work.
|
||||
* }, [reactFlow]);
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <button onClick={countNodes}>Update count</button>
|
||||
* <p>There are {count} nodes in the flow.</p>
|
||||
* </div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(): ReactFlowInstance<
|
||||
NodeType,
|
||||
|
||||
@@ -9,7 +9,9 @@ import type { Edge, Node, ReactFlowState } from '../types';
|
||||
const zustandErrorMessage = errorMessages['error001']();
|
||||
|
||||
/**
|
||||
* Hook for accessing the internal store. Should only be used in rare cases.
|
||||
* This hook can be used to subscribe to internal state changes of the React Flow
|
||||
* component. The `useStore` hook is re-exported from the [Zustand](https://github.com/pmndrs/zustand)
|
||||
* state management library, so you should check out their docs for more details.
|
||||
*
|
||||
* @public
|
||||
* @param selector
|
||||
@@ -17,8 +19,13 @@ const zustandErrorMessage = errorMessages['error001']();
|
||||
* @returns The selected state slice
|
||||
*
|
||||
* @example
|
||||
* const nodes = useStore((state: ReactFlowState<MyNodeType>) => state.nodes);
|
||||
* ```ts
|
||||
* const nodes = useStore((state) => state.nodes);
|
||||
* ```
|
||||
*
|
||||
* @remarks This hook should only be used if there is no other way to access the internal
|
||||
* state. For many of the common use cases, there are dedicated hooks available
|
||||
* such as {@link useReactFlow}, {@link useViewport}, etc.
|
||||
*/
|
||||
function useStore<StateSlice = unknown>(
|
||||
selector: (state: ReactFlowState) => StateSlice,
|
||||
@@ -33,6 +40,20 @@ function useStore<StateSlice = unknown>(
|
||||
return useZustandStore(store, selector, equalityFn);
|
||||
}
|
||||
|
||||
/**
|
||||
* In some cases, you might need to access the store directly. This hook returns the store object which can be used on demand to access the state or dispatch actions.
|
||||
*
|
||||
* @returns The store object
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const store = useStoreApi();
|
||||
* ```
|
||||
*
|
||||
* @remarks This hook should only be used if there is no other way to access the internal
|
||||
* state. For many of the common use cases, there are dedicated hooks available
|
||||
* such as {@link useReactFlow}, {@link useViewport}, etc.
|
||||
*/
|
||||
function useStoreApi<NodeType extends Node = Node, EdgeType extends Edge = Edge>() {
|
||||
const store = useContext(StoreContext) as UseBoundStoreWithEqualityFn<
|
||||
StoreApi<ReactFlowState<NodeType, EdgeType>>
|
||||
|
||||
@@ -4,10 +4,48 @@ import type { UpdateNodeInternals, InternalNodeUpdate } from '@xyflow/system';
|
||||
import { useStoreApi } from '../hooks/useStore';
|
||||
|
||||
/**
|
||||
* Hook for updating node internals.
|
||||
* When you programmatically add or remove handles to a node or update a node's
|
||||
*handle position, you need to let React Flow know about it using this hook. This
|
||||
*will update the internal dimensions of the node and properly reposition handles
|
||||
*on the canvas if necessary.
|
||||
*
|
||||
* @public
|
||||
* @returns function for updating node internals
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
*import { useCallback, useState } from 'react';
|
||||
*import { Handle, useUpdateNodeInternals } from '@xyflow/react';
|
||||
*
|
||||
*export default function RandomHandleNode({ id }) {
|
||||
* const updateNodeInternals = useUpdateNodeInternals();
|
||||
* const [handleCount, setHandleCount] = useState(0);
|
||||
* const randomizeHandleCount = useCallback(() => {
|
||||
* setHandleCount(Math.floor(Math.random() * 10));
|
||||
* updateNodeInternals(id);
|
||||
* }, [id, updateNodeInternals]);
|
||||
*
|
||||
* return (
|
||||
* <>
|
||||
* {Array.from({ length: handleCount }).map((_, index) => (
|
||||
* <Handle
|
||||
* key={index}
|
||||
* type="target"
|
||||
* position="left"
|
||||
* id={`handle-${index}`}
|
||||
* />
|
||||
* ))}
|
||||
*
|
||||
* <div>
|
||||
* <button onClick={randomizeHandleCount}>Randomize handle count</button>
|
||||
* <p>There are {handleCount} handles on this node.</p>
|
||||
* </div>
|
||||
* </>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
* @remarks This hook can only be used in a component that is a child of a
|
||||
*{@link ReactFlowProvider} or a {@link ReactFlow} component.
|
||||
*/
|
||||
export function useUpdateNodeInternals(): UpdateNodeInternals {
|
||||
const store = useStoreApi();
|
||||
|
||||
@@ -11,10 +11,33 @@ const viewportSelector = (state: ReactFlowState) => ({
|
||||
});
|
||||
|
||||
/**
|
||||
* Hook for getting the current viewport from the store.
|
||||
* The `useViewport` hook is a convenient way to read the current state of the
|
||||
*{@link Viewport} in a component. Components that use this hook
|
||||
*will re-render **whenever the viewport changes**.
|
||||
*
|
||||
* @public
|
||||
* @returns The current viewport
|
||||
*
|
||||
* @example
|
||||
*
|
||||
*```jsx
|
||||
*import { useViewport } from '@xyflow/react';
|
||||
*
|
||||
*export default function ViewportDisplay() {
|
||||
* const { x, y, zoom } = useViewport();
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <p>
|
||||
* The viewport is currently at ({x}, {y}) and zoomed to {zoom}.
|
||||
* </p>
|
||||
* </div>
|
||||
* );
|
||||
*}
|
||||
*```
|
||||
*
|
||||
* @remarks This hook can only be used in a component that is a child of a
|
||||
*{@link ReactFlowProvider} or a {@link ReactFlow} component.
|
||||
*/
|
||||
export function useViewport(): Viewport {
|
||||
const viewport = useStore(viewportSelector, shallow);
|
||||
|
||||
@@ -8,8 +8,8 @@ import type { Node, ReactFlowState } from '../types';
|
||||
const selector = (onlyRenderVisible: boolean) => (s: ReactFlowState) => {
|
||||
return onlyRenderVisible
|
||||
? getNodesInside<Node>(s.nodeLookup, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true).map(
|
||||
(node) => node.id
|
||||
)
|
||||
(node) => node.id
|
||||
)
|
||||
: Array.from(s.nodeLookup.keys());
|
||||
};
|
||||
|
||||
|
||||
@@ -47,12 +47,14 @@ const createStore = ({
|
||||
...getInitialState({ nodes, edges, width, height, fitView, nodeOrigin, nodeExtent, defaultNodes, defaultEdges }),
|
||||
setNodes: (nodes: Node[]) => {
|
||||
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,
|
||||
// - 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.
|
||||
/*
|
||||
* setNodes() is called exclusively in response to user actions:
|
||||
* - 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.
|
||||
*/
|
||||
adoptUserNodes(nodes, nodeLookup, parentLookup, {
|
||||
nodeOrigin,
|
||||
nodeExtent,
|
||||
@@ -81,9 +83,11 @@ const createStore = ({
|
||||
set({ hasDefaultEdges: true });
|
||||
}
|
||||
},
|
||||
// Every node gets registerd at a ResizeObserver. Whenever a node
|
||||
// changes its dimensions, this function is called to measure the
|
||||
// new dimensions and update the nodes.
|
||||
/*
|
||||
* Every node gets registerd at a ResizeObserver. Whenever a node
|
||||
* changes its dimensions, this function is called to measure the
|
||||
* new dimensions and update the nodes.
|
||||
*/
|
||||
updateNodeInternals: (updates, params = { triggerFitView: true }) => {
|
||||
const {
|
||||
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
|
||||
// has not provided an onNodesChange handler.
|
||||
// Nodes are only rendered if they have a width and height
|
||||
// attribute which they get from this handler.
|
||||
/*
|
||||
* here we are cirmumventing the onNodesChange handler
|
||||
* in order to be able to display nodes even if the user
|
||||
* has not provided an onNodesChange handler.
|
||||
* Nodes are only rendered if they have a width and height
|
||||
* attribute which they get from this handler.
|
||||
*/
|
||||
set({ fitViewDone: nextFitViewDone });
|
||||
} else {
|
||||
// we always want to trigger useStore calls whenever updateNodeInternals is called
|
||||
@@ -155,9 +161,9 @@ const createStore = ({
|
||||
type: 'position',
|
||||
position: expandParent
|
||||
? {
|
||||
x: Math.max(0, dragItem.position.x),
|
||||
y: Math.max(0, dragItem.position.y),
|
||||
}
|
||||
x: Math.max(0, dragItem.position.x),
|
||||
y: Math.max(0, dragItem.position.y),
|
||||
}
|
||||
: dragItem.position,
|
||||
dragging,
|
||||
};
|
||||
@@ -248,8 +254,10 @@ const createStore = ({
|
||||
const nodeChanges = nodesToUnselect.map((n) => {
|
||||
const internalNode = nodeLookup.get(n.id);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -342,8 +350,10 @@ const createStore = ({
|
||||
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 => {
|
||||
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>
|
||||
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
|
||||
* const nodes = [
|
||||
* {
|
||||
@@ -64,7 +65,8 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
* ];
|
||||
*/
|
||||
nodes?: NodeType[];
|
||||
/** An array of edges to render in a controlled flow.
|
||||
/**
|
||||
* An array of edges to render in a controlled flow.
|
||||
* @example
|
||||
* const edges = [
|
||||
* {
|
||||
@@ -79,7 +81,8 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
defaultNodes?: NodeType[];
|
||||
/** The initial edges to render in an uncontrolled flow. */
|
||||
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.
|
||||
* @example
|
||||
@@ -132,7 +135,8 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
onReconnect?: OnReconnect<EdgeType>;
|
||||
onReconnectStart?: (event: ReactMouseEvent, 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
|
||||
* import ReactFlow, { useNodesState } from '@xyflow/react';
|
||||
* 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} />)
|
||||
*/
|
||||
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
|
||||
* import ReactFlow, { useEdgesState } from '@xyflow/react';
|
||||
* const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
@@ -181,7 +186,8 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
onSelectionStart?: (event: ReactMouseEvent) => void;
|
||||
onSelectionEnd?: (event: ReactMouseEvent) => 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.
|
||||
* @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;
|
||||
/** This event handler gets called when mouse leaves the pane */
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
nodeClickDistance?: number;
|
||||
/** 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>;
|
||||
/** 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.
|
||||
* @example
|
||||
@@ -242,7 +251,8 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
* const nodeTypes = { nameOfNodeType: CustomNode };
|
||||
*/
|
||||
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.
|
||||
* @example
|
||||
@@ -251,7 +261,8 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
* const edgeTypes = { nameOfEdgeType: CustomEdge };
|
||||
*/
|
||||
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!
|
||||
*/
|
||||
@@ -262,17 +273,20 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
connectionLineComponent?: ConnectionLineComponent<NodeType>;
|
||||
/** Styles to be applied to the container of the connection line */
|
||||
connectionLineContainerStyle?: CSSProperties;
|
||||
/** 'strict' connection mode will only allow you to connect source handles to target handles.
|
||||
/**
|
||||
* 'strict' connection mode will only allow you to connect source handles to target handles.
|
||||
*
|
||||
* 'loose' connection mode will allow you to connect handles of any type to one another.
|
||||
* @default 'strict'
|
||||
*/
|
||||
connectionMode?: ConnectionMode;
|
||||
/** Pressing down this key deletes all selected nodes & edges.
|
||||
/**
|
||||
* Pressing down this key deletes all selected nodes & edges.
|
||||
* @default 'Backspace'
|
||||
*/
|
||||
deleteKeyCode?: KeyCode | null;
|
||||
/** If a key is set, you can pan the viewport while that key is held down even if panOnScroll is set to false.
|
||||
/**
|
||||
* If a key is set, you can pan the viewport while that key is held down even if panOnScroll is set to false.
|
||||
*
|
||||
* By setting this prop to null you can disable this functionality.
|
||||
* @default 'Space'
|
||||
@@ -280,91 +294,110 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
selectionKeyCode?: KeyCode | null;
|
||||
/** Select multiple elements with a selection box, without pressing down selectionKey */
|
||||
selectionOnDrag?: boolean;
|
||||
/** When set to "partial", when the user creates a selection box by click and dragging nodes that are only partially in the box are still selected.
|
||||
/**
|
||||
* When set to "partial", when the user creates a selection box by click and dragging nodes that are only partially in the box are still selected.
|
||||
* @default 'full'
|
||||
*/
|
||||
selectionMode?: SelectionMode;
|
||||
/** If a key is set, you can pan the viewport while that key is held down even if panOnScroll is set to false.
|
||||
/**
|
||||
* If a key is set, you can pan the viewport while that key is held down even if panOnScroll is set to false.
|
||||
*
|
||||
* By setting this prop to null you can disable this functionality.
|
||||
* @default 'Space'
|
||||
*/
|
||||
panActivationKeyCode?: KeyCode | null;
|
||||
/** Pressing down this key you can select multiple elements by clicking.
|
||||
/**
|
||||
* Pressing down this key you can select multiple elements by clicking.
|
||||
* @default 'Meta' for macOS, "Ctrl" for other systems
|
||||
*/
|
||||
multiSelectionKeyCode?: KeyCode | null;
|
||||
/** If a key is set, you can zoom the viewport while that key is held down even if panOnScroll is set to false.
|
||||
/**
|
||||
* If a key is set, you can zoom the viewport while that key is held down even if panOnScroll is set to false.
|
||||
*
|
||||
* By setting this prop to null you can disable this functionality.
|
||||
* @default 'Meta' for macOS, "Ctrl" for other systems
|
||||
* */
|
||||
*
|
||||
*/
|
||||
zoomActivationKeyCode?: KeyCode | null;
|
||||
/** Set this prop to make the flow snap to the grid */
|
||||
snapToGrid?: boolean;
|
||||
/** Grid all nodes will snap to
|
||||
/**
|
||||
* Grid all nodes will snap to
|
||||
* @example [20, 20]
|
||||
*/
|
||||
snapGrid?: SnapGrid;
|
||||
/** You can enable this optimisation to instruct Svelte Flow to only render nodes and edges that would be visible in the viewport.
|
||||
/**
|
||||
* You can enable this optimisation to instruct Svelte Flow to only render nodes and edges that would be visible in the viewport.
|
||||
*
|
||||
* This might improve performance when you have a large number of nodes and edges but also adds an overhead.
|
||||
* @default false
|
||||
*/
|
||||
onlyRenderVisibleElements?: boolean;
|
||||
/** Controls if all nodes should be draggable
|
||||
/**
|
||||
* Controls if all nodes should be draggable
|
||||
* @default true
|
||||
*/
|
||||
nodesDraggable?: boolean;
|
||||
/** Controls if all nodes should be connectable to each other
|
||||
/**
|
||||
* Controls if all nodes should be connectable to each other
|
||||
* @default true
|
||||
*/
|
||||
nodesConnectable?: boolean;
|
||||
/** Controls if all nodes should be focusable
|
||||
/**
|
||||
* Controls if all nodes should be focusable
|
||||
* @default true
|
||||
*/
|
||||
nodesFocusable?: boolean;
|
||||
/** Defines nodes relative position to its coordinates
|
||||
/**
|
||||
* Defines nodes relative position to its coordinates
|
||||
* @example
|
||||
* [0, 0] // default, top left
|
||||
* [0.5, 0.5] // center
|
||||
* [1, 1] // bottom right
|
||||
*/
|
||||
nodeOrigin?: NodeOrigin;
|
||||
/** Controls if all edges should be focusable
|
||||
/**
|
||||
* Controls if all edges should be focusable
|
||||
* @default true
|
||||
*/
|
||||
edgesFocusable?: boolean;
|
||||
/** Controls if all edges should be updateable
|
||||
/**
|
||||
* Controls if all edges should be updateable
|
||||
* @default true
|
||||
*/
|
||||
edgesReconnectable?: boolean;
|
||||
/** Controls if all elements should (nodes & edges) be selectable
|
||||
/**
|
||||
* Controls if all elements should (nodes & edges) be selectable
|
||||
* @default true
|
||||
*/
|
||||
elementsSelectable?: boolean;
|
||||
/** If true, nodes get selected on drag
|
||||
/**
|
||||
* If true, nodes get selected on drag
|
||||
* @default true
|
||||
*/
|
||||
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.
|
||||
* @example [0, 2] // allows panning with the left and right mouse buttons
|
||||
* [0, 1, 2, 3, 4] // allows panning with all mouse buttons
|
||||
*/
|
||||
panOnDrag?: boolean | number[];
|
||||
/** Minimum zoom level
|
||||
/**
|
||||
* Minimum zoom level
|
||||
* @default 0.5
|
||||
*/
|
||||
minZoom?: number;
|
||||
/** Maximum zoom level
|
||||
/**
|
||||
* Maximum zoom level
|
||||
* @default 2
|
||||
*/
|
||||
maxZoom?: number;
|
||||
/** Controlled viewport to be used instead of internal one */
|
||||
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.
|
||||
* @example
|
||||
@@ -378,23 +411,27 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
* Gets called when the viewport changes.
|
||||
*/
|
||||
onViewportChange?: (viewport: Viewport) => void;
|
||||
/** By default the viewport extends infinitely. You can use this prop to set a boundary.
|
||||
/**
|
||||
* By default the viewport extends infinitely. You can use this prop to set a boundary.
|
||||
*
|
||||
* The first pair of coordinates is the top left boundary and the second pair is the bottom right.
|
||||
* @example [[-1000, -10000], [1000, 1000]]
|
||||
*/
|
||||
translateExtent?: CoordinateExtent;
|
||||
/** Disabling this prop will allow the user to scroll the page even when their pointer is over the flow.
|
||||
/**
|
||||
* Disabling this prop will allow the user to scroll the page even when their pointer is over the flow.
|
||||
* @default true
|
||||
*/
|
||||
preventScrolling?: boolean;
|
||||
/** By default nodes can be placed on an infinite flow. You can use this prop to set a boundary.
|
||||
/**
|
||||
* By default nodes can be placed on an infinite flow. You can use this prop to set a boundary.
|
||||
*
|
||||
* The first pair of coordinates is the top left boundary and the second pair is the bottom right.
|
||||
* @example [[-1000, -10000], [1000, 1000]]
|
||||
*/
|
||||
nodeExtent?: CoordinateExtent;
|
||||
/** Color of edge markers
|
||||
/**
|
||||
* Color of edge markers
|
||||
* @example "#b1b1b7"
|
||||
*/
|
||||
defaultMarkerColor?: string;
|
||||
@@ -402,17 +439,20 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
zoomOnScroll?: boolean;
|
||||
/** Controls if the viewport should zoom by pinching on a touch screen */
|
||||
zoomOnPinch?: boolean;
|
||||
/** Controls if the viewport should pan by scrolling inside the container
|
||||
/**
|
||||
* Controls if the viewport should pan by scrolling inside the container
|
||||
*
|
||||
* Can be limited to a specific direction with panOnScrollMode
|
||||
*/
|
||||
panOnScroll?: boolean;
|
||||
/** Controls how fast viewport should be panned on scroll.
|
||||
/**
|
||||
* Controls how fast viewport should be panned on scroll.
|
||||
*
|
||||
* Use togther with panOnScroll prop.
|
||||
*/
|
||||
panOnScrollSpeed?: number;
|
||||
/** This prop is used to limit the direction of panning when panOnScroll is enabled.
|
||||
/**
|
||||
* This prop is used to limit the direction of panning when panOnScroll is enabled.
|
||||
*
|
||||
* The "free" option allows panning in any direction.
|
||||
* @default "free"
|
||||
@@ -427,7 +467,8 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
noPanClassName?: string;
|
||||
/** If set, initial viewport will show all nodes & edges */
|
||||
fitView?: boolean;
|
||||
/** Options to be used in combination with fitView
|
||||
/**
|
||||
* Options to be used in combination with fitView
|
||||
* @example
|
||||
* const fitViewOptions = {
|
||||
* padding: 0.1,
|
||||
@@ -439,30 +480,35 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
* };
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* If you set this option to false, users will need to drag the connection line to the target
|
||||
* handle to create a connection.
|
||||
*/
|
||||
connectOnClick?: boolean;
|
||||
/** Set position of the attribution
|
||||
/**
|
||||
* Set position of the attribution
|
||||
* @default 'bottom-right'
|
||||
* @example 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'
|
||||
*/
|
||||
attributionPosition?: PanelPosition;
|
||||
/** By default, we render a small attribution in the corner of your flows that links back to the project.
|
||||
/**
|
||||
* By default, we render a small attribution in the corner of your flows that links back to the project.
|
||||
*
|
||||
* Anyone is free to remove this attribution whether they're a Pro subscriber or not
|
||||
* but we ask that you take a quick look at our {@link https://reactflow.dev/learn/troubleshooting/remove-attribution | removing attribution guide}
|
||||
* before doing so.
|
||||
*/
|
||||
proOptions?: ProOptions;
|
||||
/** Enabling this option will raise the z-index of nodes when they are selected.
|
||||
/**
|
||||
* Enabling this option will raise the z-index of nodes when they are selected.
|
||||
* @default true
|
||||
*/
|
||||
elevateNodesOnSelect?: boolean;
|
||||
/** Enabling this option will raise the z-index of edges when they are selected.
|
||||
/**
|
||||
* Enabling this option will raise the z-index of edges when they are selected.
|
||||
* @default true
|
||||
*/
|
||||
elevateEdgesOnSelect?: boolean;
|
||||
@@ -471,36 +517,43 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
* @default false
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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.
|
||||
* You might use it for additional logging or to show a message to the user.
|
||||
*/
|
||||
onError?: OnError;
|
||||
/** This callback can be used to validate a new connection
|
||||
/**
|
||||
* This callback can be used to validate a new connection
|
||||
*
|
||||
* If you return false, the edge will not be added to your flow.
|
||||
* If you have custom connection logic its preferred to use this callback over the isValidConnection prop on the handle component for performance reasons.
|
||||
* @default (connection: Connection) => true
|
||||
*/
|
||||
isValidConnection?: IsValidConnection<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.
|
||||
* @default 1
|
||||
@@ -510,12 +563,14 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
width?: number;
|
||||
/** Sets a fixed height for the flow */
|
||||
height?: number;
|
||||
/** Controls color scheme used for styling the flow
|
||||
/**
|
||||
* Controls color scheme used for styling the flow
|
||||
* @default 'system'
|
||||
* @example 'system' | 'light' | 'dark'
|
||||
*/
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,9 @@ import type {
|
||||
|
||||
import { EdgeTypes, InternalNode, Node } from '.';
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
export type EdgeLabelOptions = {
|
||||
label?: string | ReactNode;
|
||||
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
|
||||
*/
|
||||
export type Edge<
|
||||
@@ -91,6 +95,11 @@ export type EdgeWrapperProps<EdgeType extends Edge = Edge> = {
|
||||
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 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
|
||||
* @expand
|
||||
*/
|
||||
export type EdgeProps<EdgeType extends Edge = Edge> = Pick<
|
||||
EdgeType,
|
||||
@@ -121,6 +132,7 @@ export type EdgeProps<EdgeType extends Edge = Edge> = Pick<
|
||||
/**
|
||||
* BaseEdge component props
|
||||
* @public
|
||||
* @expand
|
||||
*/
|
||||
export type BaseEdgeProps = Omit<SVGAttributes<SVGPathElement>, 'd'> &
|
||||
EdgeLabelOptions & {
|
||||
@@ -137,6 +149,7 @@ export type BaseEdgeProps = Omit<SVGAttributes<SVGPathElement>, 'd'> &
|
||||
/**
|
||||
* Helper type for edge components that get exported by the library
|
||||
* @public
|
||||
* @expand
|
||||
*/
|
||||
export type EdgeComponentProps = EdgePosition &
|
||||
EdgeLabelOptions & {
|
||||
@@ -156,35 +169,47 @@ export type EdgeComponentWithPathOptions<PathOptions> = EdgeComponentProps & {
|
||||
/**
|
||||
* BezierEdge component props
|
||||
* @public
|
||||
* @expand
|
||||
*/
|
||||
export type BezierEdgeProps = EdgeComponentWithPathOptions<BezierPathOptions>;
|
||||
|
||||
/**
|
||||
* SmoothStepEdge component props
|
||||
* @public
|
||||
* @expand
|
||||
*/
|
||||
export type SmoothStepEdgeProps = EdgeComponentWithPathOptions<SmoothStepPathOptions>;
|
||||
|
||||
/**
|
||||
* StepEdge component props
|
||||
* @public
|
||||
* @expand
|
||||
*/
|
||||
export type StepEdgeProps = EdgeComponentWithPathOptions<StepPathOptions>;
|
||||
|
||||
/**
|
||||
* StraightEdge component props
|
||||
* @public
|
||||
* @expand
|
||||
*/
|
||||
export type StraightEdgeProps = Omit<EdgeComponentProps, 'sourcePosition' | 'targetPosition'>;
|
||||
|
||||
/**
|
||||
* SimpleBezier component props
|
||||
* @public
|
||||
* @expand
|
||||
*/
|
||||
export type SimpleBezierEdgeProps = EdgeComponentProps;
|
||||
|
||||
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> = {
|
||||
connectionLineStyle?: CSSProperties;
|
||||
connectionLineType: ConnectionLineType;
|
||||
|
||||
@@ -18,11 +18,44 @@ import {
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 OnNodesDelete<NodeType extends Node = Node> = (nodes: NodeType[]) => 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: {
|
||||
nodes: NodeType[];
|
||||
edges: EdgeType[];
|
||||
@@ -39,6 +72,7 @@ export type NodeTypes = Record<
|
||||
}
|
||||
>
|
||||
>;
|
||||
|
||||
export type EdgeTypes = Record<
|
||||
string,
|
||||
ComponentType<
|
||||
@@ -64,12 +98,23 @@ export type OnSelectionChangeParams = {
|
||||
export type OnSelectionChangeFunc = (params: OnSelectionChangeParams) => void;
|
||||
|
||||
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 FitView = (fitViewOptions?: FitViewOptions) => Promise<boolean>;
|
||||
export type OnInit<NodeType extends Node = Node, EdgeType extends Edge = Edge> = (
|
||||
reactFlowInstance: ReactFlowInstance<NodeType, EdgeType>
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
export type ViewportHelperFunctions = {
|
||||
/**
|
||||
* Zooms viewport in by 1.2.
|
||||
|
||||
@@ -13,6 +13,9 @@ export type DeleteElementsOptions = {
|
||||
edges?: (Edge | { id: Edge['id'] })[];
|
||||
};
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
|
||||
/**
|
||||
* Returns nodes.
|
||||
@@ -217,6 +220,14 @@ export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge =
|
||||
}) => 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<
|
||||
NodeType,
|
||||
EdgeType
|
||||
|
||||
@@ -4,7 +4,10 @@ import type { CoordinateExtent, NodeBase, OnError, NodeProps as NodePropsBase, I
|
||||
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
|
||||
*/
|
||||
export type Node<
|
||||
@@ -18,9 +21,10 @@ export type Node<
|
||||
};
|
||||
|
||||
/**
|
||||
* The node data structure that gets used for internal nodes.
|
||||
* There are some data structures added under node.internal
|
||||
* that are needed for tracking some properties
|
||||
* The `InternalNode` type is identical to the base [`Node`](/api-references/types/node)
|
||||
* type but is extended with some additional properties used internally.
|
||||
* Some functions and callbacks that return nodes may return an `InternalNode`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type InternalNode<NodeType extends Node = Node> = InternalNodeBase<NodeType>;
|
||||
@@ -56,8 +60,46 @@ export type NodeWrapperProps<NodeType extends Node> = {
|
||||
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 =
|
||||
| Node<{ label: string }, 'input' | 'output' | 'default'>
|
||||
| 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>;
|
||||
|
||||
@@ -11,13 +11,17 @@ import {
|
||||
} from '@xyflow/system';
|
||||
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 then applies the changes and returns the updated elements.
|
||||
/*
|
||||
* 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 then applies the changes and returns the updated elements.
|
||||
*/
|
||||
function applyChanges(changes: any[], elements: any[]): 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 addItemChanges: any[] = [];
|
||||
|
||||
@@ -26,15 +30,19 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
||||
addItemChanges.push(change);
|
||||
continue;
|
||||
} 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]);
|
||||
} else {
|
||||
const elementChanges = changesMap.get(change.id);
|
||||
|
||||
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);
|
||||
} else {
|
||||
changesMap.set(change.id, [change]);
|
||||
@@ -45,8 +53,10 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
||||
for (const element of elements) {
|
||||
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) {
|
||||
updatedElements.push(element);
|
||||
continue;
|
||||
@@ -62,9 +72,11 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
||||
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
|
||||
/// each _mutate_ this object, so there's only ever one copy.
|
||||
/**
|
||||
* 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
|
||||
* each _mutate_ this object, so there's only ever one copy.
|
||||
*/
|
||||
const updatedElement = { ...element };
|
||||
|
||||
for (const change of changes) {
|
||||
@@ -74,8 +86,10 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
||||
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) {
|
||||
addItemChanges.forEach((change) => {
|
||||
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.
|
||||
* @public
|
||||
* @remarks Various events on the <ReactFlow /> component can produce an {@link NodeChange} that describes how to update the edges of your flow in some way.
|
||||
If you don't need any custom behaviour, this util can be used to take an array of these changes and apply them to your edges.
|
||||
* @param changes - Array of changes to apply
|
||||
* @param nodes - Array of nodes to apply the changes to
|
||||
* @returns Array of updated nodes
|
||||
* @example
|
||||
* const onNodesChange = useCallback(
|
||||
(changes) => {
|
||||
setNodes((oldNodes) => applyNodeChanges(changes, oldNodes));
|
||||
},
|
||||
[setNodes],
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactFLow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
|
||||
);
|
||||
*```tsx
|
||||
*import { useState, useCallback } from 'react';
|
||||
*import { ReactFlow, applyNodeChanges, type Node, type Edge, type OnNodesChange } from '@xyflow/react';
|
||||
*
|
||||
*export default function Flow() {
|
||||
* const [nodes, setNodes] = useState<Node[]>([]);
|
||||
* const [edges, setEdges] = useState<Edge[]>([]);
|
||||
* const onNodesChange: OnNodesChange = useCallback(
|
||||
* (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>(
|
||||
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.
|
||||
* @public
|
||||
* @remarks Various events on the <ReactFlow /> component can produce an {@link EdgeChange} that describes how to update the edges of your flow in some way.
|
||||
If you don't need any custom behaviour, this util can be used to take an array of these changes and apply them to your edges.
|
||||
* @param changes - Array of changes to apply
|
||||
* @param edges - Array of edge to apply the changes to
|
||||
* @returns Array of updated edges
|
||||
* @example
|
||||
* ```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(
|
||||
(changes) => {
|
||||
setEdges((oldEdges) => applyEdgeChanges(changes, oldEdges));
|
||||
},
|
||||
[setEdges],
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactFlow nodes={nodes} edges={edges} onEdgesChange={onEdgesChange} />
|
||||
);
|
||||
* (changes) => {
|
||||
* setEdges((oldEdges) => applyEdgeChanges(changes, oldEdges));
|
||||
* },
|
||||
* [setEdges],
|
||||
* );
|
||||
*
|
||||
* return (
|
||||
* <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>(
|
||||
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
|
||||
if (!(item.selected === undefined && !willBeSelected) && item.selected !== willBeSelected) {
|
||||
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,
|
||||
// in order to have only one selected node at a time - the onNodesChange callback comes too late here :/
|
||||
/*
|
||||
* 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,
|
||||
* in order to have only one selected node at a time - the onNodesChange callback comes too late here :/
|
||||
*/
|
||||
item.selected = willBeSelected;
|
||||
}
|
||||
changes.push(createSelectionChange(item.id, willBeSelected));
|
||||
|
||||
@@ -4,21 +4,45 @@ import { isNodeBase, isEdgeBase } from '@xyflow/system';
|
||||
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
|
||||
* @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Node if it returns true
|
||||
* @param element - The element to test
|
||||
* @returns A boolean indicating whether the element is an Node
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
*import { isNode } from '@xyflow/react';
|
||||
*
|
||||
*if (isNode(node)) {
|
||||
* // ..
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export const isNode = <NodeType extends Node = Node>(element: unknown): element is NodeType =>
|
||||
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
|
||||
* @remarks In TypeScript this is a type guard that will narrow the type of whatever you pass in to Edge if it returns true
|
||||
* @param element - The element to test
|
||||
* @returns A boolean indicating whether the element is an Edge
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
*import { isEdge } from '@xyflow/react';
|
||||
*
|
||||
*if (isEdge(edge)) {
|
||||
* // ..
|
||||
*}
|
||||
*```
|
||||
*/
|
||||
export const isEdge = <EdgeType extends Edge = Edge>(element: unknown): element is EdgeType =>
|
||||
isEdgeBase<EdgeType>(element);
|
||||
|
||||
Reference in New Issue
Block a user