@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user