feat(nodes-edges): add zIndexMode
This commit is contained in:
@@ -4,7 +4,7 @@ import { Provider } from '../../contexts/StoreContext';
|
||||
import { createStore } from '../../store';
|
||||
import { BatchProvider } from '../BatchProvider';
|
||||
import type { Node, Edge, FitViewOptions } from '../../types';
|
||||
import { CoordinateExtent, NodeOrigin } from '@xyflow/system';
|
||||
import { CoordinateExtent, NodeOrigin, ZIndexMode } from '@xyflow/system';
|
||||
|
||||
export type ReactFlowProviderProps = {
|
||||
/** These nodes are used to initialize the flow. They are not dynamic. */
|
||||
@@ -48,6 +48,7 @@ export type ReactFlowProviderProps = {
|
||||
*/
|
||||
nodeExtent?: CoordinateExtent;
|
||||
children: ReactNode;
|
||||
zIndexMode?: ZIndexMode;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -97,6 +98,7 @@ export function ReactFlowProvider({
|
||||
fitView,
|
||||
nodeOrigin,
|
||||
nodeExtent,
|
||||
zIndexMode,
|
||||
children,
|
||||
}: ReactFlowProviderProps) {
|
||||
const [store] = useState(() =>
|
||||
@@ -113,6 +115,7 @@ export function ReactFlowProvider({
|
||||
fitViewOptions,
|
||||
nodeOrigin,
|
||||
nodeExtent,
|
||||
zIndexMode,
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ const reactFlowFieldsToTrack = [
|
||||
'debug',
|
||||
'autoPanSpeed',
|
||||
'ariaLabelConfig',
|
||||
'zIndexMode',
|
||||
] as const;
|
||||
|
||||
type ReactFlowFieldsToTrack = (typeof reactFlowFieldsToTrack)[number];
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useContext, type ReactNode } from 'react';
|
||||
import StoreContext from '../../contexts/StoreContext';
|
||||
import { ReactFlowProvider } from '../../components/ReactFlowProvider';
|
||||
import type { Node, Edge, FitViewOptions } from '../../types';
|
||||
import { CoordinateExtent, NodeOrigin } from '@xyflow/system';
|
||||
import { CoordinateExtent, NodeOrigin, ZIndexMode } from '@xyflow/system';
|
||||
|
||||
export function Wrapper({
|
||||
children,
|
||||
@@ -19,6 +19,7 @@ export function Wrapper({
|
||||
maxZoom,
|
||||
nodeOrigin,
|
||||
nodeExtent,
|
||||
zIndexMode,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
nodes?: Node[];
|
||||
@@ -33,6 +34,7 @@ export function Wrapper({
|
||||
maxZoom?: number;
|
||||
nodeOrigin?: NodeOrigin;
|
||||
nodeExtent?: CoordinateExtent;
|
||||
zIndexMode?: ZIndexMode;
|
||||
}) {
|
||||
const isWrapped = useContext(StoreContext);
|
||||
|
||||
@@ -58,6 +60,7 @@ export function Wrapper({
|
||||
initialMaxZoom={maxZoom}
|
||||
nodeOrigin={nodeOrigin}
|
||||
nodeExtent={nodeExtent}
|
||||
zIndexMode={zIndexMode}
|
||||
>
|
||||
{children}
|
||||
</ReactFlowProvider>
|
||||
|
||||
@@ -148,6 +148,7 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
||||
debug,
|
||||
onScroll,
|
||||
ariaLabelConfig,
|
||||
zIndexMode,
|
||||
...rest
|
||||
}: ReactFlowProps<NodeType, EdgeType>,
|
||||
ref: ForwardedRef<HTMLDivElement>
|
||||
@@ -186,6 +187,7 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
||||
maxZoom={maxZoom}
|
||||
nodeOrigin={nodeOrigin}
|
||||
nodeExtent={nodeExtent}
|
||||
zIndexMode={zIndexMode}
|
||||
>
|
||||
<GraphView<NodeType, EdgeType>
|
||||
onInit={onInit}
|
||||
@@ -311,6 +313,7 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
|
||||
onBeforeDelete={onBeforeDelete}
|
||||
debug={debug}
|
||||
ariaLabelConfig={ariaLabelConfig}
|
||||
zIndexMode={zIndexMode}
|
||||
/>
|
||||
<SelectionListener<NodeType, EdgeType> onSelectionChange={onSelectionChange} />
|
||||
{children}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
NodeOrigin,
|
||||
CoordinateExtent,
|
||||
fitViewport,
|
||||
ZIndexMode,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes';
|
||||
@@ -33,6 +34,7 @@ const createStore = ({
|
||||
maxZoom,
|
||||
nodeOrigin,
|
||||
nodeExtent,
|
||||
zIndexMode,
|
||||
}: {
|
||||
nodes?: Node[];
|
||||
edges?: Edge[];
|
||||
@@ -46,6 +48,7 @@ const createStore = ({
|
||||
maxZoom?: number;
|
||||
nodeOrigin?: NodeOrigin;
|
||||
nodeExtent?: CoordinateExtent;
|
||||
zIndexMode?: ZIndexMode;
|
||||
}) =>
|
||||
createWithEqualityFn<ReactFlowState>((set, get) => {
|
||||
async function resolveFitView() {
|
||||
@@ -89,9 +92,10 @@ const createStore = ({
|
||||
nodeExtent,
|
||||
defaultNodes,
|
||||
defaultEdges,
|
||||
zIndexMode,
|
||||
}),
|
||||
setNodes: (nodes: Node[]) => {
|
||||
const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect, fitViewQueued } = get();
|
||||
const { nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect, fitViewQueued, zIndexMode } = get();
|
||||
/*
|
||||
* setNodes() is called exclusively in response to user actions:
|
||||
* - either when the `<ReactFlow nodes>` prop is updated in the controlled ReactFlow setup,
|
||||
@@ -106,6 +110,7 @@ const createStore = ({
|
||||
nodeExtent,
|
||||
elevateNodesOnSelect,
|
||||
checkEquality: true,
|
||||
zIndexMode,
|
||||
});
|
||||
|
||||
if (fitViewQueued && nodesInitialized) {
|
||||
@@ -140,8 +145,17 @@ const createStore = ({
|
||||
* new dimensions and update the nodes.
|
||||
*/
|
||||
updateNodeInternals: (updates) => {
|
||||
const { triggerNodeChanges, nodeLookup, parentLookup, domNode, nodeOrigin, nodeExtent, debug, fitViewQueued } =
|
||||
get();
|
||||
const {
|
||||
triggerNodeChanges,
|
||||
nodeLookup,
|
||||
parentLookup,
|
||||
domNode,
|
||||
nodeOrigin,
|
||||
nodeExtent,
|
||||
debug,
|
||||
fitViewQueued,
|
||||
zIndexMode,
|
||||
} = get();
|
||||
|
||||
const { changes, updatedInternals } = updateNodeInternalsSystem(
|
||||
updates,
|
||||
@@ -149,14 +163,15 @@ const createStore = ({
|
||||
parentLookup,
|
||||
domNode,
|
||||
nodeOrigin,
|
||||
nodeExtent
|
||||
nodeExtent,
|
||||
zIndexMode
|
||||
);
|
||||
|
||||
if (!updatedInternals) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateAbsolutePositions(nodeLookup, parentLookup, { nodeOrigin, nodeExtent });
|
||||
updateAbsolutePositions(nodeLookup, parentLookup, { nodeOrigin, nodeExtent, zIndexMode });
|
||||
|
||||
if (fitViewQueued) {
|
||||
resolveFitView();
|
||||
@@ -332,7 +347,7 @@ const createStore = ({
|
||||
triggerEdgeChanges(edgeChanges);
|
||||
},
|
||||
setNodeExtent: (nextNodeExtent) => {
|
||||
const { nodes, nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect, nodeExtent } = get();
|
||||
const { nodes, nodeLookup, parentLookup, nodeOrigin, elevateNodesOnSelect, nodeExtent, zIndexMode } = get();
|
||||
|
||||
if (
|
||||
nextNodeExtent[0][0] === nodeExtent[0][0] &&
|
||||
@@ -348,6 +363,7 @@ const createStore = ({
|
||||
nodeExtent: nextNodeExtent,
|
||||
elevateNodesOnSelect,
|
||||
checkEquality: false,
|
||||
zIndexMode,
|
||||
});
|
||||
|
||||
set({ nodeExtent: nextNodeExtent });
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
initialConnection,
|
||||
CoordinateExtent,
|
||||
defaultAriaLabelConfig,
|
||||
ZIndexMode,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import type { Edge, FitViewOptions, InternalNode, Node, ReactFlowStore } from '../types';
|
||||
@@ -28,6 +29,7 @@ const getInitialState = ({
|
||||
maxZoom = 2,
|
||||
nodeOrigin,
|
||||
nodeExtent,
|
||||
zIndexMode = 'auto',
|
||||
}: {
|
||||
nodes?: Node[];
|
||||
edges?: Edge[];
|
||||
@@ -41,6 +43,7 @@ const getInitialState = ({
|
||||
maxZoom?: number;
|
||||
nodeOrigin?: NodeOrigin;
|
||||
nodeExtent?: CoordinateExtent;
|
||||
zIndexMode?: ZIndexMode;
|
||||
} = {}): ReactFlowStore => {
|
||||
const nodeLookup = new Map<string, InternalNode>();
|
||||
const parentLookup = new Map();
|
||||
@@ -146,6 +149,7 @@ const getInitialState = ({
|
||||
lib: 'react',
|
||||
debug: false,
|
||||
ariaLabelConfig: defaultAriaLabelConfig,
|
||||
zIndexMode,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import type {
|
||||
OnReconnect,
|
||||
AriaLabelConfig,
|
||||
FinalConnectionState,
|
||||
ZIndexMode,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import type {
|
||||
@@ -691,4 +692,5 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
|
||||
* Allows localization, customization of ARIA descriptions, control labels, minimap labels, and other UI strings.
|
||||
*/
|
||||
ariaLabelConfig?: Partial<AriaLabelConfig>;
|
||||
zIndexMode?: ZIndexMode;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
type ParentLookup,
|
||||
type AriaLabelConfig,
|
||||
SetCenter,
|
||||
ZIndexMode,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import type {
|
||||
@@ -152,6 +153,8 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
|
||||
lib: string;
|
||||
debug: boolean;
|
||||
ariaLabelConfig: AriaLabelConfig;
|
||||
|
||||
zIndexMode: ZIndexMode;
|
||||
};
|
||||
|
||||
export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
|
||||
|
||||
@@ -367,3 +367,5 @@ export type OnBeforeDeleteBase<NodeType extends NodeBase = NodeBase, EdgeType ex
|
||||
nodes: NodeType[];
|
||||
edges: EdgeType[];
|
||||
}) => Promise<boolean | { nodes: NodeType[]; edges: EdgeType[] }>;
|
||||
|
||||
export type ZIndexMode = 'auto' | 'manual';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Handle, HandleConnection, infiniteExtent, NodeHandle, NodeHandleBounds } from '..';
|
||||
import { Handle, HandleConnection, infiniteExtent, NodeHandleBounds, ZIndexMode } from '..';
|
||||
import {
|
||||
NodeBase,
|
||||
CoordinateExtent,
|
||||
@@ -37,6 +37,7 @@ const defaultOptions = {
|
||||
nodeOrigin: [0, 0] as NodeOrigin,
|
||||
nodeExtent: infiniteExtent,
|
||||
elevateNodesOnSelect: true,
|
||||
zIndexMode: 'auto' as ZIndexMode,
|
||||
defaults: {},
|
||||
};
|
||||
|
||||
@@ -112,21 +113,27 @@ type UpdateNodesOptions<NodeType extends NodeBase> = {
|
||||
nodeExtent?: CoordinateExtent;
|
||||
elevateNodesOnSelect?: boolean;
|
||||
defaults?: Partial<NodeType>;
|
||||
zIndexMode?: ZIndexMode;
|
||||
checkEquality?: boolean;
|
||||
};
|
||||
|
||||
function isManualZIndexMode(zIndexMode?: ZIndexMode): boolean {
|
||||
return zIndexMode === 'manual';
|
||||
}
|
||||
|
||||
export function adoptUserNodes<NodeType extends NodeBase>(
|
||||
nodes: NodeType[],
|
||||
nodeLookup: NodeLookup<InternalNodeBase<NodeType>>,
|
||||
parentLookup: ParentLookup<InternalNodeBase<NodeType>>,
|
||||
options?: UpdateNodesOptions<NodeType>
|
||||
options: UpdateNodesOptions<NodeType> = {}
|
||||
): boolean {
|
||||
const _options = mergeObjects(adoptUserNodesDefaultOptions, options);
|
||||
|
||||
let rootParentIndex = { i: -1 };
|
||||
let nodesInitialized = nodes.length > 0;
|
||||
const tmpLookup = new Map(nodeLookup);
|
||||
const selectedNodeZ: number = _options?.elevateNodesOnSelect ? SELECTED_NODE_Z : 0;
|
||||
const selectedNodeZ: number =
|
||||
_options?.elevateNodesOnSelect && !isManualZIndexMode(_options.zIndexMode) ? SELECTED_NODE_Z : 0;
|
||||
|
||||
nodeLookup.clear();
|
||||
parentLookup.clear();
|
||||
@@ -152,7 +159,7 @@ export function adoptUserNodes<NodeType extends NodeBase>(
|
||||
positionAbsolute: clampedPosition,
|
||||
// if user re-initializes the node or removes `measured` for whatever reason, we reset the handleBounds so that the node gets re-measured
|
||||
handleBounds: parseHandles(userNode, internalNode),
|
||||
z: calculateZ(userNode, selectedNodeZ),
|
||||
z: calculateZ(userNode, selectedNodeZ, _options.zIndexMode),
|
||||
userNode,
|
||||
},
|
||||
};
|
||||
@@ -201,10 +208,10 @@ function updateChildNode<NodeType extends NodeBase>(
|
||||
node: InternalNodeBase<NodeType>,
|
||||
nodeLookup: NodeLookup<InternalNodeBase<NodeType>>,
|
||||
parentLookup: ParentLookup<InternalNodeBase<NodeType>>,
|
||||
options?: UpdateNodesOptions<NodeType>,
|
||||
options: UpdateNodesOptions<NodeType>,
|
||||
rootParentIndex?: { i: number }
|
||||
) {
|
||||
const { elevateNodesOnSelect, nodeOrigin, nodeExtent } = mergeObjects(defaultOptions, options);
|
||||
const { elevateNodesOnSelect, nodeOrigin, nodeExtent, zIndexMode } = mergeObjects(defaultOptions, options);
|
||||
const parentId = node.parentId!;
|
||||
const parentNode = nodeLookup.get(parentId);
|
||||
|
||||
@@ -218,7 +225,12 @@ function updateChildNode<NodeType extends NodeBase>(
|
||||
updateParentLookup(node, parentLookup);
|
||||
|
||||
// We just want to set the rootParentIndex for the first child
|
||||
if (rootParentIndex && !parentNode.parentId && parentNode.internals.rootParentIndex === undefined) {
|
||||
if (
|
||||
rootParentIndex &&
|
||||
!parentNode.parentId &&
|
||||
parentNode.internals.rootParentIndex === undefined &&
|
||||
!isManualZIndexMode(zIndexMode)
|
||||
) {
|
||||
parentNode.internals.rootParentIndex = ++rootParentIndex.i;
|
||||
parentNode.internals.z = parentNode.internals.z + rootParentIndex.i * ROOT_PARENT_Z_INCREMENT;
|
||||
}
|
||||
@@ -228,8 +240,8 @@ function updateChildNode<NodeType extends NodeBase>(
|
||||
rootParentIndex.i = parentNode.internals.rootParentIndex;
|
||||
}
|
||||
|
||||
const selectedNodeZ = elevateNodesOnSelect ? SELECTED_NODE_Z : 0;
|
||||
const { x, y, z } = calculateChildXYZ(node, parentNode, nodeOrigin, nodeExtent, selectedNodeZ);
|
||||
const selectedNodeZ = elevateNodesOnSelect && !isManualZIndexMode(zIndexMode) ? SELECTED_NODE_Z : 0;
|
||||
const { x, y, z } = calculateChildXYZ(node, parentNode, nodeOrigin, nodeExtent, selectedNodeZ, zIndexMode);
|
||||
const { positionAbsolute } = node.internals;
|
||||
const positionChanged = x !== positionAbsolute.x || y !== positionAbsolute.y;
|
||||
|
||||
@@ -246,8 +258,14 @@ function updateChildNode<NodeType extends NodeBase>(
|
||||
}
|
||||
}
|
||||
|
||||
function calculateZ(node: NodeBase, selectedNodeZ: number) {
|
||||
return (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? selectedNodeZ : 0);
|
||||
function calculateZ(node: NodeBase, selectedNodeZ: number, zIndexMode: ZIndexMode): number {
|
||||
const zIndex = isNumeric(node.zIndex) ? node.zIndex : 0;
|
||||
|
||||
if (isManualZIndexMode(zIndexMode)) {
|
||||
return zIndex;
|
||||
}
|
||||
|
||||
return zIndex + (node.selected ? selectedNodeZ : 0);
|
||||
}
|
||||
|
||||
function calculateChildXYZ<NodeType extends NodeBase>(
|
||||
@@ -255,7 +273,8 @@ function calculateChildXYZ<NodeType extends NodeBase>(
|
||||
parentNode: InternalNodeBase<NodeType>,
|
||||
nodeOrigin: NodeOrigin,
|
||||
nodeExtent: CoordinateExtent,
|
||||
selectedNodeZ: number
|
||||
selectedNodeZ: number,
|
||||
zIndexMode: ZIndexMode
|
||||
) {
|
||||
const { x: parentX, y: parentY } = parentNode.internals.positionAbsolute;
|
||||
const childDimensions = getNodeDimensions(childNode);
|
||||
@@ -274,7 +293,7 @@ function calculateChildXYZ<NodeType extends NodeBase>(
|
||||
absolutePosition = clampPositionToParent(absolutePosition, childDimensions, parentNode);
|
||||
}
|
||||
|
||||
const childZ = calculateZ(childNode, selectedNodeZ);
|
||||
const childZ = calculateZ(childNode, selectedNodeZ, zIndexMode);
|
||||
const parentZ = parentNode.internals.z ?? 0;
|
||||
|
||||
return {
|
||||
@@ -378,7 +397,8 @@ export function updateNodeInternals<NodeType extends InternalNodeBase>(
|
||||
parentLookup: ParentLookup<NodeType>,
|
||||
domNode: HTMLElement | null,
|
||||
nodeOrigin?: NodeOrigin,
|
||||
nodeExtent?: CoordinateExtent
|
||||
nodeExtent?: CoordinateExtent,
|
||||
zIndexMode?: ZIndexMode
|
||||
): { changes: (NodeDimensionChange | NodePositionChange)[]; updatedInternals: boolean } {
|
||||
const viewportNode = domNode?.querySelector('.xyflow__viewport');
|
||||
let updatedInternals = false;
|
||||
@@ -446,7 +466,7 @@ export function updateNodeInternals<NodeType extends InternalNodeBase>(
|
||||
nodeLookup.set(node.id, newNode);
|
||||
|
||||
if (node.parentId) {
|
||||
updateChildNode(newNode, nodeLookup, parentLookup, { nodeOrigin });
|
||||
updateChildNode(newNode, nodeLookup, parentLookup, { nodeOrigin, zIndexMode });
|
||||
}
|
||||
|
||||
updatedInternals = true;
|
||||
|
||||
Reference in New Issue
Block a user