7
.changeset/brown-apples-deliver.md
Normal file
7
.changeset/brown-apples-deliver.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@xyflow/react': patch
|
||||
'@xyflow/svelte': patch
|
||||
'@xyflow/system': patch
|
||||
---
|
||||
|
||||
Add more TSDocs to components, hooks, utils funcs and types
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
useEdgesState,
|
||||
useOnSelectionChange,
|
||||
OnSelectionChangeParams,
|
||||
OnSelectionChangeFunc,
|
||||
} from '@xyflow/react';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -11,7 +11,9 @@ import type {
|
||||
import type { Node } from '$lib/types';
|
||||
|
||||
/**
|
||||
* The Edge type is mainly used for the `edges` that get passed to the SvelteFlow component.
|
||||
* An `Edge` is the complete description with everything React Flow needs
|
||||
*to know in order to render it.
|
||||
* @public
|
||||
*/
|
||||
export type Edge<
|
||||
EdgeData extends Record<string, unknown> = Record<string, unknown>,
|
||||
|
||||
@@ -42,7 +42,10 @@ export type NodeReplaceChange<NodeType extends NodeBase = NodeBase> = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Union type of all possible node changes.
|
||||
* The [`onNodesChange`](/api-reference/react-flow#on-nodes-change) callback takes
|
||||
*an array of `NodeChange` objects that you should use to update your flow's state.
|
||||
*The `NodeChange` type is a union of six different object types that represent that
|
||||
*various ways an node can change in a flow.
|
||||
* @public
|
||||
*/
|
||||
export type NodeChange<NodeType extends NodeBase = NodeBase> =
|
||||
@@ -67,6 +70,14 @@ export type EdgeReplaceChange<EdgeType extends EdgeBase = EdgeBase> = {
|
||||
type: 'replace';
|
||||
};
|
||||
|
||||
/**
|
||||
* The [`onEdgesChange`](/api-reference/react-flow#on-edges-change) callback takes
|
||||
*an array of `EdgeChange` objects that you should use to update your flow's state.
|
||||
*The `EdgeChange` type is a union of four different object types that represent that
|
||||
*various ways an edge can change in a flow.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type EdgeChange<EdgeType extends EdgeBase = EdgeBase> =
|
||||
| EdgeSelectionChange
|
||||
| EdgeRemoveChange
|
||||
|
||||
@@ -12,11 +12,13 @@ export type EdgeBase<
|
||||
source: string;
|
||||
/** Id of target node */
|
||||
target: string;
|
||||
/** Id of source handle
|
||||
/**
|
||||
* Id of source handle
|
||||
* only needed if there are multiple handles per node
|
||||
*/
|
||||
sourceHandle?: string | null;
|
||||
/** Id of target handle
|
||||
/**
|
||||
* Id of target handle
|
||||
* only needed if there are multiple handles per node
|
||||
*/
|
||||
targetHandle?: string | null;
|
||||
@@ -27,11 +29,13 @@ export type EdgeBase<
|
||||
/** Arbitrary data passed to an edge */
|
||||
data?: EdgeData;
|
||||
selected?: boolean;
|
||||
/** Set the marker on the beginning of an edge
|
||||
/**
|
||||
* Set the marker on the beginning of an edge
|
||||
* @example 'arrow', 'arrowclosed' or custom marker
|
||||
*/
|
||||
markerStart?: EdgeMarkerType;
|
||||
/** Set the marker on the end of an edge
|
||||
/**
|
||||
* Set the marker on the end of an edge
|
||||
* @example 'arrow', 'arrowclosed' or custom marker
|
||||
*/
|
||||
markerEnd?: EdgeMarkerType;
|
||||
@@ -54,11 +58,24 @@ export type BezierPathOptions = {
|
||||
curvature?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
export type DefaultEdgeOptionsBase<EdgeType extends EdgeBase> = Omit<
|
||||
EdgeType,
|
||||
'id' | 'source' | 'target' | 'sourceHandle' | 'targetHandle' | 'selected'
|
||||
>;
|
||||
|
||||
/**
|
||||
* If you set the `connectionLineType` prop on your [`<ReactFlow />`](/api-reference/react-flow#connection-connectionLineType)
|
||||
*component, it will dictate the style of connection line rendered when creating
|
||||
*new edges.
|
||||
*
|
||||
* @public
|
||||
*
|
||||
* @remarks If you choose to render a custom connection line component, this value will be
|
||||
*passed to your component as part of its [`ConnectionLineComponentProps`](/api-reference/types/connection-line-component-props).
|
||||
*/
|
||||
export enum ConnectionLineType {
|
||||
Bezier = 'default',
|
||||
Straight = 'straight',
|
||||
@@ -67,6 +84,13 @@ export enum ConnectionLineType {
|
||||
SimpleBezier = 'simplebezier',
|
||||
}
|
||||
|
||||
/**
|
||||
* Edges can optionally have markers at the start and end of an edge. The `EdgeMarker`
|
||||
*type is used to configure those markers! Check the docs for [`MarkerType`](/api-reference/types/marker-type)
|
||||
*for details on what types of edge marker are available.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type EdgeMarker = {
|
||||
type: MarkerType;
|
||||
color?: string;
|
||||
@@ -79,6 +103,12 @@ export type EdgeMarker = {
|
||||
|
||||
export type EdgeMarkerType = string | EdgeMarker;
|
||||
|
||||
/**
|
||||
* Edges may optionally have a marker on either end. The MarkerType type enumerates
|
||||
* the options available to you when configuring a given marker.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export enum MarkerType {
|
||||
Arrow = 'arrow',
|
||||
ArrowClosed = 'arrowclosed',
|
||||
@@ -88,6 +118,9 @@ export type MarkerProps = EdgeMarker & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
export type EdgePosition = {
|
||||
sourceX: number;
|
||||
sourceY: number;
|
||||
|
||||
@@ -26,6 +26,13 @@ export type SetViewport = (viewport: Viewport, options?: ViewportHelperFunctionO
|
||||
export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => Promise<boolean>;
|
||||
export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => Promise<boolean>;
|
||||
|
||||
/**
|
||||
* The `Connection` type is the basic minimal description of an [`Edge`](/api-reference/types/edge)
|
||||
* between two nodes. The [`addEdge`](/api-reference/utils/add-edge) util can be used to upgrade
|
||||
* a `Connection` to an [`Edge`](/api-reference/types/edge).
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type Connection = {
|
||||
source: string;
|
||||
target: string;
|
||||
@@ -33,15 +40,28 @@ export type Connection = {
|
||||
targetHandle: string | null;
|
||||
};
|
||||
|
||||
// TODO: remove in next version
|
||||
/**
|
||||
* The `HandleConnection` type is an extention of a basic [Connection](/api-reference/types/connection) that includes the `edgeId`.
|
||||
*/
|
||||
export type HandleConnection = Connection & {
|
||||
edgeId: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The `NodeConnection` type is an extention of a basic [Connection](/api-reference/types/connection) that includes the `edgeId`.
|
||||
*
|
||||
*/
|
||||
export type NodeConnection = Connection & {
|
||||
edgeId: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The `ConnectionMode` is used to set the mode of connection between nodes.
|
||||
* The `Strict` mode is the default one and only allows source to target edges.
|
||||
* `Loose` mode allows source to source and target to target edges as well.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export enum ConnectionMode {
|
||||
Strict = 'strict',
|
||||
Loose = 'loose',
|
||||
@@ -59,6 +79,9 @@ export type OnConnectEnd = (event: MouseEvent | TouchEvent, connectionState: Fin
|
||||
|
||||
export type IsValidConnection = (edge: EdgeBase | Connection) => boolean;
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
export type FitViewParamsBase<NodeType extends NodeBase> = {
|
||||
nodes: Map<string, InternalNodeBase<NodeType>>;
|
||||
width: number;
|
||||
@@ -68,6 +91,9 @@ export type FitViewParamsBase<NodeType extends NodeBase> = {
|
||||
maxZoom: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
export type FitViewOptionsBase<NodeType extends NodeBase = NodeBase> = {
|
||||
padding?: number;
|
||||
includeHiddenNodes?: boolean;
|
||||
@@ -77,6 +103,17 @@ export type FitViewOptionsBase<NodeType extends NodeBase = NodeBase> = {
|
||||
nodes?: (NodeType | { id: string })[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Internally, React Flow maintains a coordinate system that is independent of the
|
||||
* rest of the page. The `Viewport` type tells you where in that system your flow
|
||||
* is currently being display at and how zoomed in or out it is.
|
||||
*
|
||||
* @public
|
||||
* @remarks A `Transform` has the same properties as the viewport, but they represent
|
||||
* different things. Make sure you don't get them muddled up or things will start
|
||||
* to look weird!
|
||||
*
|
||||
*/
|
||||
export type Viewport = {
|
||||
x: number;
|
||||
y: number;
|
||||
@@ -87,12 +124,23 @@ export type KeyCode = string | Array<string>;
|
||||
|
||||
export type SnapGrid = [number, number];
|
||||
|
||||
/**
|
||||
* This enum is used to set the different modes of panning the viewport when the
|
||||
* user scrolls. The `Free` mode allows the user to pan in any direction by scrolling
|
||||
* with a device like a trackpad. The `Vertical` and `Horizontal` modes restrict
|
||||
* scroll panning to only the vertical or horizontal axis, respectively.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export enum PanOnScrollMode {
|
||||
Free = 'free',
|
||||
Vertical = 'vertical',
|
||||
Horizontal = 'horizontal',
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
export type ViewportHelperFunctionOptions = {
|
||||
duration?: number;
|
||||
};
|
||||
@@ -113,6 +161,14 @@ export type D3ZoomHandler = (this: Element, event: any, d: unknown) => void;
|
||||
|
||||
export type UpdateNodeInternals = (nodeId: string | string[]) => void;
|
||||
|
||||
/**
|
||||
* This type is mostly used to help position things on top of the flow viewport. For
|
||||
* example both the [`<MiniMap />`](/api-reference/components/minimap) and
|
||||
* [`<Controls />`](/api-reference/components/controls) components take a `position`
|
||||
* prop of this type.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type PanelPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
||||
|
||||
export type ProOptions = {
|
||||
@@ -174,6 +230,13 @@ export type ConnectionInProgress<NodeType extends InternalNodeBase = InternalNod
|
||||
toPosition: Position;
|
||||
toNode: NodeType | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* The `ConnectionState` type bundles all information about an ongoing connection.
|
||||
* It is returned by the [`useConnection`](/api-reference/hooks/use-connection) hook.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ConnectionState<NodeType extends InternalNodeBase = InternalNodeBase> =
|
||||
| ConnectionInProgress<NodeType>
|
||||
| NoConnection;
|
||||
|
||||
@@ -14,11 +14,13 @@ export type Handle = {
|
||||
};
|
||||
|
||||
export type HandleProps = {
|
||||
/** Type of the handle
|
||||
/**
|
||||
* Type of the handle
|
||||
* @example HandleType.Source, HandleType.Target
|
||||
*/
|
||||
type: HandleType;
|
||||
/** Position of the handle
|
||||
/**
|
||||
* Position of the handle
|
||||
* @example Position.TopLeft, Position.TopRight,
|
||||
* Position.BottomLeft, Position.BottomRight
|
||||
*/
|
||||
@@ -29,11 +31,13 @@ export type HandleProps = {
|
||||
isConnectableStart?: boolean;
|
||||
/** Should you be able to connect to this handle */
|
||||
isConnectableEnd?: boolean;
|
||||
/** Callback if connection is valid
|
||||
/**
|
||||
* Callback if connection is valid
|
||||
* @remarks connection becomes an edge if isValidConnection returns true
|
||||
*/
|
||||
isValidConnection?: IsValidConnection;
|
||||
/** Id of the handle
|
||||
/**
|
||||
* Id of the handle
|
||||
* @remarks optional if there is only one handle of this type
|
||||
*/
|
||||
id?: string | null;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Optional } from '../utils/types';
|
||||
/**
|
||||
* Framework independent node data structure.
|
||||
*
|
||||
* @inline
|
||||
* @typeParam NodeData - type of the node data
|
||||
* @typeParam NodeType - type of the node
|
||||
*/
|
||||
@@ -13,7 +14,8 @@ export type NodeBase<
|
||||
> = {
|
||||
/** Unique id of a node */
|
||||
id: string;
|
||||
/** Position of a node on the pane
|
||||
/**
|
||||
* Position of a node on the pane
|
||||
* @example { x: 0, y: 0 }
|
||||
*/
|
||||
position: XYPosition;
|
||||
@@ -21,11 +23,13 @@ export type NodeBase<
|
||||
data: NodeData;
|
||||
/** Type of node defined in nodeTypes */
|
||||
type?: NodeType;
|
||||
/** Only relevant for default, source, target nodeType. controls source position
|
||||
/**
|
||||
* Only relevant for default, source, target nodeType. controls source position
|
||||
* @example 'right', 'left', 'top', 'bottom'
|
||||
*/
|
||||
sourcePosition?: Position;
|
||||
/** Only relevant for default, source, target nodeType. controls target position
|
||||
/**
|
||||
* Only relevant for default, source, target nodeType. controls target position
|
||||
* @example 'right', 'left', 'top', 'bottom'
|
||||
*/
|
||||
targetPosition?: Position;
|
||||
@@ -45,13 +49,15 @@ export type NodeBase<
|
||||
/** Parent node id, used for creating sub-flows */
|
||||
parentId?: string;
|
||||
zIndex?: number;
|
||||
/** Boundary a node can be moved in
|
||||
/**
|
||||
* Boundary a node can be moved in
|
||||
* @example 'parent' or [[0, 0], [100, 100]]
|
||||
*/
|
||||
extent?: 'parent' | CoordinateExtent;
|
||||
expandParent?: boolean;
|
||||
ariaLabel?: string;
|
||||
/** Origin of the node relative to it's position
|
||||
/**
|
||||
* Origin of the node relative to it's position
|
||||
* @example
|
||||
* [0.5, 0.5] // centers the node
|
||||
* [0, 0] // top left
|
||||
@@ -73,8 +79,10 @@ export type InternalNodeBase<NodeType extends NodeBase = NodeBase> = NodeType &
|
||||
internals: {
|
||||
positionAbsolute: XYPosition;
|
||||
z: number;
|
||||
/** Holds a reference to the original node object provided by the user.
|
||||
* Used as an optimization to avoid certain operations. */
|
||||
/**
|
||||
* Holds a reference to the original node object provided by the user.
|
||||
* Used as an optimization to avoid certain operations.
|
||||
*/
|
||||
userNode: NodeType;
|
||||
handleBounds?: NodeHandleBounds;
|
||||
bounds?: NodeBounds;
|
||||
@@ -82,7 +90,7 @@ export type InternalNodeBase<NodeType extends NodeBase = NodeBase> = NodeType &
|
||||
};
|
||||
|
||||
/**
|
||||
* The node data structure that gets used for the nodes prop.
|
||||
* The node data structure that gets used for the custom nodes props.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -134,10 +142,22 @@ export type NodeDragItem = {
|
||||
expandParent?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* The origin of a Node determines how it is placed relative to its own coordinates.
|
||||
* `[0, 0]` places it at the top left corner, `[0.5, 0.5]` right in the center and
|
||||
* `[1, 1]` at the bottom right of its position.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type NodeOrigin = [number, number];
|
||||
|
||||
export type OnSelectionDrag = (event: MouseEvent, nodes: NodeBase[]) => void;
|
||||
|
||||
/**
|
||||
* Type for the handles of a node
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type NodeHandle = Omit<Optional<Handle, 'width' | 'height'>, 'nodeId'>;
|
||||
|
||||
export type Align = 'center' | 'start' | 'end';
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/**
|
||||
* While [`PanelPosition`](/api-reference/types/panel-position) can be used to place a
|
||||
* component in the corners of a container, the `Position` enum is less precise and used
|
||||
* primarily in relation to edges and handles.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export enum Position {
|
||||
Left = 'left',
|
||||
Top = 'top',
|
||||
@@ -12,6 +19,11 @@ export const oppositePosition = {
|
||||
[Position.Bottom]: Position.Top,
|
||||
};
|
||||
|
||||
/**
|
||||
* All positions are stored in an object with x and y coordinates.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type XYPosition = {
|
||||
x: number;
|
||||
y: number;
|
||||
@@ -33,4 +45,14 @@ export type Box = XYPosition & {
|
||||
|
||||
export type Transform = [number, number, number];
|
||||
|
||||
/**
|
||||
* A coordinate extent represents two points in a coordinate system: one in the top
|
||||
* left corner and one in the bottom right corner. It is used to represent the
|
||||
* bounds of nodes in the flow or the bounds of the viewport.
|
||||
*
|
||||
* @public
|
||||
*
|
||||
* @remarks Props that expect a `CoordinateExtent` usually default to `[[-∞, -∞], [+∞, +∞]]`
|
||||
* to represent an unbounded extent.
|
||||
*/
|
||||
export type CoordinateExtent = [[number, number], [number, number]];
|
||||
|
||||
@@ -61,9 +61,11 @@ export const getEventPosition = (event: MouseEvent | TouchEvent, bounds?: DOMRec
|
||||
};
|
||||
};
|
||||
|
||||
// The handle bounds are calculated relative to the node element.
|
||||
// We store them in the internals object of the node in order to avoid
|
||||
// unnecessary recalculations.
|
||||
/*
|
||||
* The handle bounds are calculated relative to the node element.
|
||||
* We store them in the internals object of the node in order to avoid
|
||||
* unnecessary recalculations.
|
||||
*/
|
||||
export const getHandleBounds = (
|
||||
type: 'source' | 'target',
|
||||
nodeElement: HTMLDivElement,
|
||||
|
||||
@@ -38,8 +38,10 @@ export function getBezierEdgeCenter({
|
||||
targetControlX: number;
|
||||
targetControlY: number;
|
||||
}): [number, number, number, number] {
|
||||
// cubic bezier t=0.5 mid point, not the actual mid point, but easy to calculate
|
||||
// https://stackoverflow.com/questions/67516101/how-to-find-distance-mid-point-of-bezier-curve
|
||||
/*
|
||||
* cubic bezier t=0.5 mid point, not the actual mid point, but easy to calculate
|
||||
* https://stackoverflow.com/questions/67516101/how-to-find-distance-mid-point-of-bezier-curve
|
||||
*/
|
||||
const centerX = sourceX * 0.125 + sourceControlX * 0.375 + targetControlX * 0.375 + targetX * 0.125;
|
||||
const centerY = sourceY * 0.125 + sourceControlY * 0.375 + targetControlY * 0.375 + targetY * 0.125;
|
||||
const offsetX = Math.abs(centerX - sourceX);
|
||||
@@ -70,7 +72,9 @@ function getControlWithCurvature({ pos, x1, y1, x2, y2, c }: GetControlWithCurva
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a bezier path from source to target handle
|
||||
* The `getBezierPath` util returns everything you need to render a bezier edge
|
||||
*between two nodes.
|
||||
* @public
|
||||
* @param params.sourceX - The x position of the source handle
|
||||
* @param params.sourceY - The y position of the source handle
|
||||
* @param params.sourcePosition - The position of the source handle (default: Position.Bottom)
|
||||
@@ -80,17 +84,22 @@ function getControlWithCurvature({ pos, x1, y1, x2, y2, c }: GetControlWithCurva
|
||||
* @param params.curvature - The curvature of the bezier edge
|
||||
* @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label
|
||||
* @example
|
||||
* ```js
|
||||
* const source = { x: 0, y: 20 };
|
||||
const target = { x: 150, y: 100 };
|
||||
|
||||
const [path, labelX, labelY, offsetX, offsetY] = getBezierPath({
|
||||
sourceX: source.x,
|
||||
sourceY: source.y,
|
||||
sourcePosition: Position.Right,
|
||||
targetX: target.x,
|
||||
targetY: target.y,
|
||||
targetPosition: Position.Left,
|
||||
});
|
||||
* const target = { x: 150, y: 100 };
|
||||
*
|
||||
* const [path, labelX, labelY, offsetX, offsetY] = getBezierPath({
|
||||
* sourceX: source.x,
|
||||
* sourceY: source.y,
|
||||
* sourcePosition: Position.Right,
|
||||
* targetX: target.x,
|
||||
* targetY: target.y,
|
||||
* targetPosition: Position.Left,
|
||||
*});
|
||||
*```
|
||||
*
|
||||
* @remarks This function returns a tuple (aka a fixed-size array) to make it easier to
|
||||
*work with multiple edge paths at once.
|
||||
*/
|
||||
export function getBezierPath({
|
||||
sourceX,
|
||||
|
||||
@@ -90,12 +90,16 @@ const connectionExists = (edge: EdgeBase, edges: EdgeBase[]) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* This util is a convenience function to add a new Edge to an array of edges
|
||||
* @remarks It also performs some validation to make sure you don't add an invalid edge or duplicate an existing one.
|
||||
* This util is a convenience function to add a new Edge to an array of edges. It also performs some validation to make sure you don't add an invalid edge or duplicate an existing one.
|
||||
* @public
|
||||
* @param edgeParams - Either an Edge or a Connection you want to add
|
||||
* @param edges - The array of all current edges
|
||||
* @returns A new array of edges with the new edge added
|
||||
*
|
||||
* @remarks If an edge with the same `target` and `source` already exists (and the same
|
||||
*`targetHandle` and `sourceHandle` if those are set), then this util won't add
|
||||
*a new edge even if the `id` property is different.
|
||||
*
|
||||
*/
|
||||
export const addEdge = <EdgeType extends EdgeBase>(
|
||||
edgeParams: EdgeType | Connection,
|
||||
@@ -137,12 +141,21 @@ export type ReconnectEdgeOptions = {
|
||||
};
|
||||
|
||||
/**
|
||||
* A handy utility to reconnect an existing edge with new properties
|
||||
* A handy utility to update an existing [`Edge`](/api-reference/types/edge) with new properties.
|
||||
*This searches your edge array for an edge with a matching `id` and updates its
|
||||
*properties with the connection you provide.
|
||||
* @public
|
||||
* @param oldEdge - The edge you want to update
|
||||
* @param newConnection - The new connection you want to update the edge with
|
||||
* @param edges - The array of all current edges
|
||||
* @param options.shouldReplaceId - should the id of the old edge be replaced with the new connection id
|
||||
* @returns the updated edges array
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
*const onReconnect = useCallback(
|
||||
* (oldEdge: Edge, newConnection: Connection) => setEdges((els) => reconnectEdge(oldEdge, newConnection, els)),[]);
|
||||
*```
|
||||
*/
|
||||
export const reconnectEdge = <EdgeType extends EdgeBase>(
|
||||
oldEdge: EdgeType,
|
||||
|
||||
@@ -38,8 +38,10 @@ const getDirection = ({
|
||||
|
||||
const distance = (a: XYPosition, b: XYPosition) => Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2));
|
||||
|
||||
// ith this function we try to mimic a orthogonal edge routing behaviour
|
||||
// It's not as good as a real orthogonal edge routing but it's faster and good enough as a default for step and smooth step edges
|
||||
/*
|
||||
* ith this function we try to mimic a orthogonal edge routing behaviour
|
||||
* It's not as good as a real orthogonal edge routing but it's faster and good enough as a default for step and smooth step edges
|
||||
*/
|
||||
function getPoints({
|
||||
source,
|
||||
sourcePosition = Position.Bottom,
|
||||
@@ -83,16 +85,20 @@ function getPoints({
|
||||
if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) {
|
||||
centerX = center.x ?? defaultCenterX;
|
||||
centerY = center.y ?? defaultCenterY;
|
||||
// --->
|
||||
// |
|
||||
// >---
|
||||
/*
|
||||
* --->
|
||||
* |
|
||||
* >---
|
||||
*/
|
||||
const verticalSplit: XYPosition[] = [
|
||||
{ x: centerX, y: sourceGapped.y },
|
||||
{ x: centerX, y: targetGapped.y },
|
||||
];
|
||||
// |
|
||||
// ---
|
||||
// |
|
||||
/*
|
||||
* |
|
||||
* ---
|
||||
* |
|
||||
*/
|
||||
const horizontalSplit: XYPosition[] = [
|
||||
{ x: sourceGapped.x, y: centerY },
|
||||
{ x: targetGapped.x, y: centerY },
|
||||
@@ -191,7 +197,10 @@ function getBend(a: XYPosition, b: XYPosition, c: XYPosition, size: number): str
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a smooth step path from source to target handle
|
||||
* The `getSmoothStepPath` util returns everything you need to render a stepped path
|
||||
*between two nodes. The `borderRadius` property can be used to choose how rounded
|
||||
*the corners of those steps are.
|
||||
* @public
|
||||
* @param params.sourceX - The x position of the source handle
|
||||
* @param params.sourceY - The y position of the source handle
|
||||
* @param params.sourcePosition - The position of the source handle (default: Position.Bottom)
|
||||
@@ -200,17 +209,20 @@ function getBend(a: XYPosition, b: XYPosition, c: XYPosition, size: number): str
|
||||
* @param params.targetPosition - The position of the target handle (default: Position.Top)
|
||||
* @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label
|
||||
* @example
|
||||
* ```js
|
||||
* const source = { x: 0, y: 20 };
|
||||
const target = { x: 150, y: 100 };
|
||||
|
||||
const [path, labelX, labelY, offsetX, offsetY] = getSmoothStepPath({
|
||||
sourceX: source.x,
|
||||
sourceY: source.y,
|
||||
sourcePosition: Position.Right,
|
||||
targetX: target.x,
|
||||
targetY: target.y,
|
||||
targetPosition: Position.Left,
|
||||
});
|
||||
* const target = { x: 150, y: 100 };
|
||||
*
|
||||
* const [path, labelX, labelY, offsetX, offsetY] = getSmoothStepPath({
|
||||
* sourceX: source.x,
|
||||
* sourceY: source.y,
|
||||
* sourcePosition: Position.Right,
|
||||
* targetX: target.x,
|
||||
* targetY: target.y,
|
||||
* targetPosition: Position.Left,
|
||||
* });
|
||||
* ```
|
||||
* @remarks This function returns a tuple (aka a fixed-size array) to make it easier to work with multiple edge paths at once.
|
||||
*/
|
||||
export function getSmoothStepPath({
|
||||
sourceX,
|
||||
|
||||
@@ -8,24 +8,28 @@ export type GetStraightPathParams = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a straight path from source to target handle
|
||||
* Calculates the straight line path between two points.
|
||||
* @public
|
||||
* @param params.sourceX - The x position of the source handle
|
||||
* @param params.sourceY - The y position of the source handle
|
||||
* @param params.targetX - The x position of the target handle
|
||||
* @param params.targetY - The y position of the target handle
|
||||
* @returns A path string you can use in an SVG, the labelX and labelY position (center of path) and offsetX, offsetY between source handle and label
|
||||
* @example
|
||||
* ```js
|
||||
* const source = { x: 0, y: 20 };
|
||||
const target = { x: 150, y: 100 };
|
||||
|
||||
const [path, labelX, labelY, offsetX, offsetY] = getStraightPath({
|
||||
sourceX: source.x,
|
||||
sourceY: source.y,
|
||||
sourcePosition: Position.Right,
|
||||
targetX: target.x,
|
||||
targetY: target.y,
|
||||
targetPosition: Position.Left,
|
||||
});
|
||||
* const target = { x: 150, y: 100 };
|
||||
*
|
||||
* const [path, labelX, labelY, offsetX, offsetY] = getStraightPath({
|
||||
* sourceX: source.x,
|
||||
* sourceY: source.y,
|
||||
* sourcePosition: Position.Right,
|
||||
* targetX: target.x,
|
||||
* targetY: target.y,
|
||||
* targetPosition: Position.Left,
|
||||
* });
|
||||
* ```
|
||||
* @remarks This function returns a tuple (aka a fixed-size array) to make it easier to work with multiple edge paths at once.
|
||||
*/
|
||||
export function getStraightPath({
|
||||
sourceX,
|
||||
|
||||
@@ -186,8 +186,8 @@ export const rendererPointToPoint = ({ x, y }: XYPosition, [tx, ty, tScale]: Tra
|
||||
* @returns A transforned {@link Viewport} that encloses the given bounds which you can pass to e.g. {@link setViewport}
|
||||
* @example
|
||||
* const { x, y, zoom } = getViewportForBounds(
|
||||
{ x: 0, y: 0, width: 100, height: 100},
|
||||
1200, 800, 0.5, 2);
|
||||
*{ x: 0, y: 0, width: 100, height: 100},
|
||||
*1200, 800, 0.5, 2);
|
||||
*/
|
||||
export const getViewportForBounds = (
|
||||
bounds: Rect,
|
||||
|
||||
@@ -54,12 +54,27 @@ export const isInternalNodeBase = <NodeType extends InternalNodeBase = InternalN
|
||||
): element is NodeType => 'id' in element && 'internals' in element && !('source' in element) && !('target' in element);
|
||||
|
||||
/**
|
||||
* Pass in a node, and get connected nodes where edge.source === node.id
|
||||
* This util is used to tell you what nodes, if any, are connected to the given node
|
||||
* as the _target_ of an edge.
|
||||
* @public
|
||||
* @param node - The node to get the connected nodes from
|
||||
* @param nodes - The array of all nodes
|
||||
* @param edges - The array of all edges
|
||||
* @returns An array of nodes that are connected over eges where the source is the given node
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
*import { getOutgoers } from '@xyflow/react';
|
||||
*
|
||||
*const nodes = [];
|
||||
*const edges = [];
|
||||
*
|
||||
*const outgoers = getOutgoers(
|
||||
* { id: '1', position: { x: 0, y: 0 }, data: { label: 'node' } },
|
||||
* nodes,
|
||||
* edges,
|
||||
*);
|
||||
*```
|
||||
*/
|
||||
export const getOutgoers = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
|
||||
node: NodeType | { id: string },
|
||||
@@ -81,12 +96,27 @@ export const getOutgoers = <NodeType extends NodeBase = NodeBase, EdgeType exten
|
||||
};
|
||||
|
||||
/**
|
||||
* Pass in a node, and get connected nodes where edge.target === node.id
|
||||
* This util is used to tell you what nodes, if any, are connected to the given node
|
||||
* as the _source_ of an edge.
|
||||
* @public
|
||||
* @param node - The node to get the connected nodes from
|
||||
* @param nodes - The array of all nodes
|
||||
* @param edges - The array of all edges
|
||||
* @returns An array of nodes that are connected over eges where the target is the given node
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
*import { getIncomers } from '@xyflow/react';
|
||||
*
|
||||
*const nodes = [];
|
||||
*const edges = [];
|
||||
*
|
||||
*const incomers = getIncomers(
|
||||
* { id: '1', position: { x: 0, y: 0 }, data: { label: 'node' } },
|
||||
* nodes,
|
||||
* edges,
|
||||
*);
|
||||
*```
|
||||
*/
|
||||
export const getIncomers = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
|
||||
node: NodeType | { id: string },
|
||||
@@ -124,12 +154,40 @@ export type GetNodesBoundsParams<NodeType extends NodeBase = NodeBase> = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal function for determining a bounding box that contains all given nodes in an array.
|
||||
* Returns the bounding box that contains all the given nodes in an array. This can
|
||||
* be useful when combined with [`getViewportForBounds`](/api-reference/utils/get-viewport-for-bounds)
|
||||
* to calculate the correct transform to fit the given nodes in a viewport.
|
||||
* @public
|
||||
* @remarks Useful when combined with {@link getViewportForBounds} to calculate the correct transform to fit the given nodes in a viewport.
|
||||
* @param nodes - Nodes to calculate the bounds for
|
||||
* @param params.nodeOrigin - Origin of the nodes: [0, 0] - top left, [0.5, 0.5] - center
|
||||
* @returns Bounding box enclosing all nodes
|
||||
*
|
||||
* @remarks This function was previously called `getRectOfNodes`
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
*import { getNodesBounds } from '@xyflow/react';
|
||||
*
|
||||
*const nodes = [
|
||||
* {
|
||||
* id: 'a',
|
||||
* position: { x: 0, y: 0 },
|
||||
* data: { label: 'a' },
|
||||
* width: 50,
|
||||
* height: 25,
|
||||
* },
|
||||
* {
|
||||
* id: 'b',
|
||||
* position: { x: 100, y: 100 },
|
||||
* data: { label: 'b' },
|
||||
* width: 50,
|
||||
* height: 25,
|
||||
* },
|
||||
*];
|
||||
*
|
||||
*const bounds = getNodesBounds(nodes);
|
||||
*```
|
||||
*/
|
||||
export const getNodesBounds = <NodeType extends NodeBase = NodeBase>(
|
||||
nodes: (NodeType | InternalNodeBase<NodeType> | string)[],
|
||||
@@ -238,10 +296,30 @@ export const getNodesInside = <NodeType extends NodeBase = NodeBase>(
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all connecting edges for a given set of nodes
|
||||
* This utility filters an array of edges, keeping only those where either the source or target
|
||||
* node is present in the given array of nodes.
|
||||
* @public
|
||||
* @param nodes - Nodes you want to get the connected edges for
|
||||
* @param edges - All edges
|
||||
* @returns Array of edges that connect any of the given nodes with each other
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
*import { getConnectedEdges } from '@xyflow/react';
|
||||
*
|
||||
*const nodes = [
|
||||
* { id: 'a', position: { x: 0, y: 0 } },
|
||||
* { id: 'b', position: { x: 100, y: 0 } },
|
||||
*];
|
||||
*
|
||||
*const edges = [
|
||||
* { id: 'a->c', source: 'a', target: 'c' },
|
||||
* { id: 'c->d', source: 'c', target: 'd' },
|
||||
*];
|
||||
*
|
||||
*const connectedEdges = getConnectedEdges(nodes, edges);
|
||||
* // => [{ id: 'a->c', source: 'a', target: 'c' }]
|
||||
*```
|
||||
*/
|
||||
export const getConnectedEdges = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
|
||||
nodes: NodeType[],
|
||||
|
||||
@@ -15,8 +15,10 @@ export function getNodeToolbarTransform(
|
||||
alignmentOffset = 1;
|
||||
}
|
||||
|
||||
// position === Position.Top
|
||||
// we set the x any y position of the toolbar based on the nodes position
|
||||
/*
|
||||
* position === Position.Top
|
||||
* we set the x any y position of the toolbar based on the nodes position
|
||||
*/
|
||||
let pos = [
|
||||
(nodeRect.x + nodeRect.width * alignmentOffset) * viewport.zoom + viewport.x,
|
||||
nodeRect.y * viewport.zoom + viewport.y - offset,
|
||||
|
||||
@@ -276,8 +276,10 @@ export function handleExpandParent(
|
||||
},
|
||||
});
|
||||
|
||||
// We move all child nodes in the oppsite direction
|
||||
// so the x,y changes of the parent do not move the children
|
||||
/*
|
||||
* We move all child nodes in the oppsite direction
|
||||
* so the x,y changes of the parent do not move the children
|
||||
*/
|
||||
parentLookup.get(parentId)?.forEach((childNode) => {
|
||||
if (!children.some((child) => child.id === childNode.id)) {
|
||||
changes.push({
|
||||
@@ -472,9 +474,11 @@ function addConnectionToLookup(
|
||||
nodeId: string,
|
||||
handleId: string | null
|
||||
) {
|
||||
// We add the connection to the connectionLookup at the following keys
|
||||
// 1. nodeId, 2. nodeId-type, 3. nodeId-type-handleId
|
||||
// If the key already exists, we add the connection to the existing map
|
||||
/*
|
||||
* We add the connection to the connectionLookup at the following keys
|
||||
* 1. nodeId, 2. nodeId-type, 3. nodeId-type-handleId
|
||||
* If the key already exists, we add the connection to the existing map
|
||||
*/
|
||||
let key = nodeId;
|
||||
const nodeMap = connectionLookup.get(key) || new Map();
|
||||
connectionLookup.set(key, nodeMap.set(connectionKey, connection));
|
||||
|
||||
@@ -140,8 +140,10 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
||||
|
||||
for (const [id, dragItem] of dragItems) {
|
||||
if (!nodeLookup.has(id)) {
|
||||
// if the node is not in the nodeLookup anymore, it was probably deleted while dragging
|
||||
// and we don't need to update it anymore
|
||||
/*
|
||||
* if the node is not in the nodeLookup anymore, it was probably deleted while dragging
|
||||
* and we don't need to update it anymore
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -150,8 +152,10 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
||||
nextPosition = snapPosition(nextPosition, snapGrid);
|
||||
}
|
||||
|
||||
// if there is selection with multiple nodes and a node extent is set, we need to adjust the node extent for each node
|
||||
// based on its position so that the node stays at it's position relative to the selection.
|
||||
/*
|
||||
* if there is selection with multiple nodes and a node extent is set, we need to adjust the node extent for each node
|
||||
* based on its position so that the node stays at it's position relative to the selection.
|
||||
*/
|
||||
let adjustedNodeExtent: CoordinateExtent = [
|
||||
[nodeExtent[0][0], nodeExtent[0][1]],
|
||||
[nodeExtent[1][0], nodeExtent[1][1]],
|
||||
|
||||
@@ -74,9 +74,11 @@ export function getDragItems<NodeType extends NodeBase>(
|
||||
return dragItems;
|
||||
}
|
||||
|
||||
// returns two params:
|
||||
// 1. the dragged node (or the first of the list, if we are dragging a node selection)
|
||||
// 2. array of selected nodes (for multi selections)
|
||||
/*
|
||||
* returns two params:
|
||||
* 1. the dragged node (or the first of the list, if we are dragging a node selection)
|
||||
* 2. array of selected nodes (for multi selections)
|
||||
*/
|
||||
export function getEventHandlerParams<NodeType extends InternalNodeBase>({
|
||||
nodeId,
|
||||
dragItems,
|
||||
@@ -112,10 +114,10 @@ export function getEventHandlerParams<NodeType extends InternalNodeBase>({
|
||||
!node
|
||||
? nodesFromDragItems[0]
|
||||
: {
|
||||
...node,
|
||||
position: dragItems.get(nodeId)?.position || node.position,
|
||||
dragging,
|
||||
},
|
||||
...node,
|
||||
position: dragItems.get(nodeId)?.position || node.position,
|
||||
dragging,
|
||||
},
|
||||
nodesFromDragItems,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -165,8 +165,10 @@ function onPointerDown(
|
||||
toNode: result.toHandle ? nodeLookup.get(result.toHandle.nodeId)! : null,
|
||||
};
|
||||
|
||||
// we don't want to trigger an update when the connection
|
||||
// is snapped to the same handle as before
|
||||
/*
|
||||
* we don't want to trigger an update when the connection
|
||||
* is snapped to the same handle as before
|
||||
*/
|
||||
if (
|
||||
isValid &&
|
||||
closestHandle &&
|
||||
@@ -190,8 +192,10 @@ function onPointerDown(
|
||||
onConnect?.(connection);
|
||||
}
|
||||
|
||||
// it's important to get a fresh reference from the store here
|
||||
// in order to get the latest state of onConnectEnd
|
||||
/*
|
||||
* it's important to get a fresh reference from the store here
|
||||
* in order to get the latest state of onConnectEnd
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { inProgress, ...connectionState } = previousConnection;
|
||||
const finalConnectionState = {
|
||||
@@ -248,8 +252,10 @@ function isValidHandle(
|
||||
|
||||
const { x, y } = getEventPosition(event);
|
||||
const handleBelow = doc.elementFromPoint(x, y);
|
||||
// we always want to prioritize the handle below the mouse cursor over the closest distance handle,
|
||||
// because it could be that the center of another handle is closer to the mouse pointer than the handle below the cursor
|
||||
/*
|
||||
* we always want to prioritize the handle below the mouse cursor over the closest distance handle,
|
||||
* because it could be that the center of another handle is closer to the mouse pointer than the handle below the cursor
|
||||
*/
|
||||
const handleToCheck = handleBelow?.classList.contains(`${lib}-flow__handle`) ? handleBelow : handleDomNode;
|
||||
|
||||
const result: Result = {
|
||||
|
||||
@@ -19,8 +19,10 @@ function getNodesWithinDistance(position: XYPosition, nodeLookup: NodeLookup, di
|
||||
return nodes;
|
||||
}
|
||||
|
||||
// this distance is used for the area around the user pointer
|
||||
// while doing a connection for finding the closest nodes
|
||||
/*
|
||||
* this distance is used for the area around the user pointer
|
||||
* while doing a connection for finding the closest nodes
|
||||
*/
|
||||
const ADDITIONAL_DISTANCE = 250;
|
||||
|
||||
export function getClosestHandle(
|
||||
|
||||
@@ -114,22 +114,22 @@ export function XYPanZoom({
|
||||
|
||||
const wheelHandler = isPanOnScroll
|
||||
? createPanOnScrollHandler({
|
||||
zoomPanValues,
|
||||
noWheelClassName,
|
||||
d3Selection,
|
||||
d3Zoom: d3ZoomInstance,
|
||||
panOnScrollMode,
|
||||
panOnScrollSpeed,
|
||||
zoomOnPinch,
|
||||
onPanZoomStart,
|
||||
onPanZoom,
|
||||
onPanZoomEnd,
|
||||
})
|
||||
zoomPanValues,
|
||||
noWheelClassName,
|
||||
d3Selection,
|
||||
d3Zoom: d3ZoomInstance,
|
||||
panOnScrollMode,
|
||||
panOnScrollSpeed,
|
||||
zoomOnPinch,
|
||||
onPanZoomStart,
|
||||
onPanZoom,
|
||||
onPanZoomEnd,
|
||||
})
|
||||
: createZoomOnScrollHandler({
|
||||
noWheelClassName,
|
||||
preventScrolling,
|
||||
d3ZoomHandler,
|
||||
});
|
||||
noWheelClassName,
|
||||
preventScrolling,
|
||||
d3ZoomHandler,
|
||||
});
|
||||
|
||||
d3Selection.on('wheel.zoom', wheelHandler, { passive: false });
|
||||
|
||||
@@ -178,9 +178,11 @@ export function XYPanZoom({
|
||||
});
|
||||
d3ZoomInstance.filter(filter);
|
||||
|
||||
// We cannot add zoomOnDoubleClick to the filter above because
|
||||
// double tapping on touch screens circumvents the filter and
|
||||
// dblclick.zoom is fired on the selection directly
|
||||
/*
|
||||
* We cannot add zoomOnDoubleClick to the filter above because
|
||||
* double tapping on touch screens circumvents the filter and
|
||||
* dblclick.zoom is fired on the selection directly
|
||||
*/
|
||||
if (zoomOnDoubleClick) {
|
||||
d3Selection.on('dblclick.zoom', d3DblClickZoomHandler);
|
||||
} else {
|
||||
|
||||
@@ -90,8 +90,10 @@ export function createPanOnScrollHandler({
|
||||
return;
|
||||
}
|
||||
|
||||
// increase scroll speed in firefox
|
||||
// firefox: deltaMode === 1; chrome: deltaMode === 0
|
||||
/*
|
||||
* increase scroll speed in firefox
|
||||
* firefox: deltaMode === 1; chrome: deltaMode === 0
|
||||
*/
|
||||
const deltaNormalize = event.deltaMode === 1 ? 20 : 1;
|
||||
let deltaX = panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize;
|
||||
let deltaY = panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize;
|
||||
@@ -114,9 +116,11 @@ export function createPanOnScrollHandler({
|
||||
|
||||
clearTimeout(zoomPanValues.panScrollTimeout);
|
||||
|
||||
// for pan on scroll we need to handle the event calls on our own
|
||||
// we can't use the start, zoom and end events from d3-zoom
|
||||
// because start and move gets called on every scroll event and not once at the beginning
|
||||
/*
|
||||
* for pan on scroll we need to handle the event calls on our own
|
||||
* we can't use the start, zoom and end events from d3-zoom
|
||||
* because start and move gets called on every scroll event and not once at the beginning
|
||||
*/
|
||||
if (!zoomPanValues.isPanScrolling) {
|
||||
zoomPanValues.isPanScrolling = true;
|
||||
|
||||
|
||||
@@ -154,8 +154,10 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X
|
||||
parentExtent = parentNode && node.extent === 'parent' ? nodeToParentExtent(parentNode) : undefined;
|
||||
}
|
||||
|
||||
// Collect all child nodes to correct their relative positions when top/left changes
|
||||
// Determine largest minimal extent the parent node is allowed to resize to
|
||||
/*
|
||||
* Collect all child nodes to correct their relative positions when top/left changes
|
||||
* Determine largest minimal extent the parent node is allowed to resize to
|
||||
*/
|
||||
childNodes = [];
|
||||
childExtent = undefined;
|
||||
|
||||
@@ -230,8 +232,10 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X
|
||||
prevValues.x = change.x;
|
||||
prevValues.y = change.y;
|
||||
|
||||
// when top/left changes, correct the relative positions of child nodes
|
||||
// so that they stay in the same position
|
||||
/*
|
||||
* when top/left changes, correct the relative positions of child nodes
|
||||
* so that they stay in the same position
|
||||
*/
|
||||
if (childNodes.length > 0) {
|
||||
const xChange = x - prevX;
|
||||
const yChange = y - prevY;
|
||||
|
||||
@@ -11,10 +11,25 @@ export type ResizeParamsWithDirection = ResizeParams & {
|
||||
direction: number[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to determine the control line position of the NodeResizer
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ControlLinePosition = 'top' | 'bottom' | 'left' | 'right';
|
||||
|
||||
/**
|
||||
* Used to determine the control position of the NodeResizer
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ControlPosition = ControlLinePosition | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
||||
|
||||
/**
|
||||
* Used to determine the variant of the resize control
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export enum ResizeControlVariant {
|
||||
Line = 'line',
|
||||
Handle = 'handle',
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
"license": "MIT",
|
||||
"main": "src/index.js",
|
||||
"devDependencies": {
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-config-turbo": "^2.4.0",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"@typescript-eslint/eslint-plugin": "^8.23.0",
|
||||
"@typescript-eslint/parser": "^8.23.0",
|
||||
"eslint-plugin-prettier": "^4.2.1"
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-config-turbo": "^2.4.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.37.4"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user