Merge branch 'main' into middlewares

This commit is contained in:
Moritz Klack
2025-12-02 16:34:21 +01:00
committed by GitHub
115 changed files with 2360 additions and 426 deletions
@@ -0,0 +1,79 @@
import { useCallback } from 'react';
import cc from 'classcat';
import { shallow } from 'zustand/shallow';
import { getEdgeToolbarTransform } from '@xyflow/system';
import { EdgeLabelRenderer } from '../../components/EdgeLabelRenderer';
import { useStore } from '../../hooks/useStore';
import { Edge, ReactFlowState } from '../../types';
import type { EdgeToolbarProps } from './types';
const zoomSelector = (state: ReactFlowState) => state.transform[2];
/**
* This component can render a toolbar or tooltip to one side of a custom edge. This
* toolbar doesn't scale with the viewport so that the content stays the same size.
*
* @public
* @example
* ```jsx
* import { EdgeToolbar, BaseEdge, getBezierPath, type EdgeProps } from "@xyflow/react";
*
* export function CustomEdge({ id, data, ...props }: EdgeProps) {
* const [edgePath, centerX, centerY] = getBezierPath(props);
*
* return (
* <>
* <BaseEdge id={id} path={edgePath} />
* <EdgeToolbar edgeId={id} x={centerX} y={centerY} isVisible>
* <button onClick={() => console.log('edge', id, 'click')}}>Click me</button>
* </EdgeToolbar>
* </>
* );
* }
* ```
*/
export function EdgeToolbar({
edgeId,
x,
y,
children,
className,
style,
isVisible,
alignX = 'center',
alignY = 'center',
...rest
}: EdgeToolbarProps) {
const edgeSelector = useCallback((state: ReactFlowState): Edge | undefined => state.edgeLookup.get(edgeId), [edgeId]);
const edge = useStore(edgeSelector, shallow);
const isActive = typeof isVisible === 'boolean' ? isVisible : edge?.selected;
const zoom = useStore(zoomSelector);
if (!isActive) {
return null;
}
const zIndex = (edge?.zIndex ?? 0) + 1;
const transform = getEdgeToolbarTransform(x, y, zoom, alignX, alignY);
return (
<EdgeLabelRenderer>
<div
style={{
position: 'absolute',
transform,
zIndex,
pointerEvents: 'all',
transformOrigin: '0 0',
...style,
}}
className={cc(['react-flow__edge-toolbar', className])}
data-id={edge?.id ?? ''}
{...rest}
>
{children}
</div>
</EdgeLabelRenderer>
);
}
@@ -0,0 +1,2 @@
export { EdgeToolbar } from './EdgeToolbar';
export type { EdgeToolbarProps } from './types';
@@ -0,0 +1,14 @@
import type { HTMLAttributes, ReactNode } from 'react';
import type { EdgeToolbarBaseProps } from '@xyflow/system';
/**
* @inline
*/
export type EdgeToolbarProps = EdgeToolbarBaseProps &
HTMLAttributes<HTMLDivElement> & {
/**
* An edge toolbar must be attached to an edge.
*/
edgeId: string;
children?: ReactNode;
};
@@ -3,3 +3,4 @@ export * from './Controls';
export * from './MiniMap';
export * from './NodeResizer';
export * from './NodeToolbar';
export * from './EdgeToolbar';
@@ -69,7 +69,7 @@ const ConnectionLine = <NodeType extends Node = Node>({
CustomComponent,
isValid,
}: ConnectionLineProps<NodeType>) => {
const { inProgress, from, fromNode, fromHandle, fromPosition, to, toNode, toHandle, toPosition } =
const { inProgress, from, fromNode, fromHandle, fromPosition, to, toNode, toHandle, toPosition, pointer } =
useConnection<NodeType>();
if (!inProgress) {
@@ -92,6 +92,7 @@ const ConnectionLine = <NodeType extends Node = Node>({
connectionStatus={getConnectionStatus(isValid)}
toNode={toNode}
toHandle={toHandle}
pointer={pointer}
/>
);
}
@@ -1,4 +1,4 @@
import { useState, useMemo, useRef, type KeyboardEvent, useCallback, JSX } from 'react';
import { useState, useMemo, useRef, type KeyboardEvent, useCallback, JSX, memo } from 'react';
import cc from 'classcat';
import { shallow } from 'zustand/shallow';
import {
@@ -15,7 +15,7 @@ import { builtinEdgeTypes, nullPosition } from './utils';
import { EdgeUpdateAnchors } from './EdgeUpdateAnchors';
import type { Edge, EdgeWrapperProps } from '../../types';
export function EdgeWrapper<EdgeType extends Edge = Edge>({
function EdgeWrapper<EdgeType extends Edge = Edge>({
id,
edgesFocusable,
edgesReconnectable,
@@ -263,3 +263,5 @@ export function EdgeWrapper<EdgeType extends Edge = Edge>({
</svg>
);
}
export default memo(EdgeWrapper) as typeof EdgeWrapper;
@@ -1,4 +1,4 @@
import { type MouseEvent, type KeyboardEvent } from 'react';
import { type MouseEvent, type KeyboardEvent, memo } from 'react';
import cc from 'classcat';
import { shallow } from 'zustand/shallow';
import {
@@ -20,7 +20,7 @@ import { arrowKeyDiffs, builtinNodeTypes, getNodeInlineStyleDimensions } from '.
import { useNodeObserver } from './useNodeObserver';
import type { InternalNode, Node, NodeWrapperProps } from '../../types';
export function NodeWrapper<NodeType extends Node>({
function NodeWrapper<NodeType extends Node>({
id,
onClick,
onMouseEnter,
@@ -249,3 +249,5 @@ export function NodeWrapper<NodeType extends Node>({
</div>
);
}
export default memo(NodeWrapper) as typeof NodeWrapper;
@@ -69,7 +69,6 @@ const reactFlowFieldsToTrack = [
'onBeforeDelete',
'debug',
'autoPanSpeed',
'paneClickDistance',
'ariaLabelConfig',
] as const;
@@ -93,7 +92,6 @@ const selector = (s: ReactFlowState) => ({
setNodeExtent: s.setNodeExtent,
reset: s.reset,
setDefaultNodesAndEdges: s.setDefaultNodesAndEdges,
setPaneClickDistance: s.setPaneClickDistance,
});
const initPrevValues = {
@@ -109,7 +107,6 @@ const initPrevValues = {
elementsSelectable: true,
noPanClassName: 'nopan',
rfId: '1',
paneClickDistance: 0,
};
export function StoreUpdater<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
@@ -124,7 +121,6 @@ export function StoreUpdater<NodeType extends Node = Node, EdgeType extends Edge
setNodeExtent,
reset,
setDefaultNodesAndEdges,
setPaneClickDistance,
} = useStore(selector, shallow);
const store = useStoreApi<NodeType, EdgeType>();
@@ -155,7 +151,6 @@ export function StoreUpdater<NodeType extends Node = Node, EdgeType extends Edge
else if (fieldName === 'maxZoom') setMaxZoom(fieldValue as number);
else if (fieldName === 'translateExtent') setTranslateExtent(fieldValue as CoordinateExtent);
else if (fieldName === 'nodeExtent') setNodeExtent(fieldValue as CoordinateExtent);
else if (fieldName === 'paneClickDistance') setPaneClickDistance(fieldValue as number);
else if (fieldName === 'ariaLabelConfig')
store.setState({ ariaLabelConfig: mergeAriaLabelConfig(fieldValue as AriaLabelConfig) });
// Renamed fields
@@ -5,7 +5,7 @@ import { useStore } from '../../hooks/useStore';
import { useVisibleEdgeIds } from '../../hooks/useVisibleEdgeIds';
import MarkerDefinitions from './MarkerDefinitions';
import { GraphViewProps } from '../GraphView';
import { EdgeWrapper } from '../../components/EdgeWrapper';
import EdgeWrapper from '../../components/EdgeWrapper';
import type { Edge, ReactFlowState, Node } from '../../types';
type EdgeRendererProps<EdgeType extends Edge = Edge> = Pick<
@@ -1,4 +1,5 @@
import { memo, type ReactNode } from 'react';
import { shallow } from 'zustand/shallow';
import { useStore } from '../../hooks/useStore';
import { useGlobalKeyHandler } from '../../hooks/useGlobalKeyHandler';
@@ -72,7 +73,7 @@ function FlowRendererComponent<NodeType extends Node = Node>({
onViewportChange,
isControlledViewport,
}: FlowRendererProps<NodeType>) {
const { nodesSelectionActive, userSelectionActive } = useStore(selector);
const { nodesSelectionActive, userSelectionActive } = useStore(selector, shallow);
const selectionKeyPressed = useKeyPress(selectionKeyCode, { target: win });
const panActivationKeyPressed = useKeyPress(panActivationKeyCode, { target: win });
@@ -105,6 +106,7 @@ function FlowRendererComponent<NodeType extends Node = Node>({
onViewportChange={onViewportChange}
isControlledViewport={isControlledViewport}
paneClickDistance={paneClickDistance}
selectionOnDrag={_selectionOnDrag}
>
<Pane
onSelectionStart={onSelectionStart}
@@ -119,6 +121,7 @@ function FlowRendererComponent<NodeType extends Node = Node>({
isSelecting={!!isSelecting}
selectionMode={selectionMode}
selectionKeyPressed={selectionKeyPressed}
paneClickDistance={paneClickDistance}
selectionOnDrag={_selectionOnDrag}
>
{children}
@@ -6,7 +6,7 @@ import { useStore } from '../../hooks/useStore';
import { containerStyle } from '../../styles/utils';
import { GraphViewProps } from '../GraphView';
import { useResizeObserver } from './useResizeObserver';
import { NodeWrapper } from '../../components/NodeWrapper';
import NodeWrapper from '../../components/NodeWrapper';
import type { Node, ReactFlowState } from '../../types';
export type NodeRendererProps<NodeType extends Node> = Pick<
+63 -55
View File
@@ -1,23 +1,15 @@
/**
* The user selection rectangle gets displayed when a user drags the mouse while pressing shift
*/
import {
useRef,
type MouseEventHandler,
type MutableRefObject,
type MouseEvent as ReactMouseEvent,
type PointerEvent as ReactPointerEvent,
type WheelEvent as ReactWheelEvent,
type ReactNode,
} from 'react';
import { shallow } from 'zustand/shallow';
import cc from 'classcat';
import {
getNodesInside,
getEventPosition,
SelectionMode,
areSetsEqual,
type NodeChange,
type EdgeChange,
} from '@xyflow/system';
import { getNodesInside, getEventPosition, SelectionMode, areSetsEqual } from '@xyflow/system';
import { UserSelection } from '../../components/UserSelection';
import { containerStyle } from '../../styles/utils';
@@ -29,6 +21,7 @@ type PaneProps = {
isSelecting: boolean;
selectionKeyPressed: boolean;
children: ReactNode;
paneClickDistance: number;
} & Partial<
Pick<
ReactFlowProps,
@@ -47,9 +40,9 @@ type PaneProps = {
>;
const wrapHandler = (
handler: React.MouseEventHandler | undefined,
containerRef: React.MutableRefObject<HTMLDivElement | null>
): React.MouseEventHandler => {
handler: MouseEventHandler | undefined,
containerRef: MutableRefObject<HTMLDivElement | null>
): MouseEventHandler => {
return (event: ReactMouseEvent) => {
if (event.target !== containerRef.current) {
return;
@@ -70,6 +63,7 @@ export function Pane({
selectionKeyPressed,
selectionMode = SelectionMode.Full,
panOnDrag,
paneClickDistance,
selectionOnDrag,
onSelectionStart,
onSelectionEnd,
@@ -83,17 +77,15 @@ export function Pane({
}: PaneProps) {
const store = useStoreApi();
const { userSelectionActive, elementsSelectable, dragging, connectionInProgress } = useStore(selector, shallow);
const hasActiveSelection = elementsSelectable && (isSelecting || userSelectionActive);
const isSelectionEnabled = elementsSelectable && (isSelecting || userSelectionActive);
const container = useRef<HTMLDivElement | null>(null);
const containerBounds = useRef<DOMRect>();
const selectedNodeIds = useRef<Set<string>>(new Set());
const selectedEdgeIds = useRef<Set<string>>(new Set());
// Used to prevent click events when the user lets go of the selectionKey during a selection
const selectionInProgress = useRef<boolean>(false);
const selectionStarted = useRef<boolean>(false);
const onClick = (event: ReactMouseEvent) => {
// We prevent click events when the user let go of the selectionKey during a selection
@@ -117,31 +109,37 @@ export function Pane({
onPaneContextMenu?.(event);
};
const onWheel = onPaneScroll ? (event: React.WheelEvent) => onPaneScroll(event) : undefined;
const onWheel = onPaneScroll ? (event: ReactWheelEvent) => onPaneScroll(event) : undefined;
const onPointerDown = (event: ReactPointerEvent): void => {
const { resetSelectedElements, domNode } = store.getState();
const onClickCapture = (event: ReactMouseEvent) => {
if (selectionInProgress.current) {
event.stopPropagation();
selectionInProgress.current = false;
}
};
// We are using capture here in order to prevent other pointer events
// to be able to create a selection above a node or an edge
const onPointerDownCapture = (event: ReactPointerEvent): void => {
const { domNode } = store.getState();
containerBounds.current = domNode?.getBoundingClientRect();
if (!containerBounds.current) return;
if (
!elementsSelectable ||
!isSelecting ||
event.button !== 0 ||
event.target !== container.current ||
!containerBounds.current
) {
const eventTargetIsContainer = event.target === container.current;
// if a child element has the 'nokey' class, we don't want to swallow the event and don't start a selection
const isNoKeyEvent = !eventTargetIsContainer && !!(event.target as HTMLElement).closest('.nokey');
const isSelectionActive = (selectionOnDrag && eventTargetIsContainer) || selectionKeyPressed;
if (isNoKeyEvent || !isSelecting || !isSelectionActive || event.button !== 0 || !event.isPrimary) {
return;
}
(event.target as Partial<Element> | null)?.setPointerCapture?.(event.pointerId);
(event.target as Partial<Element>)?.setPointerCapture?.(event.pointerId);
selectionStarted.current = true;
selectionInProgress.current = false;
const { x, y } = getEventPosition(event.nativeEvent, containerBounds.current);
resetSelectedElements();
store.setState({
userSelectionRect: {
width: 0,
@@ -153,7 +151,10 @@ export function Pane({
},
});
onSelectionStart?.(event);
if (!eventTargetIsContainer) {
event.stopPropagation();
event.preventDefault();
}
};
const onPointerMove = (event: ReactPointerEvent): void => {
@@ -166,17 +167,28 @@ export function Pane({
triggerNodeChanges,
triggerEdgeChanges,
defaultEdgeOptions,
resetSelectedElements,
} = store.getState();
if (!containerBounds.current || !userSelectionRect) {
return;
}
selectionInProgress.current = true;
const { x: mouseX, y: mouseY } = getEventPosition(event.nativeEvent, containerBounds.current);
const { startX, startY } = userSelectionRect;
if (!selectionInProgress.current) {
const requiredDistance = selectionKeyPressed ? 0 : paneClickDistance;
const distance = Math.hypot(mouseX - startX, mouseY - startY);
if (distance <= requiredDistance) {
return;
}
resetSelectedElements();
onSelectionStart?.(event);
}
selectionInProgress.current = true;
const nextUserSelectRect = {
startX,
startY,
@@ -211,12 +223,12 @@ export function Pane({
}
if (!areSetsEqual(prevSelectedNodeIds, selectedNodeIds.current)) {
const changes = getSelectionChanges(nodeLookup, selectedNodeIds.current, true) as NodeChange[];
const changes = getSelectionChanges(nodeLookup, selectedNodeIds.current, true);
triggerNodeChanges(changes);
}
if (!areSetsEqual(prevSelectedEdgeIds, selectedEdgeIds.current)) {
const changes = getSelectionChanges(edgeLookup, selectedEdgeIds.current) as EdgeChange[];
const changes = getSelectionChanges(edgeLookup, selectedEdgeIds.current);
triggerEdgeChanges(changes);
}
@@ -228,37 +240,32 @@ export function Pane({
};
const onPointerUp = (event: ReactPointerEvent) => {
if (event.button !== 0 || !selectionStarted.current) {
if (event.button !== 0) {
return;
}
(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.
*/
if (!userSelectionActive && userSelectionRect && event.target === container.current) {
if (!userSelectionActive && event.target === container.current && store.getState().userSelectionRect) {
onClick?.(event);
}
store.setState({
userSelectionActive: false,
userSelectionRect: null,
nodesSelectionActive: selectedNodeIds.current.size > 0,
});
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 (selectionKeyPressed || selectionOnDrag) {
selectionInProgress.current = false;
if (selectionInProgress.current) {
onSelectionEnd?.(event);
store.setState({
nodesSelectionActive: selectedNodeIds.current.size > 0,
});
}
selectionStarted.current = false;
};
const draggable = panOnDrag === true || (Array.isArray(panOnDrag) && panOnDrag.includes(0));
@@ -266,13 +273,14 @@ export function Pane({
return (
<div
className={cc(['react-flow__pane', { draggable, dragging, selection: isSelecting }])}
onClick={hasActiveSelection ? undefined : wrapHandler(onClick, container)}
onClick={isSelectionEnabled ? undefined : wrapHandler(onClick, container)}
onContextMenu={wrapHandler(onContextMenu, container)}
onWheel={wrapHandler(onWheel, container)}
onPointerEnter={hasActiveSelection ? undefined : onPaneMouseEnter}
onPointerDown={hasActiveSelection ? onPointerDown : onPaneMouseMove}
onPointerMove={hasActiveSelection ? onPointerMove : onPaneMouseMove}
onPointerUp={hasActiveSelection ? onPointerUp : undefined}
onPointerEnter={isSelectionEnabled ? undefined : onPaneMouseEnter}
onPointerMove={isSelectionEnabled ? onPointerMove : onPaneMouseMove}
onPointerUp={isSelectionEnabled ? onPointerUp : undefined}
onPointerDownCapture={isSelectionEnabled ? onPointerDownCapture : undefined}
onClickCapture={isSelectionEnabled ? onClickCapture : undefined}
onPointerLeave={onPaneMouseLeave}
ref={container}
style={containerStyle}
@@ -104,7 +104,7 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
onPaneMouseLeave,
onPaneScroll,
onPaneContextMenu,
paneClickDistance = 0,
paneClickDistance = 1,
nodeClickDistance = 0,
children,
onReconnect,
@@ -309,7 +309,6 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
nodeDragThreshold={nodeDragThreshold}
connectionDragThreshold={connectionDragThreshold}
onBeforeDelete={onBeforeDelete}
paneClickDistance={paneClickDistance}
debug={debug}
ariaLabelConfig={ariaLabelConfig}
/>
@@ -12,12 +12,7 @@ import type { ReactFlowState } from '../../types';
type ZoomPaneProps = Omit<
FlowRendererProps,
| 'deleteKeyCode'
| 'selectionKeyCode'
| 'multiSelectionKeyCode'
| 'noDragClassName'
| 'disableKeyboardA11y'
| 'selectionOnDrag'
'deleteKeyCode' | 'selectionKeyCode' | 'multiSelectionKeyCode' | 'noDragClassName' | 'disableKeyboardA11y'
> & {
isControlledViewport: boolean;
};
@@ -25,6 +20,7 @@ type ZoomPaneProps = Omit<
const selector = (s: ReactFlowState) => ({
userSelectionActive: s.userSelectionActive,
lib: s.lib,
connectionInProgress: s.connection.inProgress,
});
export function ZoomPane({
@@ -48,10 +44,11 @@ export function ZoomPane({
onViewportChange,
isControlledViewport,
paneClickDistance,
selectionOnDrag,
}: ZoomPaneProps) {
const store = useStoreApi();
const zoomPane = useRef<HTMLDivElement>(null);
const { userSelectionActive, lib } = useStore(selector, shallow);
const { userSelectionActive, lib, connectionInProgress } = useStore(selector, shallow);
const zoomActivationKeyPressed = useKeyPress(zoomActivationKeyCode);
const panZoom = useRef<PanZoomInstance>();
@@ -76,7 +73,6 @@ export function ZoomPane({
maxZoom,
translateExtent,
viewport: defaultViewport,
paneClickDistance,
onDraggingChange: (paneDragging: boolean) => store.setState({ paneDragging }),
onPanZoomStart: (event, vp) => {
const { onViewportChangeStart, onMoveStart } = store.getState();
@@ -126,6 +122,9 @@ export function ZoomPane({
noWheelClassName,
lib,
onTransformChange,
connectionInProgress,
selectionOnDrag,
paneClickDistance,
});
}, [
onPaneContextMenu,
@@ -143,6 +142,9 @@ export function ZoomPane({
noWheelClassName,
lib,
onTransformChange,
connectionInProgress,
selectionOnDrag,
paneClickDistance,
]);
return (
+10 -1
View File
@@ -202,6 +202,11 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
return { deletedNodes: matchingNodes, deletedEdges: matchingEdges };
},
/**
* Partial is defined as "the 2 nodes/areas are intersecting partially".
* If a is contained in b or b is contained in a, they are both
* considered fully intersecting.
*/
getIntersectingNodes: (nodeOrRect, partially = true, nodes) => {
const isRect = isRectObject(nodeOrRect);
const nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect);
@@ -240,7 +245,11 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
const overlappingArea = getOverlappingArea(nodeRect, area);
const partiallyVisible = partially && overlappingArea > 0;
return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
return (
partiallyVisible ||
overlappingArea >= area.width * area.height ||
overlappingArea >= nodeRect.width * nodeRect.height
);
},
updateNode,
updateNodeData: (id, dataUpdate, options = { replace: false }) => {
+1 -1
View File
@@ -13,7 +13,7 @@ export function useResizeHandler(domNode: MutableRefObject<HTMLDivElement | null
useEffect(() => {
const updateDimensions = () => {
if (!domNode.current) {
if (!domNode.current || !(domNode.current.checkVisibility?.() ?? true)) {
return false;
}
const size = getDimensions(domNode.current);
+8 -4
View File
@@ -14,6 +14,8 @@ import {
NodeOrigin,
CoordinateExtent,
fitViewport,
getHandlePosition,
Position,
} from '@xyflow/system';
import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
@@ -176,7 +178,7 @@ const createStore = ({
updateNodePositions: (nodeDragItems, dragging = false) => {
const parentExpandChildren: ParentExpandChild[] = [];
let changes = [];
const { nodeLookup, triggerNodeChanges, onNodesChangeMiddlewareMap } = get();
const { nodeLookup, triggerNodeChanges, connection, updateConnection, onNodesChangeMiddlewareMap } = get();
for (const [id, dragItem] of nodeDragItems) {
// we are using the nodelookup to be sure to use the current expandParent and parentId value
@@ -195,6 +197,11 @@ const createStore = ({
dragging,
};
if (node && connection.inProgress && connection.fromNode.id === node.id) {
const updatedFrom = getHandlePosition(node, connection.fromHandle, Position.Left, true);
updateConnection({ ...connection, from: updatedFrom });
}
if (expandParent && node.parentId) {
parentExpandChildren.push({
id,
@@ -316,9 +323,6 @@ const createStore = ({
set({ translateExtent });
},
setPaneClickDistance: (clickDistance) => {
get().panZoom?.setClickDistance(clickDistance);
},
resetSelectedElements: () => {
const { edges, nodes, triggerNodeChanges, triggerEdgeChanges, elementsSelectable } = get();
+2 -2
View File
@@ -79,8 +79,8 @@ const getInitialState = ({
return {
rfId: '1',
width: 0,
height: 0,
width: width ?? 0,
height: height ?? 0,
transform,
nodes: storeNodes,
nodesInitialized,
@@ -612,6 +612,7 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
elevateNodesOnSelect?: boolean;
/**
* Enabling this option will raise the z-index of edges when they are selected.
* @default false
*/
elevateEdgesOnSelect?: boolean;
/**
+3 -1
View File
@@ -22,6 +22,7 @@ import type {
OnError,
OnReconnect,
FinalConnectionState,
XYPosition,
} from '@xyflow/system';
import { EdgeTypes, InternalNode, Node } from '.';
@@ -149,7 +150,7 @@ export type EdgeTextProps = Omit<SVGAttributes<SVGElement>, 'x' | 'y'> &
*/
export type EdgeProps<EdgeType extends Edge = Edge> = Pick<
EdgeType,
'id' | 'animated' | 'data' | 'style' | 'selected' | 'source' | 'target' | 'selectable' | 'deletable'
'id' | 'type' | 'animated' | 'data' | 'style' | 'selected' | 'source' | 'target' | 'selectable' | 'deletable'
> &
EdgePosition &
EdgeLabelOptions & {
@@ -279,6 +280,7 @@ export type ConnectionLineComponentProps<NodeType extends Node = Node> = {
connectionStatus: 'valid' | 'invalid' | null;
toNode: InternalNode<NodeType> | null;
toHandle: Handle | null;
pointer: XYPosition;
};
export type ConnectionLineComponent<NodeType extends Node = Node> = ComponentType<
+10 -10
View File
@@ -228,16 +228,16 @@ export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge =
nodeId: string;
handleId?: string | null;
}) => NodeConnection[];
// /**
// * Fits the view.
// *
// * @param options.padding - optional padding
// * @param options.includeHiddenNodes - optional includeHiddenNodes
// * @param options.minZoom - optional minZoom
// * @param options.maxZoom - optional maxZoom
// * @param options.duration - optional duration. If set, a transition will be applied
// * @param options.nodes - optional nodes to fit the view to
// */
/**
* Fits the view based on the passed params. By default it fits the view to all nodes.
*
* @param options.padding - optional padding
* @param options.nodes - optional nodes to fit the view to
* @param options.minZoom - optional minZoom
* @param options.maxZoom - optional maxZoom
* @param options.duration - optional duration. If set, a transition will be applied
* @param options.includeHiddenNodes - optional includeHiddenNodes
*/
fitView: FitView<NodeType>;
};
/**
-1
View File
@@ -178,7 +178,6 @@ export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
triggerEdgeChanges: (changes: EdgeChange<EdgeType>[]) => void;
panBy: PanBy;
setCenter: SetCenter;
setPaneClickDistance: (distance: number) => void;
};
export type ReactFlowState<NodeType extends Node = Node, EdgeType extends Edge = Edge> = ReactFlowStore<
+3 -3
View File
@@ -125,9 +125,9 @@ function applyChange(change: any, element: any): any {
case 'dimensions': {
if (typeof change.dimensions !== 'undefined') {
element.measured ??= {};
element.measured.width = change.dimensions.width;
element.measured.height = change.dimensions.height;
element.measured = {
...change.dimensions,
};
if (change.setAttributes) {
if (change.setAttributes === true || change.setAttributes === 'width') {