Merge pull request #5010 from xyflow/tsdocs-update

TSDoc update
This commit is contained in:
Moritz Klack
2025-02-12 16:49:36 +01:00
committed by GitHub
89 changed files with 1876 additions and 416 deletions
@@ -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,
+5 -3
View File
@@ -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';
+35 -7
View File
@@ -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,
+6 -4
View File
@@ -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) => {
+31 -3
View File
@@ -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);