Merge pull request #3727 from xyflow/perf/noderenderer-minimap
Perf: NodeRenderer, Minimap and Nodes
This commit is contained in:
@@ -21,7 +21,6 @@ import Interaction from '../examples/Interaction';
|
||||
import Intersection from '../examples/Intersection';
|
||||
import Layouting from '../examples/Layouting';
|
||||
import MultiFlows from '../examples/MultiFlows';
|
||||
import NestedNodes from '../examples/NestedNodes';
|
||||
import NodeResizer from '../examples/NodeResizer';
|
||||
import NodeTypeChange from '../examples/NodeTypeChange';
|
||||
import NodeTypesObjectChange from '../examples/NodeTypesObjectChange';
|
||||
@@ -185,11 +184,6 @@ const routes: IRoute[] = [
|
||||
path: 'multiflows',
|
||||
component: MultiFlows,
|
||||
},
|
||||
{
|
||||
name: 'Nested Nodes',
|
||||
path: 'nested-nodes',
|
||||
component: NestedNodes,
|
||||
},
|
||||
{
|
||||
name: 'Node Type Change',
|
||||
path: 'nodetype-change',
|
||||
|
||||
@@ -4,8 +4,12 @@ import { Node, Position, MarkerType, XYPosition } from '@xyflow/react';
|
||||
// of the line between the center of the intersectionNode and the target node
|
||||
function getNodeIntersection(intersectionNode: Node, targetNode: Node) {
|
||||
// https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a
|
||||
const { width: intersectionNodeWidth, height: intersectionNodeHeight } = intersectionNode;
|
||||
const intersectionNodePosition = intersectionNode.computed?.positionAbsolute!;
|
||||
|
||||
const {
|
||||
width: intersectionNodeWidth,
|
||||
height: intersectionNodeHeight,
|
||||
positionAbsolute: intersectionNodePosition,
|
||||
} = intersectionNode.computed || {};
|
||||
const targetPosition = targetNode.computed?.positionAbsolute!;
|
||||
|
||||
const w = intersectionNodeWidth! / 2;
|
||||
|
||||
@@ -5,11 +5,11 @@ import { Position, XYPosition, Node, Edge } from '@xyflow/react';
|
||||
function getNodeIntersection(intersectionNode: Node, targetNode: Node): XYPosition {
|
||||
// https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a
|
||||
|
||||
const {
|
||||
width: intersectionNodeWidth,
|
||||
height: intersectionNodeHeight,
|
||||
position: intersectionNodePosition,
|
||||
} = intersectionNode;
|
||||
const { position: intersectionNodePosition } = intersectionNode;
|
||||
const { width: intersectionNodeWidth, height: intersectionNodeHeight } = intersectionNode.computed ?? {
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
const targetPosition = targetNode.position;
|
||||
|
||||
const w = (intersectionNodeWidth ?? 0) / 2;
|
||||
|
||||
@@ -48,8 +48,10 @@ const initialEdges: Edge[] = [
|
||||
|
||||
const setHidden = (hidden: boolean) => (els: any[]) =>
|
||||
els.map((e: any) => {
|
||||
e.hidden = hidden;
|
||||
return e;
|
||||
return {
|
||||
...e,
|
||||
hidden,
|
||||
};
|
||||
});
|
||||
|
||||
const HiddenFlow = () => {
|
||||
|
||||
@@ -56,16 +56,16 @@ const LayoutFlow = () => {
|
||||
|
||||
const layoutedNodes = nodes.map((node) => {
|
||||
const nodeWithPosition = dagreGraph.node(node.id);
|
||||
node.targetPosition = isHorizontal ? Position.Left : Position.Top;
|
||||
node.sourcePosition = isHorizontal ? Position.Right : Position.Bottom;
|
||||
// we need to pass a slightly different position in order to notify react flow about the change
|
||||
// @TODO how can we change the position handling so that we dont need this hack?
|
||||
node.position = {
|
||||
x: nodeWithPosition.x + Math.random() / 1000,
|
||||
y: nodeWithPosition.y,
|
||||
};
|
||||
|
||||
return node;
|
||||
return {
|
||||
...node,
|
||||
targetPosition: isHorizontal ? Position.Left : Position.Top,
|
||||
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
|
||||
position: {
|
||||
x: nodeWithPosition.x,
|
||||
y: nodeWithPosition.y,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
setNodes(layoutedNodes);
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
import { useState, MouseEvent, useCallback } from 'react';
|
||||
import {
|
||||
ReactFlow,
|
||||
Controls,
|
||||
MiniMap,
|
||||
Background,
|
||||
addEdge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
Node,
|
||||
Edge,
|
||||
ReactFlowInstance,
|
||||
Connection,
|
||||
} from '@xyflow/react';
|
||||
|
||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge);
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: 'Node 2' },
|
||||
position: { x: 100, y: 100 },
|
||||
className: 'light',
|
||||
style: { backgroundColor: 'rgba(255, 0, 0, 0.8)', width: 200, height: 200 },
|
||||
},
|
||||
{
|
||||
id: '2a',
|
||||
data: { label: 'Node 2a' },
|
||||
position: { x: 10, y: 50 },
|
||||
parentNode: '2',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { label: 'Node 3' },
|
||||
position: { x: 320, y: 100 },
|
||||
className: 'light',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
data: { label: 'Node 4' },
|
||||
position: { x: 320, y: 200 },
|
||||
className: 'light',
|
||||
style: { backgroundColor: 'rgba(255, 0, 0, 0.7)', width: 300, height: 300 },
|
||||
},
|
||||
{
|
||||
id: '4a',
|
||||
data: { label: 'Node 4a' },
|
||||
position: { x: 15, y: 65 },
|
||||
className: 'light',
|
||||
parentNode: '4',
|
||||
extent: 'parent',
|
||||
},
|
||||
{
|
||||
id: '4b',
|
||||
data: { label: 'Node 4b' },
|
||||
position: { x: 15, y: 120 },
|
||||
className: 'light',
|
||||
style: {
|
||||
backgroundColor: 'rgba(255, 0, 255, 0.7)',
|
||||
height: 150,
|
||||
width: 270,
|
||||
},
|
||||
parentNode: '4',
|
||||
},
|
||||
{
|
||||
id: '4b1',
|
||||
data: { label: 'Node 4b1' },
|
||||
position: { x: 20, y: 40 },
|
||||
className: 'light',
|
||||
parentNode: '4b',
|
||||
},
|
||||
{
|
||||
id: '4b2',
|
||||
data: { label: 'Node 4b2' },
|
||||
position: { x: 100, y: 100 },
|
||||
className: 'light',
|
||||
parentNode: '4b',
|
||||
},
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
{ id: 'e1-2', source: '1', target: '2', animated: true },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
{ id: 'e2a-4a', source: '2a', target: '4a' },
|
||||
{ id: 'e3-4', source: '3', target: '4' },
|
||||
{ id: 'e3-4b', source: '3', target: '4b' },
|
||||
{ id: 'e4a-4b1', source: '4a', target: '4b1' },
|
||||
{ id: 'e4a-4b2', source: '4a', target: '4b2' },
|
||||
{ id: 'e4b1-4b2', source: '4b1', target: '4b2' },
|
||||
];
|
||||
|
||||
const NestedFlow = () => {
|
||||
const [rfInstance, setRfInstance] = useState<ReactFlowInstance | null>(null);
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
|
||||
const onConnect = useCallback(
|
||||
(connection: Connection) => {
|
||||
setEdges((eds) => addEdge(connection, eds));
|
||||
},
|
||||
[setEdges]
|
||||
);
|
||||
const onInit = useCallback((reactFlowInstance: ReactFlowInstance) => setRfInstance(reactFlowInstance), []);
|
||||
|
||||
const updatePos = () => {
|
||||
setNodes((nds) => {
|
||||
return nds.map((n) => {
|
||||
n.position = {
|
||||
x: Math.random() * 400,
|
||||
y: Math.random() * 400,
|
||||
};
|
||||
|
||||
return n;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const logToObject = () => console.log(rfInstance?.toObject());
|
||||
const resetTransform = () => rfInstance?.setViewport({ x: 0, y: 0, zoom: 1 });
|
||||
|
||||
const toggleClassnames = () => {
|
||||
setNodes((nds) => {
|
||||
return nds.map((n) => {
|
||||
n.className = n.className === 'light' ? 'dark' : 'light';
|
||||
return n;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const toggleChildNodes = () => {
|
||||
setNodes((nds) => {
|
||||
return nds.map((n) => {
|
||||
n.hidden = !!n.parentNode && !n.hidden;
|
||||
return n;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onInit={onInit}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeClick={onNodeClick}
|
||||
onEdgeClick={onEdgeClick}
|
||||
onConnect={onConnect}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
className="react-flow-basic-example"
|
||||
minZoom={0.2}
|
||||
maxZoom={4}
|
||||
onlyRenderVisibleElements={false}
|
||||
>
|
||||
<MiniMap pannable />
|
||||
<Controls />
|
||||
<Background />
|
||||
|
||||
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
|
||||
<button onClick={resetTransform} style={{ marginRight: 5 }}>
|
||||
reset transform
|
||||
</button>
|
||||
<button onClick={updatePos} style={{ marginRight: 5 }}>
|
||||
change pos
|
||||
</button>
|
||||
<button onClick={toggleClassnames} style={{ marginRight: 5 }}>
|
||||
toggle classnames
|
||||
</button>
|
||||
<button style={{ marginRight: 5 }} onClick={toggleChildNodes}>
|
||||
toggleChildNodes
|
||||
</button>
|
||||
<button onClick={logToObject}>toObject</button>
|
||||
</div>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default NestedFlow;
|
||||
@@ -64,7 +64,6 @@ const StressFlow = () => {
|
||||
minZoom={0.2}
|
||||
fitView
|
||||
>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
<Background />
|
||||
|
||||
|
||||
@@ -11,13 +11,13 @@ const idStyle: CSSProperties = {
|
||||
left: 2,
|
||||
};
|
||||
|
||||
const DebugNode: FC<NodeProps> = ({ zIndex, positionAbsolute, id }) => {
|
||||
const DebugNode: FC<NodeProps> = ({ zIndex, positionAbsoluteX, positionAbsoluteY, id }) => {
|
||||
return (
|
||||
<>
|
||||
<Handle type="target" position={Position.Top} />
|
||||
<div style={idStyle}>{id}</div>
|
||||
<div style={infoStyle}>
|
||||
x:{Math.round(positionAbsolute.x)} y:{Math.round(positionAbsolute.y)} z:{zIndex}
|
||||
x:{Math.round(positionAbsoluteX)} y:{Math.round(positionAbsoluteY)} z:{zIndex}
|
||||
</div>
|
||||
<Handle type="source" position={Position.Bottom} />
|
||||
</>
|
||||
|
||||
@@ -24,9 +24,12 @@ const UpdateNode = () => {
|
||||
nds.map((n) => {
|
||||
if (n.id === '1') {
|
||||
// it's important that you create a new object here in order to notify react flow about the change
|
||||
n.data = {
|
||||
...n.data,
|
||||
label: nodeName,
|
||||
return {
|
||||
...n,
|
||||
data: {
|
||||
...n.data,
|
||||
label: nodeName,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,7 +43,10 @@ const UpdateNode = () => {
|
||||
nds.map((n) => {
|
||||
if (n.id === '1') {
|
||||
// it's important that you create a new object here in order to notify react flow about the change
|
||||
n.style = { ...n.style, backgroundColor: nodeBg };
|
||||
return {
|
||||
...n,
|
||||
style: { ...n.style, backgroundColor: nodeBg },
|
||||
};
|
||||
}
|
||||
|
||||
return n;
|
||||
@@ -52,8 +58,10 @@ const UpdateNode = () => {
|
||||
setNodes((nds) =>
|
||||
nds.map((n) => {
|
||||
if (n.id === '1' || n.id === 'e1-2') {
|
||||
// when you update a simple type you can just update the value
|
||||
n.hidden = nodeHidden;
|
||||
return {
|
||||
...n,
|
||||
hidden: nodeHidden,
|
||||
};
|
||||
}
|
||||
|
||||
return n;
|
||||
|
||||
@@ -12,8 +12,8 @@ function CustomHandle({ nodeId, ...handleProps }: HandleComponentProps & { nodeI
|
||||
[nodeId]
|
||||
);
|
||||
const connections = useHandleConnections({
|
||||
handleType: handleProps.type,
|
||||
handleId: handleProps.id,
|
||||
type: handleProps.type,
|
||||
id: handleProps.id,
|
||||
onConnect,
|
||||
onDisconnect,
|
||||
});
|
||||
|
||||
@@ -15,8 +15,8 @@ function CustomHandle({ nodeId, ...handleProps }: HandleComponentProps & { nodeI
|
||||
[nodeId]
|
||||
);
|
||||
const connections = useHandleConnections({
|
||||
handleType: handleProps.type,
|
||||
handleId: handleProps.id,
|
||||
type: handleProps.type,
|
||||
id: handleProps.id,
|
||||
onConnect,
|
||||
onDisconnect,
|
||||
});
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { Handle, Position, type NodeProps, type XYPosition } from '@xyflow/svelte';
|
||||
import { Handle, Position, type NodeProps } from '@xyflow/svelte';
|
||||
|
||||
type $$Props = NodeProps;
|
||||
|
||||
export let data: { label: string } = { label: 'Node' };
|
||||
export let positionAbsolute: XYPosition = { x: 0, y: 0 };
|
||||
export let positionAbsoluteX: number = 0;
|
||||
export let positionAbsoluteY: number = 0;
|
||||
</script>
|
||||
|
||||
<div class="custom">
|
||||
<div>{data.label}</div>
|
||||
<div>{~~positionAbsolute.x}, {~~positionAbsolute.y}</div>
|
||||
<div>{~~positionAbsoluteX}, {~~positionAbsoluteY}</div>
|
||||
|
||||
<Handle type="target" position={Position.Top} />
|
||||
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { memo } from 'react';
|
||||
import { ComponentType, memo } from 'react';
|
||||
import { NodeOrigin, getNodePositionWithOrigin } from '@xyflow/system';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { getNodePositionWithOrigin } from '@xyflow/system';
|
||||
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
import type { ReactFlowState } from '../../types';
|
||||
import MiniMapNode from './MiniMapNode';
|
||||
import type { MiniMapNodes, GetMiniMapNodeAttribute } from './types';
|
||||
import type { MiniMapNodes as MiniMapNodesProps, GetMiniMapNodeAttribute, MiniMapNodeProps } from './types';
|
||||
|
||||
declare const window: any;
|
||||
|
||||
const selector = (s: ReactFlowState) => s.nodeOrigin;
|
||||
const selectorNodes = (s: ReactFlowState) =>
|
||||
s.nodes.filter(
|
||||
(node) => !node.hidden && (node.computed?.width || node.width) && (node.computed?.height || node.height)
|
||||
);
|
||||
const selectorNodeIds = (s: ReactFlowState) => s.nodes.map((node) => node.id);
|
||||
const getAttrFunction = (func: any): GetMiniMapNodeAttribute => (func instanceof Function ? func : () => func);
|
||||
|
||||
function MiniMapNodes({
|
||||
@@ -28,8 +25,8 @@ function MiniMapNodes({
|
||||
// a component properly.
|
||||
nodeComponent: NodeComponent = MiniMapNode,
|
||||
onClick,
|
||||
}: MiniMapNodes) {
|
||||
const nodes = useStore(selectorNodes, shallow);
|
||||
}: MiniMapNodesProps) {
|
||||
const nodeIds = useStore(selectorNodeIds, shallow);
|
||||
const nodeOrigin = useStore(selector);
|
||||
const nodeColorFunc = getAttrFunction(nodeColor);
|
||||
const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor);
|
||||
@@ -39,33 +36,78 @@ function MiniMapNodes({
|
||||
|
||||
return (
|
||||
<>
|
||||
{nodes.map((node) => {
|
||||
const { x, y } = getNodePositionWithOrigin(node, node.origin || nodeOrigin).positionAbsolute;
|
||||
const color = nodeColor === undefined ? undefined : nodeColorFunc(node);
|
||||
const strokeColor = nodeStrokeColor === undefined ? undefined : nodeStrokeColorFunc(node);
|
||||
|
||||
return (
|
||||
<NodeComponent
|
||||
key={node.id}
|
||||
x={x}
|
||||
y={y}
|
||||
width={node.computed?.width ?? node.width ?? 0}
|
||||
height={node.computed?.height ?? node.height ?? 0}
|
||||
style={node.style}
|
||||
selected={!!node.selected}
|
||||
className={nodeClassNameFunc(node)}
|
||||
color={color}
|
||||
borderRadius={nodeBorderRadius}
|
||||
strokeColor={strokeColor}
|
||||
strokeWidth={nodeStrokeWidth}
|
||||
shapeRendering={shapeRendering}
|
||||
onClick={onClick}
|
||||
id={node.id}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{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`.
|
||||
<NodeComponentWrapper
|
||||
key={nodeId}
|
||||
id={nodeId}
|
||||
nodeOrigin={nodeOrigin}
|
||||
nodeColorFunc={nodeColorFunc}
|
||||
nodeStrokeColorFunc={nodeStrokeColorFunc}
|
||||
nodeClassNameFunc={nodeClassNameFunc}
|
||||
nodeBorderRadius={nodeBorderRadius}
|
||||
nodeStrokeWidth={nodeStrokeWidth}
|
||||
NodeComponent={NodeComponent}
|
||||
onClick={onClick}
|
||||
shapeRendering={shapeRendering}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const NodeComponentWrapper = memo(function NodeComponentWrapper({
|
||||
id,
|
||||
nodeOrigin,
|
||||
nodeColorFunc,
|
||||
nodeStrokeColorFunc,
|
||||
nodeClassNameFunc,
|
||||
nodeBorderRadius,
|
||||
nodeStrokeWidth,
|
||||
shapeRendering,
|
||||
NodeComponent,
|
||||
onClick,
|
||||
}: {
|
||||
id: string;
|
||||
nodeOrigin: NodeOrigin;
|
||||
nodeColorFunc: GetMiniMapNodeAttribute;
|
||||
nodeStrokeColorFunc: GetMiniMapNodeAttribute;
|
||||
nodeClassNameFunc: GetMiniMapNodeAttribute;
|
||||
nodeBorderRadius: number;
|
||||
nodeStrokeWidth?: number;
|
||||
NodeComponent: ComponentType<MiniMapNodeProps>;
|
||||
onClick: MiniMapNodesProps['onClick'];
|
||||
shapeRendering: string;
|
||||
}) {
|
||||
const node = useStore((s) => s.nodeLookup.get(id));
|
||||
if (!node || node.hidden || !(node.computed?.width || node.width) || !(node.computed?.height || node.height)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const positionOrigin = getNodePositionWithOrigin(node, node.origin || nodeOrigin).positionAbsolute;
|
||||
|
||||
return (
|
||||
<NodeComponent
|
||||
x={positionOrigin.x}
|
||||
y={positionOrigin.y}
|
||||
width={node.computed?.width ?? node.width ?? 0}
|
||||
height={node.computed?.height ?? node.height ?? 0}
|
||||
style={node.style}
|
||||
selected={!!node.selected}
|
||||
className={nodeClassNameFunc(node)}
|
||||
color={nodeColorFunc(node)}
|
||||
borderRadius={nodeBorderRadius}
|
||||
strokeColor={nodeStrokeColorFunc(node)}
|
||||
strokeWidth={nodeStrokeWidth}
|
||||
shapeRendering={shapeRendering}
|
||||
onClick={onClick}
|
||||
id={node.id}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default memo(MiniMapNodes);
|
||||
|
||||
@@ -9,16 +9,20 @@ import { useNodeId } from '../../contexts/NodeIdContext';
|
||||
import NodeToolbarPortal from './NodeToolbarPortal';
|
||||
import { NodeToolbarProps } from './types';
|
||||
|
||||
const nodeEqualityFn = (a: Node | undefined, b: Node | undefined) =>
|
||||
a?.computed?.positionAbsolute?.x === b?.computed?.positionAbsolute?.x &&
|
||||
a?.computed?.positionAbsolute?.y === b?.computed?.positionAbsolute?.y &&
|
||||
a?.width === b?.width &&
|
||||
a?.height === b?.height &&
|
||||
a?.selected === b?.selected &&
|
||||
a?.[internalsSymbol]?.z === b?.[internalsSymbol]?.z;
|
||||
const nodeEqualityFn = (a?: Node, b?: Node) =>
|
||||
a?.computed?.positionAbsolute?.x !== b?.computed?.positionAbsolute?.x ||
|
||||
a?.computed?.positionAbsolute?.y !== b?.computed?.positionAbsolute?.y ||
|
||||
a?.computed?.width !== b?.computed?.width ||
|
||||
a?.computed?.height !== b?.computed?.height ||
|
||||
a?.selected !== b?.selected ||
|
||||
a?.[internalsSymbol]?.z !== b?.[internalsSymbol]?.z;
|
||||
|
||||
const nodesEqualityFn = (a: Node[], b: Node[]) => {
|
||||
return a.length === b.length && a.every((node, i) => nodeEqualityFn(node, b[i]));
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !a.some((node, i) => nodeEqualityFn(node, b[i]));
|
||||
};
|
||||
|
||||
const storeSelector = (state: ReactFlowState) => ({
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
import { memo, useState, useMemo, useRef, type KeyboardEvent, useCallback } from 'react';
|
||||
import cc from 'classcat';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import {
|
||||
getMarkerId,
|
||||
elementSelectionKeys,
|
||||
XYHandle,
|
||||
type Connection,
|
||||
getEdgePosition,
|
||||
errorMessages,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import { useStoreApi, useStore } from '../../hooks/useStore';
|
||||
import { ARIA_EDGE_DESC_KEY } from '../A11yDescriptions';
|
||||
import { EdgeAnchor } from '../Edges/EdgeAnchor';
|
||||
import { getMouseHandler } from '../Edges/utils';
|
||||
import type { EdgeWrapperProps } from '../../types';
|
||||
import { builtinEdgeTypes } from './utils';
|
||||
|
||||
function EdgeWrapper({
|
||||
id,
|
||||
className,
|
||||
type,
|
||||
data,
|
||||
onClick,
|
||||
onEdgeDoubleClick,
|
||||
selected,
|
||||
animated,
|
||||
label,
|
||||
labelStyle,
|
||||
labelShowBg,
|
||||
labelBgStyle,
|
||||
labelBgPadding,
|
||||
labelBgBorderRadius,
|
||||
style,
|
||||
source,
|
||||
target,
|
||||
isSelectable,
|
||||
hidden,
|
||||
sourceHandleId,
|
||||
targetHandleId,
|
||||
onContextMenu,
|
||||
onMouseEnter,
|
||||
onMouseMove,
|
||||
onMouseLeave,
|
||||
edgeUpdaterRadius,
|
||||
onEdgeUpdate,
|
||||
onEdgeUpdateStart,
|
||||
onEdgeUpdateEnd,
|
||||
markerEnd,
|
||||
markerStart,
|
||||
rfId,
|
||||
ariaLabel,
|
||||
isFocusable,
|
||||
isUpdatable,
|
||||
pathOptions,
|
||||
interactionWidth,
|
||||
edgeTypes,
|
||||
onError,
|
||||
}: EdgeWrapperProps): JSX.Element | null {
|
||||
let edgeType = type || 'default';
|
||||
let EdgeComponent = edgeTypes?.[edgeType] || builtinEdgeTypes[edgeType];
|
||||
|
||||
if (EdgeComponent === undefined) {
|
||||
onError?.('011', errorMessages['error011'](edgeType));
|
||||
edgeType = 'default';
|
||||
EdgeComponent = builtinEdgeTypes.default;
|
||||
}
|
||||
|
||||
const edgeRef = useRef<SVGGElement>(null);
|
||||
const [updateHover, setUpdateHover] = useState<boolean>(false);
|
||||
const [updating, setUpdating] = useState<boolean>(false);
|
||||
const store = useStoreApi();
|
||||
const edgePosition = useStore(
|
||||
useCallback(
|
||||
(state) => {
|
||||
const sourceNode = state.nodeLookup.get(source);
|
||||
const targetNode = state.nodeLookup.get(target);
|
||||
|
||||
if (!sourceNode || !targetNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getEdgePosition({
|
||||
id,
|
||||
sourceNode,
|
||||
targetNode,
|
||||
sourceHandle: sourceHandleId || null,
|
||||
targetHandle: targetHandleId || null,
|
||||
connectionMode: state.connectionMode,
|
||||
onError: state.onError,
|
||||
});
|
||||
},
|
||||
[source, target]
|
||||
),
|
||||
shallow
|
||||
);
|
||||
|
||||
const markerStartUrl = useMemo(() => `url(#${getMarkerId(markerStart, rfId)})`, [markerStart, rfId]);
|
||||
const markerEndUrl = useMemo(() => `url(#${getMarkerId(markerEnd, rfId)})`, [markerEnd, rfId]);
|
||||
|
||||
if (hidden || !edgePosition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onEdgeClick = (event: React.MouseEvent<SVGGElement, MouseEvent>): void => {
|
||||
const { edges, addSelectedEdges, unselectNodesAndEdges, multiSelectionActive } = store.getState();
|
||||
const edge = edges.find((e) => e.id === id);
|
||||
|
||||
if (!edge) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSelectable) {
|
||||
store.setState({ nodesSelectionActive: false });
|
||||
|
||||
if (edge.selected && multiSelectionActive) {
|
||||
unselectNodesAndEdges({ nodes: [], edges: [edge] });
|
||||
edgeRef.current?.blur();
|
||||
} else {
|
||||
addSelectedEdges([id]);
|
||||
}
|
||||
}
|
||||
|
||||
if (onClick) {
|
||||
onClick(event, edge);
|
||||
}
|
||||
};
|
||||
|
||||
const onEdgeDoubleClickHandler = getMouseHandler(id, store.getState, onEdgeDoubleClick);
|
||||
const onEdgeContextMenu = getMouseHandler(id, store.getState, onContextMenu);
|
||||
const onEdgeMouseEnter = getMouseHandler(id, store.getState, onMouseEnter);
|
||||
const onEdgeMouseMove = getMouseHandler(id, store.getState, onMouseMove);
|
||||
const onEdgeMouseLeave = getMouseHandler(id, store.getState, onMouseLeave);
|
||||
|
||||
const handleEdgeUpdater = (event: React.MouseEvent<SVGGElement, MouseEvent>, isSourceHandle: boolean) => {
|
||||
// avoid triggering edge updater if mouse btn is not left
|
||||
if (event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
autoPanOnConnect,
|
||||
domNode,
|
||||
edges,
|
||||
isValidConnection,
|
||||
connectionMode,
|
||||
connectionRadius,
|
||||
lib,
|
||||
onConnectStart,
|
||||
onConnectEnd,
|
||||
cancelConnection,
|
||||
nodes,
|
||||
panBy,
|
||||
updateConnection,
|
||||
} = store.getState();
|
||||
const nodeId = isSourceHandle ? target : source;
|
||||
const handleId = (isSourceHandle ? targetHandleId : sourceHandleId) || null;
|
||||
const handleType = isSourceHandle ? 'target' : 'source';
|
||||
|
||||
const isTarget = isSourceHandle;
|
||||
const edge = edges.find((e) => e.id === id)!;
|
||||
|
||||
setUpdating(true);
|
||||
onEdgeUpdateStart?.(event, edge, handleType);
|
||||
|
||||
const _onEdgeUpdateEnd = (evt: MouseEvent | TouchEvent) => {
|
||||
setUpdating(false);
|
||||
onEdgeUpdateEnd?.(evt, edge, handleType);
|
||||
};
|
||||
|
||||
const onConnectEdge = (connection: Connection) => onEdgeUpdate?.(edge, connection);
|
||||
|
||||
XYHandle.onPointerDown(event.nativeEvent, {
|
||||
autoPanOnConnect,
|
||||
connectionMode,
|
||||
connectionRadius,
|
||||
domNode,
|
||||
handleId,
|
||||
nodeId,
|
||||
nodes,
|
||||
isTarget,
|
||||
edgeUpdaterType: handleType,
|
||||
lib,
|
||||
cancelConnection,
|
||||
panBy,
|
||||
isValidConnection,
|
||||
onConnect: onConnectEdge,
|
||||
onConnectStart,
|
||||
onConnectEnd,
|
||||
onEdgeUpdateEnd: _onEdgeUpdateEnd,
|
||||
updateConnection,
|
||||
getTransform: () => store.getState().transform,
|
||||
});
|
||||
};
|
||||
|
||||
const onEdgeUpdaterSourceMouseDown = (event: React.MouseEvent<SVGGElement, MouseEvent>): void =>
|
||||
handleEdgeUpdater(event, true);
|
||||
const onEdgeUpdaterTargetMouseDown = (event: React.MouseEvent<SVGGElement, MouseEvent>): void =>
|
||||
handleEdgeUpdater(event, false);
|
||||
|
||||
const onEdgeUpdaterMouseEnter = () => setUpdateHover(true);
|
||||
const onEdgeUpdaterMouseOut = () => setUpdateHover(false);
|
||||
|
||||
const inactive = !isSelectable && !onClick;
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (elementSelectionKeys.includes(event.key) && isSelectable) {
|
||||
const { unselectNodesAndEdges, addSelectedEdges, edges } = store.getState();
|
||||
const unselect = event.key === 'Escape';
|
||||
|
||||
if (unselect) {
|
||||
edgeRef.current?.blur();
|
||||
unselectNodesAndEdges({ edges: [edges.find((e) => e.id === id)!] });
|
||||
} else {
|
||||
addSelectedEdges([id]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<g
|
||||
className={cc([
|
||||
'react-flow__edge',
|
||||
`react-flow__edge-${type}`,
|
||||
className,
|
||||
{ selected, animated, inactive, updating: updateHover },
|
||||
])}
|
||||
onClick={onEdgeClick}
|
||||
onDoubleClick={onEdgeDoubleClickHandler}
|
||||
onContextMenu={onEdgeContextMenu}
|
||||
onMouseEnter={onEdgeMouseEnter}
|
||||
onMouseMove={onEdgeMouseMove}
|
||||
onMouseLeave={onEdgeMouseLeave}
|
||||
onKeyDown={isFocusable ? onKeyDown : undefined}
|
||||
tabIndex={isFocusable ? 0 : undefined}
|
||||
role={isFocusable ? 'button' : 'img'}
|
||||
data-id={id}
|
||||
data-testid={`rf__edge-${id}`}
|
||||
aria-label={ariaLabel === null ? undefined : ariaLabel ? ariaLabel : `Edge from ${source} to ${target}`}
|
||||
aria-describedby={isFocusable ? `${ARIA_EDGE_DESC_KEY}-${rfId}` : undefined}
|
||||
ref={edgeRef}
|
||||
>
|
||||
{!updating && (
|
||||
<EdgeComponent
|
||||
id={id}
|
||||
source={source}
|
||||
target={target}
|
||||
selected={selected}
|
||||
animated={animated}
|
||||
label={label}
|
||||
labelStyle={labelStyle}
|
||||
labelShowBg={labelShowBg}
|
||||
labelBgStyle={labelBgStyle}
|
||||
labelBgPadding={labelBgPadding}
|
||||
labelBgBorderRadius={labelBgBorderRadius}
|
||||
data={data}
|
||||
style={style}
|
||||
sourceX={edgePosition.sourceX}
|
||||
sourceY={edgePosition.sourceY}
|
||||
targetX={edgePosition.targetX}
|
||||
targetY={edgePosition.targetY}
|
||||
sourcePosition={edgePosition.sourcePosition}
|
||||
targetPosition={edgePosition.targetPosition}
|
||||
sourceHandleId={sourceHandleId}
|
||||
targetHandleId={targetHandleId}
|
||||
markerStart={markerStartUrl}
|
||||
markerEnd={markerEndUrl}
|
||||
pathOptions={pathOptions}
|
||||
interactionWidth={interactionWidth}
|
||||
/>
|
||||
)}
|
||||
{isUpdatable && (
|
||||
<>
|
||||
{(isUpdatable === 'source' || isUpdatable === true) && (
|
||||
<EdgeAnchor
|
||||
position={edgePosition.sourcePosition}
|
||||
centerX={edgePosition.sourceX}
|
||||
centerY={edgePosition.sourceY}
|
||||
radius={edgeUpdaterRadius}
|
||||
onMouseDown={onEdgeUpdaterSourceMouseDown}
|
||||
onMouseEnter={onEdgeUpdaterMouseEnter}
|
||||
onMouseOut={onEdgeUpdaterMouseOut}
|
||||
type="source"
|
||||
/>
|
||||
)}
|
||||
{(isUpdatable === 'target' || isUpdatable === true) && (
|
||||
<EdgeAnchor
|
||||
position={edgePosition.targetPosition}
|
||||
centerX={edgePosition.targetX}
|
||||
centerY={edgePosition.targetY}
|
||||
radius={edgeUpdaterRadius}
|
||||
onMouseDown={onEdgeUpdaterTargetMouseDown}
|
||||
onMouseEnter={onEdgeUpdaterMouseEnter}
|
||||
onMouseOut={onEdgeUpdaterMouseOut}
|
||||
type="target"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
EdgeWrapper.displayName = 'EdgeWrapper';
|
||||
|
||||
export default memo(EdgeWrapper);
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { ComponentType } from 'react';
|
||||
import type { EdgeProps, EdgeTypes } from '../../types';
|
||||
import {
|
||||
BezierEdgeInternal,
|
||||
StraightEdgeInternal,
|
||||
StepEdgeInternal,
|
||||
SmoothStepEdgeInternal,
|
||||
SimpleBezierEdgeInternal,
|
||||
} from '../Edges';
|
||||
|
||||
export const builtinEdgeTypes: EdgeTypes = {
|
||||
default: BezierEdgeInternal as ComponentType<EdgeProps>,
|
||||
straight: StraightEdgeInternal as ComponentType<EdgeProps>,
|
||||
step: StepEdgeInternal as ComponentType<EdgeProps>,
|
||||
smoothstep: SmoothStepEdgeInternal as ComponentType<EdgeProps>,
|
||||
simplebezier: SimpleBezierEdgeInternal as ComponentType<EdgeProps>,
|
||||
};
|
||||
@@ -1,290 +0,0 @@
|
||||
import { memo, useState, useMemo, useRef, type ComponentType, type KeyboardEvent, useCallback } from 'react';
|
||||
import cc from 'classcat';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { getMarkerId, elementSelectionKeys, XYHandle, type Connection, getEdgePosition } from '@xyflow/system';
|
||||
|
||||
import { useStoreApi, useStore } from '../../hooks/useStore';
|
||||
import { ARIA_EDGE_DESC_KEY } from '../A11yDescriptions';
|
||||
import { EdgeAnchor } from './EdgeAnchor';
|
||||
import { getMouseHandler } from './utils';
|
||||
import type { EdgeProps, WrapEdgeProps } from '../../types';
|
||||
|
||||
export default (EdgeComponent: ComponentType<EdgeProps>) => {
|
||||
const EdgeWrapper = ({
|
||||
id,
|
||||
className,
|
||||
type,
|
||||
data,
|
||||
onClick,
|
||||
onEdgeDoubleClick,
|
||||
selected,
|
||||
animated,
|
||||
label,
|
||||
labelStyle,
|
||||
labelShowBg,
|
||||
labelBgStyle,
|
||||
labelBgPadding,
|
||||
labelBgBorderRadius,
|
||||
style,
|
||||
source,
|
||||
target,
|
||||
isSelectable,
|
||||
hidden,
|
||||
sourceHandleId,
|
||||
targetHandleId,
|
||||
onContextMenu,
|
||||
onMouseEnter,
|
||||
onMouseMove,
|
||||
onMouseLeave,
|
||||
edgeUpdaterRadius,
|
||||
onEdgeUpdate,
|
||||
onEdgeUpdateStart,
|
||||
onEdgeUpdateEnd,
|
||||
markerEnd,
|
||||
markerStart,
|
||||
rfId,
|
||||
ariaLabel,
|
||||
isFocusable,
|
||||
isUpdatable,
|
||||
pathOptions,
|
||||
interactionWidth,
|
||||
}: WrapEdgeProps): JSX.Element | null => {
|
||||
const edgeRef = useRef<SVGGElement>(null);
|
||||
const [updateHover, setUpdateHover] = useState<boolean>(false);
|
||||
const [updating, setUpdating] = useState<boolean>(false);
|
||||
const store = useStoreApi();
|
||||
const edgePosition = useStore(
|
||||
useCallback(
|
||||
(state) => {
|
||||
const sourceNode = state.nodeLookup.get(source);
|
||||
const targetNode = state.nodeLookup.get(target);
|
||||
|
||||
if (!sourceNode || !targetNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getEdgePosition({
|
||||
id,
|
||||
sourceNode,
|
||||
targetNode,
|
||||
sourceHandle: sourceHandleId || null,
|
||||
targetHandle: targetHandleId || null,
|
||||
connectionMode: state.connectionMode,
|
||||
onError: state.onError,
|
||||
});
|
||||
},
|
||||
[source, target]
|
||||
),
|
||||
shallow
|
||||
);
|
||||
|
||||
const markerStartUrl = useMemo(() => `url(#${getMarkerId(markerStart, rfId)})`, [markerStart, rfId]);
|
||||
const markerEndUrl = useMemo(() => `url(#${getMarkerId(markerEnd, rfId)})`, [markerEnd, rfId]);
|
||||
|
||||
if (hidden || !edgePosition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onEdgeClick = (event: React.MouseEvent<SVGGElement, MouseEvent>): void => {
|
||||
const { edges, addSelectedEdges, unselectNodesAndEdges, multiSelectionActive } = store.getState();
|
||||
const edge = edges.find((e) => e.id === id);
|
||||
|
||||
if (!edge) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSelectable) {
|
||||
store.setState({ nodesSelectionActive: false });
|
||||
|
||||
if (edge.selected && multiSelectionActive) {
|
||||
unselectNodesAndEdges({ nodes: [], edges: [edge] });
|
||||
edgeRef.current?.blur();
|
||||
} else {
|
||||
addSelectedEdges([id]);
|
||||
}
|
||||
}
|
||||
|
||||
if (onClick) {
|
||||
onClick(event, edge);
|
||||
}
|
||||
};
|
||||
|
||||
const onEdgeDoubleClickHandler = getMouseHandler(id, store.getState, onEdgeDoubleClick);
|
||||
const onEdgeContextMenu = getMouseHandler(id, store.getState, onContextMenu);
|
||||
const onEdgeMouseEnter = getMouseHandler(id, store.getState, onMouseEnter);
|
||||
const onEdgeMouseMove = getMouseHandler(id, store.getState, onMouseMove);
|
||||
const onEdgeMouseLeave = getMouseHandler(id, store.getState, onMouseLeave);
|
||||
|
||||
const handleEdgeUpdater = (event: React.MouseEvent<SVGGElement, MouseEvent>, isSourceHandle: boolean) => {
|
||||
// avoid triggering edge updater if mouse btn is not left
|
||||
if (event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
autoPanOnConnect,
|
||||
domNode,
|
||||
edges,
|
||||
isValidConnection,
|
||||
connectionMode,
|
||||
connectionRadius,
|
||||
lib,
|
||||
onConnectStart,
|
||||
onConnectEnd,
|
||||
cancelConnection,
|
||||
nodes,
|
||||
panBy,
|
||||
updateConnection,
|
||||
} = store.getState();
|
||||
const nodeId = isSourceHandle ? target : source;
|
||||
const handleId = (isSourceHandle ? targetHandleId : sourceHandleId) || null;
|
||||
const handleType = isSourceHandle ? 'target' : 'source';
|
||||
|
||||
const isTarget = isSourceHandle;
|
||||
const edge = edges.find((e) => e.id === id)!;
|
||||
|
||||
setUpdating(true);
|
||||
onEdgeUpdateStart?.(event, edge, handleType);
|
||||
|
||||
const _onEdgeUpdateEnd = (evt: MouseEvent | TouchEvent) => {
|
||||
setUpdating(false);
|
||||
onEdgeUpdateEnd?.(evt, edge, handleType);
|
||||
};
|
||||
|
||||
const onConnectEdge = (connection: Connection) => onEdgeUpdate?.(edge, connection);
|
||||
|
||||
XYHandle.onPointerDown(event.nativeEvent, {
|
||||
autoPanOnConnect,
|
||||
connectionMode,
|
||||
connectionRadius,
|
||||
domNode,
|
||||
handleId,
|
||||
nodeId,
|
||||
nodes,
|
||||
isTarget,
|
||||
edgeUpdaterType: handleType,
|
||||
lib,
|
||||
cancelConnection,
|
||||
panBy,
|
||||
isValidConnection,
|
||||
onConnect: onConnectEdge,
|
||||
onConnectStart,
|
||||
onConnectEnd,
|
||||
onEdgeUpdateEnd: _onEdgeUpdateEnd,
|
||||
updateConnection,
|
||||
getTransform: () => store.getState().transform,
|
||||
});
|
||||
};
|
||||
|
||||
const onEdgeUpdaterSourceMouseDown = (event: React.MouseEvent<SVGGElement, MouseEvent>): void =>
|
||||
handleEdgeUpdater(event, true);
|
||||
const onEdgeUpdaterTargetMouseDown = (event: React.MouseEvent<SVGGElement, MouseEvent>): void =>
|
||||
handleEdgeUpdater(event, false);
|
||||
|
||||
const onEdgeUpdaterMouseEnter = () => setUpdateHover(true);
|
||||
const onEdgeUpdaterMouseOut = () => setUpdateHover(false);
|
||||
|
||||
const inactive = !isSelectable && !onClick;
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (elementSelectionKeys.includes(event.key) && isSelectable) {
|
||||
const { unselectNodesAndEdges, addSelectedEdges, edges } = store.getState();
|
||||
const unselect = event.key === 'Escape';
|
||||
|
||||
if (unselect) {
|
||||
edgeRef.current?.blur();
|
||||
unselectNodesAndEdges({ edges: [edges.find((e) => e.id === id)!] });
|
||||
} else {
|
||||
addSelectedEdges([id]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<g
|
||||
className={cc([
|
||||
'react-flow__edge',
|
||||
`react-flow__edge-${type}`,
|
||||
className,
|
||||
{ selected, animated, inactive, updating: updateHover },
|
||||
])}
|
||||
onClick={onEdgeClick}
|
||||
onDoubleClick={onEdgeDoubleClickHandler}
|
||||
onContextMenu={onEdgeContextMenu}
|
||||
onMouseEnter={onEdgeMouseEnter}
|
||||
onMouseMove={onEdgeMouseMove}
|
||||
onMouseLeave={onEdgeMouseLeave}
|
||||
onKeyDown={isFocusable ? onKeyDown : undefined}
|
||||
tabIndex={isFocusable ? 0 : undefined}
|
||||
role={isFocusable ? 'button' : 'img'}
|
||||
data-id={id}
|
||||
data-testid={`rf__edge-${id}`}
|
||||
aria-label={ariaLabel === null ? undefined : ariaLabel ? ariaLabel : `Edge from ${source} to ${target}`}
|
||||
aria-describedby={isFocusable ? `${ARIA_EDGE_DESC_KEY}-${rfId}` : undefined}
|
||||
ref={edgeRef}
|
||||
>
|
||||
{!updating && (
|
||||
<EdgeComponent
|
||||
id={id}
|
||||
source={source}
|
||||
target={target}
|
||||
selected={selected}
|
||||
animated={animated}
|
||||
label={label}
|
||||
labelStyle={labelStyle}
|
||||
labelShowBg={labelShowBg}
|
||||
labelBgStyle={labelBgStyle}
|
||||
labelBgPadding={labelBgPadding}
|
||||
labelBgBorderRadius={labelBgBorderRadius}
|
||||
data={data}
|
||||
style={style}
|
||||
sourceX={edgePosition.sourceX}
|
||||
sourceY={edgePosition.sourceY}
|
||||
targetX={edgePosition.targetX}
|
||||
targetY={edgePosition.targetY}
|
||||
sourcePosition={edgePosition.sourcePosition}
|
||||
targetPosition={edgePosition.targetPosition}
|
||||
sourceHandleId={sourceHandleId}
|
||||
targetHandleId={targetHandleId}
|
||||
markerStart={markerStartUrl}
|
||||
markerEnd={markerEndUrl}
|
||||
pathOptions={pathOptions}
|
||||
interactionWidth={interactionWidth}
|
||||
/>
|
||||
)}
|
||||
{isUpdatable && (
|
||||
<>
|
||||
{(isUpdatable === 'source' || isUpdatable === true) && (
|
||||
<EdgeAnchor
|
||||
position={edgePosition.sourcePosition}
|
||||
centerX={edgePosition.sourceX}
|
||||
centerY={edgePosition.sourceY}
|
||||
radius={edgeUpdaterRadius}
|
||||
onMouseDown={onEdgeUpdaterSourceMouseDown}
|
||||
onMouseEnter={onEdgeUpdaterMouseEnter}
|
||||
onMouseOut={onEdgeUpdaterMouseOut}
|
||||
type="source"
|
||||
/>
|
||||
)}
|
||||
{(isUpdatable === 'target' || isUpdatable === true) && (
|
||||
<EdgeAnchor
|
||||
position={edgePosition.targetPosition}
|
||||
centerX={edgePosition.targetX}
|
||||
centerY={edgePosition.targetY}
|
||||
radius={edgeUpdaterRadius}
|
||||
onMouseDown={onEdgeUpdaterTargetMouseDown}
|
||||
onMouseEnter={onEdgeUpdaterMouseEnter}
|
||||
onMouseOut={onEdgeUpdaterMouseOut}
|
||||
type="target"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
EdgeWrapper.displayName = 'EdgeWrapper';
|
||||
|
||||
return memo(EdgeWrapper);
|
||||
};
|
||||
@@ -0,0 +1,256 @@
|
||||
import { useEffect, useRef, memo, type MouseEvent, type KeyboardEvent } from 'react';
|
||||
import cc from 'classcat';
|
||||
import {
|
||||
clampPosition,
|
||||
elementSelectionKeys,
|
||||
errorMessages,
|
||||
getPositionWithOrigin,
|
||||
internalsSymbol,
|
||||
isInputDOMNode,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import { useStore, useStoreApi } from '../../hooks/useStore';
|
||||
import { Provider } from '../../contexts/NodeIdContext';
|
||||
import { ARIA_NODE_DESC_KEY } from '../A11yDescriptions';
|
||||
import useDrag from '../../hooks/useDrag';
|
||||
import useUpdateNodePositions from '../../hooks/useUpdateNodePositions';
|
||||
import { handleNodeClick } from '../Nodes/utils';
|
||||
import type { NodeWrapperProps } from '../../types';
|
||||
import { arrowKeyDiffs, builtinNodeTypes } from './utils';
|
||||
|
||||
const NodeWrapper = ({
|
||||
id,
|
||||
onClick,
|
||||
onMouseEnter,
|
||||
onMouseMove,
|
||||
onMouseLeave,
|
||||
onContextMenu,
|
||||
onDoubleClick,
|
||||
nodesDraggable,
|
||||
elementsSelectable,
|
||||
nodesConnectable,
|
||||
nodesFocusable,
|
||||
resizeObserver,
|
||||
noDragClassName,
|
||||
noPanClassName,
|
||||
disableKeyboardA11y,
|
||||
rfId,
|
||||
nodeTypes,
|
||||
nodeExtent,
|
||||
nodeOrigin,
|
||||
onError,
|
||||
}: NodeWrapperProps) => {
|
||||
const node = useStore((s) => s.nodeLookup.get(id)!);
|
||||
|
||||
let nodeType = node.type || 'default';
|
||||
let NodeComponent = nodeTypes?.[nodeType] || builtinNodeTypes[nodeType];
|
||||
|
||||
if (NodeComponent === undefined) {
|
||||
onError?.('003', errorMessages['error003'](nodeType));
|
||||
nodeType = 'default';
|
||||
NodeComponent = builtinNodeTypes.default;
|
||||
}
|
||||
|
||||
const isDraggable = !!(node.draggable || (nodesDraggable && typeof node.draggable === 'undefined'));
|
||||
const isSelectable = !!(node.selectable || (elementsSelectable && typeof node.selectable === 'undefined'));
|
||||
const isConnectable = !!(node.connectable || (nodesConnectable && typeof node.connectable === 'undefined'));
|
||||
const isFocusable = !!(node.focusable || (nodesFocusable && typeof node.focusable === 'undefined'));
|
||||
|
||||
const store = useStoreApi();
|
||||
const nodeRef = useRef<HTMLDivElement>(null);
|
||||
const prevSourcePosition = useRef(node.sourcePosition);
|
||||
const prevTargetPosition = useRef(node.targetPosition);
|
||||
const prevType = useRef(nodeType);
|
||||
|
||||
const updatePositions = useUpdateNodePositions();
|
||||
|
||||
useEffect(() => {
|
||||
if (nodeRef.current && !node.hidden) {
|
||||
const currNode = nodeRef.current;
|
||||
resizeObserver?.observe(currNode);
|
||||
|
||||
return () => resizeObserver?.unobserve(currNode);
|
||||
}
|
||||
}, [node.hidden]);
|
||||
|
||||
useEffect(() => {
|
||||
// when the user programmatically changes the source or handle position, we re-initialize the node
|
||||
const typeChanged = prevType.current !== nodeType;
|
||||
const sourcePosChanged = prevSourcePosition.current !== node.sourcePosition;
|
||||
const targetPosChanged = prevTargetPosition.current !== node.targetPosition;
|
||||
|
||||
if (nodeRef.current && (typeChanged || sourcePosChanged || targetPosChanged)) {
|
||||
if (typeChanged) {
|
||||
prevType.current = nodeType;
|
||||
}
|
||||
if (sourcePosChanged) {
|
||||
prevSourcePosition.current = node.sourcePosition;
|
||||
}
|
||||
if (targetPosChanged) {
|
||||
prevTargetPosition.current = node.targetPosition;
|
||||
}
|
||||
store.getState().updateNodeDimensions(new Map([[id, { id, nodeElement: nodeRef.current, forceUpdate: true }]]));
|
||||
}
|
||||
}, [id, nodeType, node.sourcePosition, node.targetPosition]);
|
||||
|
||||
const dragging = useDrag({
|
||||
nodeRef,
|
||||
disabled: node.hidden || !isDraggable,
|
||||
noDragClassName,
|
||||
handleSelector: node.dragHandle,
|
||||
nodeId: id,
|
||||
isSelectable,
|
||||
});
|
||||
|
||||
if (node.hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const width = node.width ?? undefined;
|
||||
const height = node.height ?? undefined;
|
||||
const computedWidth = node.computed?.width;
|
||||
const computedHeight = node.computed?.height;
|
||||
const positionAbsolute = nodeExtent
|
||||
? clampPosition(node.computed?.positionAbsolute, nodeExtent)
|
||||
: node.computed?.positionAbsolute || { x: 0, y: 0 };
|
||||
const positionAbsoluteOrigin = getPositionWithOrigin({
|
||||
x: positionAbsolute.x,
|
||||
y: positionAbsolute.y,
|
||||
width: computedWidth ?? width ?? 0,
|
||||
height: computedHeight ?? height ?? 0,
|
||||
origin: node.origin || nodeOrigin,
|
||||
});
|
||||
const initialized = (!!computedWidth && !!computedHeight) || (!!width && !!height);
|
||||
const zIndex = node[internalsSymbol]?.z ?? 0;
|
||||
const isParent = !!node[internalsSymbol]?.isParent;
|
||||
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
|
||||
|
||||
const onMouseEnterHandler =
|
||||
onMouseEnter === undefined ? undefined : (event: MouseEvent) => onMouseEnter(event, { ...node });
|
||||
const onMouseMoveHandler =
|
||||
onMouseMove === undefined ? undefined : (event: MouseEvent) => onMouseMove(event, { ...node });
|
||||
const onMouseLeaveHandler =
|
||||
onMouseLeave === undefined ? undefined : (event: MouseEvent) => onMouseLeave(event, { ...node });
|
||||
const onContextMenuHandler =
|
||||
onContextMenu === undefined ? undefined : (event: MouseEvent) => onContextMenu(event, { ...node });
|
||||
const onDoubleClickHandler =
|
||||
onDoubleClick === undefined ? undefined : (event: MouseEvent) => onDoubleClick(event, { ...node });
|
||||
|
||||
const onSelectNodeHandler = (event: MouseEvent) => {
|
||||
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
|
||||
handleNodeClick({
|
||||
id,
|
||||
store,
|
||||
nodeRef,
|
||||
});
|
||||
}
|
||||
|
||||
if (onClick) {
|
||||
onClick(event, { ...node });
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (isInputDOMNode(event.nativeEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (elementSelectionKeys.includes(event.key) && isSelectable) {
|
||||
const unselect = event.key === 'Escape';
|
||||
|
||||
handleNodeClick({
|
||||
id,
|
||||
store,
|
||||
unselect,
|
||||
nodeRef,
|
||||
});
|
||||
} else if (
|
||||
!disableKeyboardA11y &&
|
||||
isDraggable &&
|
||||
node.selected &&
|
||||
Object.prototype.hasOwnProperty.call(arrowKeyDiffs, event.key)
|
||||
) {
|
||||
store.setState({
|
||||
ariaLiveMessage: `Moved selected node ${event.key
|
||||
.replace('Arrow', '')
|
||||
.toLowerCase()}. New position, x: ${~~positionAbsolute.x}, y: ${~~positionAbsolute.y}`,
|
||||
});
|
||||
|
||||
updatePositions({
|
||||
x: arrowKeyDiffs[event.key].x,
|
||||
y: arrowKeyDiffs[event.key].y,
|
||||
isShiftPressed: event.shiftKey,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cc([
|
||||
'react-flow__node',
|
||||
`react-flow__node-${nodeType}`,
|
||||
{
|
||||
// this is overwritable by passing `nopan` as a class name
|
||||
[noPanClassName]: isDraggable,
|
||||
},
|
||||
node.className,
|
||||
{
|
||||
selected: node.selected,
|
||||
selectable: isSelectable,
|
||||
parent: isParent,
|
||||
dragging,
|
||||
},
|
||||
])}
|
||||
ref={nodeRef}
|
||||
style={{
|
||||
zIndex,
|
||||
transform: `translate(${positionAbsoluteOrigin.x}px,${positionAbsoluteOrigin.y}px)`,
|
||||
pointerEvents: hasPointerEvents ? 'all' : 'none',
|
||||
visibility: initialized ? 'visible' : 'hidden',
|
||||
width,
|
||||
height,
|
||||
...node.style,
|
||||
}}
|
||||
data-id={id}
|
||||
data-testid={`rf__node-${id}`}
|
||||
onMouseEnter={onMouseEnterHandler}
|
||||
onMouseMove={onMouseMoveHandler}
|
||||
onMouseLeave={onMouseLeaveHandler}
|
||||
onContextMenu={onContextMenuHandler}
|
||||
onClick={onSelectNodeHandler}
|
||||
onDoubleClick={onDoubleClickHandler}
|
||||
onKeyDown={isFocusable ? onKeyDown : undefined}
|
||||
tabIndex={isFocusable ? 0 : undefined}
|
||||
role={isFocusable ? 'button' : undefined}
|
||||
aria-describedby={disableKeyboardA11y ? undefined : `${ARIA_NODE_DESC_KEY}-${rfId}`}
|
||||
aria-label={node.ariaLabel}
|
||||
>
|
||||
<Provider value={id}>
|
||||
<NodeComponent
|
||||
id={id}
|
||||
data={node.data}
|
||||
type={nodeType}
|
||||
width={computedWidth}
|
||||
height={computedHeight}
|
||||
positionAbsoluteX={positionAbsolute.x}
|
||||
positionAbsoluteY={positionAbsolute.y}
|
||||
selected={node.selected}
|
||||
isConnectable={isConnectable}
|
||||
sourcePosition={node.sourcePosition}
|
||||
targetPosition={node.targetPosition}
|
||||
dragging={dragging}
|
||||
dragHandle={node.dragHandle}
|
||||
zIndex={zIndex}
|
||||
/>
|
||||
</Provider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
NodeWrapper.displayName = 'NodeWrapper';
|
||||
|
||||
export default memo(NodeWrapper);
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { ComponentType } from 'react';
|
||||
import type { NodeProps, XYPosition } from '@xyflow/system';
|
||||
|
||||
import InputNode from '../Nodes/InputNode';
|
||||
import DefaultNode from '../Nodes/DefaultNode';
|
||||
import GroupNode from '../Nodes/GroupNode';
|
||||
import OutputNode from '../Nodes/OutputNode';
|
||||
import type { NodeTypes } from '../../types';
|
||||
|
||||
export const arrowKeyDiffs: Record<string, XYPosition> = {
|
||||
ArrowUp: { x: 0, y: -1 },
|
||||
ArrowDown: { x: 0, y: 1 },
|
||||
ArrowLeft: { x: -1, y: 0 },
|
||||
ArrowRight: { x: 1, y: 0 },
|
||||
};
|
||||
|
||||
export const builtinNodeTypes: NodeTypes = {
|
||||
input: InputNode as ComponentType<NodeProps>,
|
||||
default: DefaultNode as ComponentType<NodeProps>,
|
||||
output: OutputNode as ComponentType<NodeProps>,
|
||||
group: GroupNode as ComponentType<NodeProps>,
|
||||
};
|
||||
@@ -1,22 +1,9 @@
|
||||
import type { MouseEvent, RefObject } from 'react';
|
||||
import type { RefObject } from 'react';
|
||||
import type { StoreApi } from 'zustand';
|
||||
|
||||
import type { Node, ReactFlowState } from '../../types';
|
||||
import type { ReactFlowState } from '../../types';
|
||||
import { errorMessages } from '@xyflow/system';
|
||||
|
||||
export function getMouseHandler(
|
||||
id: string,
|
||||
getState: StoreApi<ReactFlowState>['getState'],
|
||||
handler?: (event: MouseEvent, node: Node) => void
|
||||
) {
|
||||
return handler === undefined
|
||||
? handler
|
||||
: (event: MouseEvent) => {
|
||||
const node = getState().nodeLookup.get(id)!;
|
||||
handler(event, { ...node });
|
||||
};
|
||||
}
|
||||
|
||||
// this handler is called by
|
||||
// 1. the click handler when node is not draggable or selectNodesOnDrag = false
|
||||
// or
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
import { useEffect, useRef, memo, type ComponentType, type MouseEvent, type KeyboardEvent } from 'react';
|
||||
import cc from 'classcat';
|
||||
import { elementSelectionKeys, isInputDOMNode, type NodeProps, type XYPosition } from '@xyflow/system';
|
||||
|
||||
import { useStoreApi } from '../../hooks/useStore';
|
||||
import { Provider } from '../../contexts/NodeIdContext';
|
||||
import { ARIA_NODE_DESC_KEY } from '../A11yDescriptions';
|
||||
import useDrag from '../../hooks/useDrag';
|
||||
import useUpdateNodePositions from '../../hooks/useUpdateNodePositions';
|
||||
import { getMouseHandler, handleNodeClick } from './utils';
|
||||
import type { WrapNodeProps } from '../../types';
|
||||
|
||||
export const arrowKeyDiffs: Record<string, XYPosition> = {
|
||||
ArrowUp: { x: 0, y: -1 },
|
||||
ArrowDown: { x: 0, y: 1 },
|
||||
ArrowLeft: { x: -1, y: 0 },
|
||||
ArrowRight: { x: 1, y: 0 },
|
||||
};
|
||||
|
||||
export default (NodeComponent: ComponentType<NodeProps>) => {
|
||||
const NodeWrapper = ({
|
||||
id,
|
||||
type,
|
||||
data,
|
||||
xPos,
|
||||
yPos,
|
||||
xPosOrigin,
|
||||
yPosOrigin,
|
||||
selected,
|
||||
onClick,
|
||||
onMouseEnter,
|
||||
onMouseMove,
|
||||
onMouseLeave,
|
||||
onContextMenu,
|
||||
onDoubleClick,
|
||||
style,
|
||||
className,
|
||||
isDraggable,
|
||||
isSelectable,
|
||||
isConnectable,
|
||||
isFocusable,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
hidden,
|
||||
resizeObserver,
|
||||
dragHandle,
|
||||
zIndex,
|
||||
isParent,
|
||||
noDragClassName,
|
||||
noPanClassName,
|
||||
initialized,
|
||||
disableKeyboardA11y,
|
||||
ariaLabel,
|
||||
rfId,
|
||||
positionAbsolute,
|
||||
width,
|
||||
height,
|
||||
}: WrapNodeProps) => {
|
||||
const store = useStoreApi();
|
||||
const nodeRef = useRef<HTMLDivElement>(null);
|
||||
const prevSourcePosition = useRef(sourcePosition);
|
||||
const prevTargetPosition = useRef(targetPosition);
|
||||
const prevType = useRef(type);
|
||||
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
|
||||
const updatePositions = useUpdateNodePositions();
|
||||
|
||||
const onMouseEnterHandler = getMouseHandler(id, store.getState, onMouseEnter);
|
||||
const onMouseMoveHandler = getMouseHandler(id, store.getState, onMouseMove);
|
||||
const onMouseLeaveHandler = getMouseHandler(id, store.getState, onMouseLeave);
|
||||
const onContextMenuHandler = getMouseHandler(id, store.getState, onContextMenu);
|
||||
const onDoubleClickHandler = getMouseHandler(id, store.getState, onDoubleClick);
|
||||
const onSelectNodeHandler = (event: MouseEvent) => {
|
||||
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
|
||||
handleNodeClick({
|
||||
id,
|
||||
store,
|
||||
nodeRef,
|
||||
});
|
||||
}
|
||||
|
||||
if (onClick) {
|
||||
const node = store.getState().nodes.find((n) => n.id === id)!;
|
||||
onClick(event, { ...node });
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (isInputDOMNode(event.nativeEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (elementSelectionKeys.includes(event.key) && isSelectable) {
|
||||
const unselect = event.key === 'Escape';
|
||||
|
||||
handleNodeClick({
|
||||
id,
|
||||
store,
|
||||
unselect,
|
||||
nodeRef,
|
||||
});
|
||||
} else if (
|
||||
!disableKeyboardA11y &&
|
||||
isDraggable &&
|
||||
selected &&
|
||||
Object.prototype.hasOwnProperty.call(arrowKeyDiffs, event.key)
|
||||
) {
|
||||
store.setState({
|
||||
ariaLiveMessage: `Moved selected node ${event.key
|
||||
.replace('Arrow', '')
|
||||
.toLowerCase()}. New position, x: ${~~xPos}, y: ${~~yPos}`,
|
||||
});
|
||||
|
||||
updatePositions({
|
||||
x: arrowKeyDiffs[event.key].x,
|
||||
y: arrowKeyDiffs[event.key].y,
|
||||
isShiftPressed: event.shiftKey,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (nodeRef.current && !hidden) {
|
||||
const currNode = nodeRef.current;
|
||||
resizeObserver?.observe(currNode);
|
||||
|
||||
return () => resizeObserver?.unobserve(currNode);
|
||||
}
|
||||
}, [hidden]);
|
||||
|
||||
useEffect(() => {
|
||||
// when the user programmatically changes the source or handle position, we re-initialize the node
|
||||
const typeChanged = prevType.current !== type;
|
||||
const sourcePosChanged = prevSourcePosition.current !== sourcePosition;
|
||||
const targetPosChanged = prevTargetPosition.current !== targetPosition;
|
||||
|
||||
if (nodeRef.current && (typeChanged || sourcePosChanged || targetPosChanged)) {
|
||||
if (typeChanged) {
|
||||
prevType.current = type;
|
||||
}
|
||||
if (sourcePosChanged) {
|
||||
prevSourcePosition.current = sourcePosition;
|
||||
}
|
||||
if (targetPosChanged) {
|
||||
prevTargetPosition.current = targetPosition;
|
||||
}
|
||||
store.getState().updateNodeDimensions(new Map([[id, { id, nodeElement: nodeRef.current, forceUpdate: true }]]));
|
||||
}
|
||||
}, [id, type, sourcePosition, targetPosition]);
|
||||
|
||||
const dragging = useDrag({
|
||||
nodeRef,
|
||||
disabled: hidden || !isDraggable,
|
||||
noDragClassName,
|
||||
handleSelector: dragHandle,
|
||||
nodeId: id,
|
||||
isSelectable,
|
||||
});
|
||||
|
||||
if (hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cc([
|
||||
'react-flow__node',
|
||||
`react-flow__node-${type}`,
|
||||
{
|
||||
// this is overwritable by passing `nopan` as a class name
|
||||
[noPanClassName]: isDraggable,
|
||||
},
|
||||
className,
|
||||
{
|
||||
selected,
|
||||
selectable: isSelectable,
|
||||
parent: isParent,
|
||||
dragging,
|
||||
},
|
||||
])}
|
||||
ref={nodeRef}
|
||||
style={{
|
||||
zIndex,
|
||||
transform: `translate(${xPosOrigin}px,${yPosOrigin}px)`,
|
||||
pointerEvents: hasPointerEvents ? 'all' : 'none',
|
||||
visibility: initialized ? 'visible' : 'hidden',
|
||||
width,
|
||||
height,
|
||||
...style,
|
||||
}}
|
||||
data-id={id}
|
||||
data-testid={`rf__node-${id}`}
|
||||
onMouseEnter={onMouseEnterHandler}
|
||||
onMouseMove={onMouseMoveHandler}
|
||||
onMouseLeave={onMouseLeaveHandler}
|
||||
onContextMenu={onContextMenuHandler}
|
||||
onClick={onSelectNodeHandler}
|
||||
onDoubleClick={onDoubleClickHandler}
|
||||
onKeyDown={isFocusable ? onKeyDown : undefined}
|
||||
tabIndex={isFocusable ? 0 : undefined}
|
||||
role={isFocusable ? 'button' : undefined}
|
||||
aria-describedby={disableKeyboardA11y ? undefined : `${ARIA_NODE_DESC_KEY}-${rfId}`}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
<Provider value={id}>
|
||||
<NodeComponent
|
||||
id={id}
|
||||
data={data}
|
||||
type={type}
|
||||
width={width}
|
||||
height={height}
|
||||
positionAbsolute={positionAbsolute}
|
||||
selected={selected}
|
||||
isConnectable={isConnectable}
|
||||
sourcePosition={sourcePosition}
|
||||
targetPosition={targetPosition}
|
||||
dragging={dragging}
|
||||
dragHandle={dragHandle}
|
||||
zIndex={zIndex}
|
||||
/>
|
||||
</Provider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
NodeWrapper.displayName = 'NodeWrapper';
|
||||
|
||||
return memo(NodeWrapper);
|
||||
};
|
||||
@@ -10,9 +10,9 @@ import { getNodesBounds } from '@xyflow/system';
|
||||
|
||||
import { useStore, useStoreApi } from '../../hooks/useStore';
|
||||
import useDrag from '../../hooks/useDrag';
|
||||
import { arrowKeyDiffs } from '../Nodes/wrapNode';
|
||||
import useUpdateNodePositions from '../../hooks/useUpdateNodePositions';
|
||||
import type { Node, ReactFlowState } from '../../types';
|
||||
import { arrowKeyDiffs } from '../NodeWrapper/utils';
|
||||
|
||||
export type NodesSelectionProps = {
|
||||
onSelectionContextMenu?: (event: MouseEvent, nodes: Node[]) => void;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { memo, ReactNode } from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import cc from 'classcat';
|
||||
import { errorMessages } from '@xyflow/system';
|
||||
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
import useVisibleEdges from '../../hooks/useVisibleEdges';
|
||||
import MarkerDefinitions from './MarkerDefinitions';
|
||||
import { GraphViewProps } from '../GraphView';
|
||||
import type { EdgeTypesWrapped, ReactFlowState } from '../../types';
|
||||
import type { ReactFlowState } from '../../types';
|
||||
import EdgeWrapper from '../../components/EdgeWrapper';
|
||||
|
||||
type EdgeRendererProps = Pick<
|
||||
GraphViewProps,
|
||||
@@ -27,8 +27,8 @@ type EdgeRendererProps = Pick<
|
||||
| 'elevateEdgesOnSelect'
|
||||
| 'rfId'
|
||||
| 'disableKeyboardA11y'
|
||||
| 'edgeTypes'
|
||||
> & {
|
||||
edgeTypes: EdgeTypesWrapped;
|
||||
elevateEdgesOnSelect: boolean;
|
||||
children: ReactNode;
|
||||
};
|
||||
@@ -74,14 +74,6 @@ const EdgeRenderer = ({
|
||||
{isMaxLevel && <MarkerDefinitions defaultColor={defaultMarkerColor} rfId={rfId} />}
|
||||
<>
|
||||
{edges.map((edge) => {
|
||||
let edgeType = edge.type || 'default';
|
||||
|
||||
if (!edgeTypes[edgeType]) {
|
||||
onError?.('011', errorMessages['error011'](edgeType));
|
||||
edgeType = 'default';
|
||||
}
|
||||
|
||||
const EdgeComponent = edgeTypes[edgeType];
|
||||
const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined'));
|
||||
const isUpdatable =
|
||||
typeof onEdgeUpdate !== 'undefined' &&
|
||||
@@ -92,7 +84,7 @@ const EdgeRenderer = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<EdgeComponent
|
||||
<EdgeWrapper
|
||||
key={edge.id}
|
||||
id={edge.id}
|
||||
className={cc([edge.className, noPanClassName])}
|
||||
@@ -131,6 +123,8 @@ const EdgeRenderer = ({
|
||||
isUpdatable={isUpdatable}
|
||||
pathOptions={'pathOptions' in edge ? edge.pathOptions : undefined}
|
||||
interactionWidth={edge.interactionWidth}
|
||||
onError={onError}
|
||||
edgeTypes={edgeTypes}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import type { ComponentType } from 'react';
|
||||
|
||||
import {
|
||||
BezierEdgeInternal,
|
||||
SmoothStepEdgeInternal,
|
||||
StepEdgeInternal,
|
||||
StraightEdgeInternal,
|
||||
SimpleBezierEdgeInternal,
|
||||
} from '../../components/Edges';
|
||||
import wrapEdge from '../../components/Edges/wrapEdge';
|
||||
import type { EdgeProps, EdgeTypes, EdgeTypesWrapped } from '../../types';
|
||||
|
||||
export type CreateEdgeTypes = (edgeTypes: EdgeTypes) => EdgeTypesWrapped;
|
||||
|
||||
export function createEdgeTypes(edgeTypes: EdgeTypes): EdgeTypesWrapped {
|
||||
const standardTypes: EdgeTypesWrapped = {
|
||||
default: wrapEdge((edgeTypes.default || BezierEdgeInternal) as ComponentType<EdgeProps>),
|
||||
straight: wrapEdge((edgeTypes.bezier || StraightEdgeInternal) as ComponentType<EdgeProps>),
|
||||
step: wrapEdge((edgeTypes.step || StepEdgeInternal) as ComponentType<EdgeProps>),
|
||||
smoothstep: wrapEdge((edgeTypes.step || SmoothStepEdgeInternal) as ComponentType<EdgeProps>),
|
||||
simplebezier: wrapEdge((edgeTypes.simplebezier || SimpleBezierEdgeInternal) as ComponentType<EdgeProps>),
|
||||
};
|
||||
|
||||
const wrappedTypes = {} as EdgeTypesWrapped;
|
||||
const specialTypes: EdgeTypesWrapped = Object.keys(edgeTypes)
|
||||
.filter((k) => !['default', 'bezier'].includes(k))
|
||||
.reduce((res, key) => {
|
||||
res[key] = wrapEdge((edgeTypes[key] || BezierEdgeInternal) as ComponentType<EdgeProps>);
|
||||
|
||||
return res;
|
||||
}, wrappedTypes);
|
||||
|
||||
return {
|
||||
...standardTypes,
|
||||
...specialTypes,
|
||||
};
|
||||
}
|
||||
@@ -8,19 +8,15 @@ import useOnInitHandler from '../../hooks/useOnInitHandler';
|
||||
import useViewportSync from '../../hooks/useViewportSync';
|
||||
import ConnectionLine from '../../components/ConnectionLine';
|
||||
import type { ReactFlowProps } from '../../types';
|
||||
import { createNodeTypes } from '../NodeRenderer/utils';
|
||||
import { createEdgeTypes } from '../EdgeRenderer/utils';
|
||||
import { useNodeOrEdgeTypes } from './utils';
|
||||
import useNodeOrEdgeTypesWarning from './useNodeOrEdgeTypesWarning';
|
||||
|
||||
export type GraphViewProps = Omit<
|
||||
ReactFlowProps,
|
||||
'onSelectionChange' | 'nodes' | 'edges' | 'nodeTypes' | 'edgeTypes' | 'onMove' | 'onMoveStart' | 'onMoveEnd'
|
||||
'onSelectionChange' | 'nodes' | 'edges' | 'onMove' | 'onMoveStart' | 'onMoveEnd'
|
||||
> &
|
||||
Required<
|
||||
Pick<
|
||||
ReactFlowProps,
|
||||
| 'nodeTypes'
|
||||
| 'edgeTypes'
|
||||
| 'selectionKeyCode'
|
||||
| 'deleteKeyCode'
|
||||
| 'multiSelectionKeyCode'
|
||||
@@ -108,8 +104,8 @@ const GraphView = ({
|
||||
viewport,
|
||||
onViewportChange,
|
||||
}: GraphViewProps) => {
|
||||
const nodeTypesWrapped = useNodeOrEdgeTypes(nodeTypes, createNodeTypes);
|
||||
const edgeTypesWrapped = useNodeOrEdgeTypes(edgeTypes, createEdgeTypes);
|
||||
useNodeOrEdgeTypesWarning(nodeTypes);
|
||||
useNodeOrEdgeTypesWarning(edgeTypes);
|
||||
|
||||
useOnInitHandler(onInit);
|
||||
useViewportSync(viewport);
|
||||
@@ -154,7 +150,7 @@ const GraphView = ({
|
||||
>
|
||||
<ViewportWrapper>
|
||||
<EdgeRenderer
|
||||
edgeTypes={edgeTypesWrapped}
|
||||
edgeTypes={edgeTypes}
|
||||
onEdgeClick={onEdgeClick}
|
||||
onEdgeDoubleClick={onEdgeDoubleClick}
|
||||
onEdgeUpdate={onEdgeUpdate}
|
||||
@@ -182,7 +178,7 @@ const GraphView = ({
|
||||
<div className="react-flow__edgelabel-renderer" />
|
||||
|
||||
<NodeRenderer
|
||||
nodeTypes={nodeTypesWrapped}
|
||||
nodeTypes={nodeTypes}
|
||||
onNodeClick={onNodeClick}
|
||||
onNodeDoubleClick={onNodeDoubleClick}
|
||||
onNodeMouseEnter={onNodeMouseEnter}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { errorMessages } from '@xyflow/system';
|
||||
|
||||
import type { EdgeTypes, NodeTypes } from '../../types';
|
||||
import { useStoreApi } from '../../hooks/useStore';
|
||||
|
||||
const emptyTypes = {};
|
||||
|
||||
/*
|
||||
* This hook warns the user if node or edgeTypes change.
|
||||
*/
|
||||
export function useNodeOrEdgeTypesWarning(nodeOrEdgeTypes?: NodeTypes): void;
|
||||
export function useNodeOrEdgeTypesWarning(nodeOrEdgeTypes?: EdgeTypes): void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export default function useNodeOrEdgeTypesWarning(nodeOrEdgeTypes: any = emptyTypes): any {
|
||||
const updateCount = useRef(0);
|
||||
const store = useStoreApi();
|
||||
|
||||
useEffect(() => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
if (updateCount.current > 1) {
|
||||
store.getState().onError?.('002', errorMessages['error002']());
|
||||
}
|
||||
updateCount.current += 1;
|
||||
}
|
||||
}, [nodeOrEdgeTypes]);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { errorMessages } from '@xyflow/system';
|
||||
|
||||
import { CreateEdgeTypes } from '../EdgeRenderer/utils';
|
||||
import { CreateNodeTypes } from '../NodeRenderer/utils';
|
||||
import type { EdgeTypes, EdgeTypesWrapped, NodeTypes, NodeTypesWrapped } from '../../types';
|
||||
import { useStoreApi } from '../../hooks/useStore';
|
||||
|
||||
export function useNodeOrEdgeTypes(nodeOrEdgeTypes: NodeTypes, createTypes: CreateNodeTypes): NodeTypesWrapped;
|
||||
export function useNodeOrEdgeTypes(nodeOrEdgeTypes: EdgeTypes, createTypes: CreateEdgeTypes): EdgeTypesWrapped;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function useNodeOrEdgeTypes(nodeOrEdgeTypes: any, createTypes: any): any {
|
||||
const typesKeysRef = useRef<string[] | null>(null);
|
||||
const store = useStoreApi();
|
||||
|
||||
const typesParsed = useMemo(() => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const typeKeys = Object.keys(nodeOrEdgeTypes);
|
||||
|
||||
if (shallow(typesKeysRef.current, typeKeys)) {
|
||||
store.getState().onError?.('002', errorMessages['error002']());
|
||||
}
|
||||
|
||||
typesKeysRef.current = typeKeys;
|
||||
}
|
||||
return createTypes(nodeOrEdgeTypes);
|
||||
}, [nodeOrEdgeTypes]);
|
||||
|
||||
return typesParsed;
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
import { memo, useMemo, useEffect, useRef, type ComponentType } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { internalsSymbol, errorMessages, Position, clampPosition, getPositionWithOrigin } from '@xyflow/system';
|
||||
|
||||
import useVisibleNodes from '../../hooks/useVisibleNodes';
|
||||
import useVisibleNodesIds from '../../hooks/useVisibleNodes';
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
import { containerStyle } from '../../styles/utils';
|
||||
import { GraphViewProps } from '../GraphView';
|
||||
import type { NodeTypesWrapped, ReactFlowState, WrapNodeProps } from '../../types';
|
||||
import type { ReactFlowState } from '../../types';
|
||||
import useResizeObserver from './useResizeObserver';
|
||||
import NodeWrapper from '../../components/NodeWrapper';
|
||||
|
||||
type NodeRendererProps = Pick<
|
||||
export type NodeRendererProps = Pick<
|
||||
GraphViewProps,
|
||||
| 'onNodeClick'
|
||||
| 'onNodeDoubleClick'
|
||||
@@ -23,127 +24,71 @@ type NodeRendererProps = Pick<
|
||||
| 'disableKeyboardA11y'
|
||||
| 'nodeOrigin'
|
||||
| 'nodeExtent'
|
||||
> & {
|
||||
nodeTypes: NodeTypesWrapped;
|
||||
};
|
||||
| 'nodeTypes'
|
||||
>;
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
nodesDraggable: s.nodesDraggable,
|
||||
nodesConnectable: s.nodesConnectable,
|
||||
nodesFocusable: s.nodesFocusable,
|
||||
elementsSelectable: s.elementsSelectable,
|
||||
updateNodeDimensions: s.updateNodeDimensions,
|
||||
onError: s.onError,
|
||||
});
|
||||
|
||||
const NodeRenderer = (props: NodeRendererProps) => {
|
||||
const { nodesDraggable, nodesConnectable, nodesFocusable, elementsSelectable, updateNodeDimensions, onError } =
|
||||
useStore(selector, shallow);
|
||||
const nodes = useVisibleNodes(props.onlyRenderVisibleElements);
|
||||
const resizeObserverRef = useRef<ResizeObserver>();
|
||||
|
||||
const resizeObserver = useMemo(() => {
|
||||
if (typeof ResizeObserver === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
|
||||
const updates = new Map();
|
||||
|
||||
entries.forEach((entry: ResizeObserverEntry) => {
|
||||
const id = entry.target.getAttribute('data-id') as string;
|
||||
updates.set(id, {
|
||||
id,
|
||||
nodeElement: entry.target as HTMLDivElement,
|
||||
forceUpdate: true,
|
||||
});
|
||||
});
|
||||
|
||||
updateNodeDimensions(updates);
|
||||
});
|
||||
|
||||
resizeObserverRef.current = observer;
|
||||
|
||||
return observer;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
resizeObserverRef?.current?.disconnect();
|
||||
};
|
||||
}, []);
|
||||
const { nodesDraggable, nodesConnectable, nodesFocusable, elementsSelectable, onError } = useStore(selector, shallow);
|
||||
const nodeIds = useVisibleNodesIds(props.onlyRenderVisibleElements);
|
||||
const resizeObserver = useResizeObserver();
|
||||
|
||||
return (
|
||||
<div className="react-flow__nodes" style={containerStyle}>
|
||||
{nodes.map((node) => {
|
||||
let nodeType = node.type || 'default';
|
||||
|
||||
if (!props.nodeTypes[nodeType]) {
|
||||
onError?.('003', errorMessages['error003'](nodeType));
|
||||
|
||||
nodeType = 'default';
|
||||
}
|
||||
|
||||
const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType<WrapNodeProps>;
|
||||
const isDraggable = !!(node.draggable || (nodesDraggable && typeof node.draggable === 'undefined'));
|
||||
const isSelectable = !!(node.selectable || (elementsSelectable && typeof node.selectable === 'undefined'));
|
||||
const isConnectable = !!(node.connectable || (nodesConnectable && typeof node.connectable === 'undefined'));
|
||||
const isFocusable = !!(node.focusable || (nodesFocusable && typeof node.focusable === 'undefined'));
|
||||
|
||||
const clampedPosition = props.nodeExtent
|
||||
? clampPosition(node.computed?.positionAbsolute, props.nodeExtent)
|
||||
: node.computed?.positionAbsolute;
|
||||
|
||||
const posX = clampedPosition?.x ?? 0;
|
||||
const posY = clampedPosition?.y ?? 0;
|
||||
const posOrigin = getPositionWithOrigin({
|
||||
x: posX,
|
||||
y: posY,
|
||||
width: node.computed?.width ?? node.width ?? 0,
|
||||
height: node.computed?.height ?? node.height ?? 0,
|
||||
origin: node.origin || props.nodeOrigin,
|
||||
});
|
||||
const initialized = (!!node.computed?.width && !!node.computed?.height) || (!!node.width && !!node.height);
|
||||
|
||||
{nodeIds.map((nodeId) => {
|
||||
return (
|
||||
<NodeComponent
|
||||
key={node.id}
|
||||
id={node.id}
|
||||
className={node.className}
|
||||
style={node.style}
|
||||
width={node.width ?? undefined}
|
||||
height={node.height ?? undefined}
|
||||
type={nodeType}
|
||||
data={node.data}
|
||||
sourcePosition={node.sourcePosition || Position.Bottom}
|
||||
targetPosition={node.targetPosition || Position.Top}
|
||||
hidden={node.hidden}
|
||||
xPos={posX}
|
||||
yPos={posY}
|
||||
xPosOrigin={posOrigin.x}
|
||||
yPosOrigin={posOrigin.y}
|
||||
positionAbsolute={clampedPosition || { x: 0, y: 0 }}
|
||||
// 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
|
||||
key={nodeId}
|
||||
id={nodeId}
|
||||
nodeTypes={props.nodeTypes}
|
||||
nodeExtent={props.nodeExtent}
|
||||
nodeOrigin={props.nodeOrigin}
|
||||
onClick={props.onNodeClick}
|
||||
onMouseEnter={props.onNodeMouseEnter}
|
||||
onMouseMove={props.onNodeMouseMove}
|
||||
onMouseLeave={props.onNodeMouseLeave}
|
||||
onContextMenu={props.onNodeContextMenu}
|
||||
onDoubleClick={props.onNodeDoubleClick}
|
||||
selected={!!node.selected}
|
||||
isDraggable={isDraggable}
|
||||
isSelectable={isSelectable}
|
||||
isConnectable={isConnectable}
|
||||
isFocusable={isFocusable}
|
||||
resizeObserver={resizeObserver}
|
||||
dragHandle={node.dragHandle}
|
||||
zIndex={node[internalsSymbol]?.z ?? 0}
|
||||
isParent={!!node[internalsSymbol]?.isParent}
|
||||
noDragClassName={props.noDragClassName}
|
||||
noPanClassName={props.noPanClassName}
|
||||
initialized={initialized}
|
||||
rfId={props.rfId}
|
||||
disableKeyboardA11y={props.disableKeyboardA11y}
|
||||
ariaLabel={node.ariaLabel}
|
||||
resizeObserver={resizeObserver}
|
||||
nodesDraggable={nodesDraggable}
|
||||
nodesConnectable={nodesConnectable}
|
||||
nodesFocusable={nodesFocusable}
|
||||
elementsSelectable={elementsSelectable}
|
||||
onError={onError}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
import { ReactFlowState } from '../../types';
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
|
||||
const selector = (s: ReactFlowState) => s.updateNodeDimensions;
|
||||
|
||||
export default function useResizeObserver() {
|
||||
const updateNodeDimensions = useStore(selector);
|
||||
const resizeObserverRef = useRef<ResizeObserver>();
|
||||
|
||||
const resizeObserver = useMemo(() => {
|
||||
if (typeof ResizeObserver === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
|
||||
const updates = new Map();
|
||||
|
||||
entries.forEach((entry: ResizeObserverEntry) => {
|
||||
const id = entry.target.getAttribute('data-id') as string;
|
||||
updates.set(id, {
|
||||
id,
|
||||
nodeElement: entry.target as HTMLDivElement,
|
||||
forceUpdate: true,
|
||||
});
|
||||
});
|
||||
|
||||
updateNodeDimensions(updates);
|
||||
});
|
||||
|
||||
resizeObserverRef.current = observer;
|
||||
|
||||
return observer;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
resizeObserverRef?.current?.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return resizeObserver;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { ComponentType } from 'react';
|
||||
import type { NodeProps } from '@xyflow/system';
|
||||
|
||||
import DefaultNode from '../../components/Nodes/DefaultNode';
|
||||
import InputNode from '../../components/Nodes/InputNode';
|
||||
import OutputNode from '../../components/Nodes/OutputNode';
|
||||
import GroupNode from '../../components/Nodes/GroupNode';
|
||||
import wrapNode from '../../components/Nodes/wrapNode';
|
||||
import type { NodeTypes, NodeTypesWrapped } from '../../types';
|
||||
|
||||
export type CreateNodeTypes = (nodeTypes: NodeTypes) => NodeTypesWrapped;
|
||||
|
||||
export function createNodeTypes(nodeTypes: NodeTypes): NodeTypesWrapped {
|
||||
const standardTypes: NodeTypesWrapped = {
|
||||
input: wrapNode((nodeTypes.input || InputNode) as ComponentType<NodeProps>),
|
||||
default: wrapNode((nodeTypes.default || DefaultNode) as ComponentType<NodeProps>),
|
||||
output: wrapNode((nodeTypes.output || OutputNode) as ComponentType<NodeProps>),
|
||||
group: wrapNode((nodeTypes.group || GroupNode) as ComponentType<NodeProps>),
|
||||
};
|
||||
|
||||
const wrappedTypes = {} as NodeTypesWrapped;
|
||||
const specialTypes: NodeTypesWrapped = Object.keys(nodeTypes)
|
||||
.filter((k) => !['input', 'default', 'output', 'group'].includes(k))
|
||||
.reduce((res, key) => {
|
||||
res[key] = wrapNode((nodeTypes[key] || DefaultNode) as ComponentType<NodeProps>);
|
||||
|
||||
return res;
|
||||
}, wrappedTypes);
|
||||
|
||||
return {
|
||||
...standardTypes,
|
||||
...specialTypes,
|
||||
};
|
||||
}
|
||||
@@ -12,40 +12,15 @@ import {
|
||||
} from '@xyflow/system';
|
||||
|
||||
import Attribution from '../../components/Attribution';
|
||||
import {
|
||||
BezierEdgeInternal,
|
||||
SmoothStepEdgeInternal,
|
||||
StepEdgeInternal,
|
||||
StraightEdgeInternal,
|
||||
SimpleBezierEdgeInternal,
|
||||
} from '../../components/Edges';
|
||||
import DefaultNode from '../../components/Nodes/DefaultNode';
|
||||
import InputNode from '../../components/Nodes/InputNode';
|
||||
import OutputNode from '../../components/Nodes/OutputNode';
|
||||
import GroupNode from '../../components/Nodes/GroupNode';
|
||||
|
||||
import SelectionListener from '../../components/SelectionListener';
|
||||
import StoreUpdater from '../../components/StoreUpdater';
|
||||
import A11yDescriptions from '../../components/A11yDescriptions';
|
||||
import GraphView from '../GraphView';
|
||||
import Wrapper from './Wrapper';
|
||||
import type { EdgeTypes, NodeTypes, ReactFlowProps, ReactFlowRefType } from '../../types';
|
||||
import type { ReactFlowProps, ReactFlowRefType } from '../../types';
|
||||
import useColorModeClass from '../../hooks/useColorModeClass';
|
||||
|
||||
const defaultNodeTypes: NodeTypes = {
|
||||
input: InputNode,
|
||||
default: DefaultNode,
|
||||
output: OutputNode,
|
||||
group: GroupNode,
|
||||
};
|
||||
|
||||
const defaultEdgeTypes: EdgeTypes = {
|
||||
default: BezierEdgeInternal,
|
||||
straight: StraightEdgeInternal,
|
||||
step: StepEdgeInternal,
|
||||
smoothstep: SmoothStepEdgeInternal,
|
||||
simplebezier: SimpleBezierEdgeInternal,
|
||||
};
|
||||
|
||||
const initNodeOrigin: NodeOrigin = [0, 0];
|
||||
const initSnapGrid: [number, number] = [15, 15];
|
||||
const initDefaultViewport: Viewport = { x: 0, y: 0, zoom: 1 };
|
||||
@@ -66,8 +41,8 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
||||
defaultNodes,
|
||||
defaultEdges,
|
||||
className,
|
||||
nodeTypes = defaultNodeTypes,
|
||||
edgeTypes = defaultEdgeTypes,
|
||||
nodeTypes,
|
||||
edgeTypes,
|
||||
onNodeClick,
|
||||
onEdgeClick,
|
||||
onInit,
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
import { useCallback } from 'react';
|
||||
import { getNodesInside } from '@xyflow/system';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { useStore } from '../hooks/useStore';
|
||||
import type { Node, ReactFlowState } from '../types';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
function useVisibleNodes(onlyRenderVisible: boolean) {
|
||||
const nodes = useStore(
|
||||
useCallback(
|
||||
(s: ReactFlowState) =>
|
||||
onlyRenderVisible
|
||||
? getNodesInside<Node>(s.nodes, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true)
|
||||
: s.nodes,
|
||||
[onlyRenderVisible]
|
||||
)
|
||||
);
|
||||
const selector = (onlyRenderVisible: boolean) => (s: ReactFlowState) => {
|
||||
return onlyRenderVisible
|
||||
? getNodesInside<Node>(s.nodes, { x: 0, y: 0, width: s.width, height: s.height }, s.transform, true).map(
|
||||
(node) => node.id
|
||||
)
|
||||
: Array.from(s.nodeLookup.keys());
|
||||
};
|
||||
|
||||
return nodes;
|
||||
function useVisibleNodeIds(onlyRenderVisible: boolean) {
|
||||
const nodeIds = useStore(useCallback(selector(onlyRenderVisible), [onlyRenderVisible]), shallow);
|
||||
|
||||
return nodeIds;
|
||||
}
|
||||
|
||||
export default useVisibleNodes;
|
||||
export default useVisibleNodeIds;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createWithEqualityFn } from 'zustand/traditional';
|
||||
import {
|
||||
clampPosition,
|
||||
fitView as fitViewSystem,
|
||||
updateNodes,
|
||||
adoptUserProvidedNodes,
|
||||
updateAbsolutePositions,
|
||||
panBy as panBySystem,
|
||||
Dimensions,
|
||||
@@ -43,11 +43,16 @@ const createRFStore = ({
|
||||
...getInitialState({ nodes, edges, width, height, fitView }),
|
||||
setNodes: (nodes: Node[]) => {
|
||||
const { nodeLookup, nodeOrigin, elevateNodesOnSelect } = get();
|
||||
// Whenver new nodes are set, we need to calculate the absolute positions of the nodes
|
||||
// and update the nodeLookup.
|
||||
const nextNodes = updateNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect });
|
||||
// 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.
|
||||
// TODO: consider updating the types to reflect the distinction between user-provided nodes and internal nodes.
|
||||
const nodesWithInternalData = adoptUserProvidedNodes(nodes, nodeLookup, { nodeOrigin, elevateNodesOnSelect });
|
||||
|
||||
set({ nodes: nextNodes });
|
||||
set({ nodes: nodesWithInternalData });
|
||||
},
|
||||
setEdges: (edges: Edge[]) => {
|
||||
const { defaultEdgeOptions = {}, connectionLookup } = get();
|
||||
@@ -74,7 +79,8 @@ const createRFStore = ({
|
||||
};
|
||||
|
||||
if (hasDefaultNodes) {
|
||||
nextState.nodes = updateNodes(nodes, new Map(), {
|
||||
const { nodeLookup } = get();
|
||||
nextState.nodes = adoptUserProvidedNodes(nodes, nodeLookup, {
|
||||
nodeOrigin: get().nodeOrigin,
|
||||
elevateNodesOnSelect: get().elevateNodesOnSelect,
|
||||
});
|
||||
@@ -168,7 +174,7 @@ const createRFStore = ({
|
||||
if (changes?.length) {
|
||||
if (hasDefaultNodes) {
|
||||
const updatedNodes = applyNodeChanges(changes, nodes);
|
||||
const nextNodes = updateNodes(updatedNodes, nodeLookup, {
|
||||
const nextNodes = adoptUserProvidedNodes(updatedNodes, nodeLookup, {
|
||||
nodeOrigin,
|
||||
elevateNodesOnSelect,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
infiniteExtent,
|
||||
ConnectionMode,
|
||||
updateNodes,
|
||||
adoptUserProvidedNodes,
|
||||
getNodesBounds,
|
||||
getViewportForBounds,
|
||||
Transform,
|
||||
@@ -25,7 +25,10 @@ const getInitialState = ({
|
||||
} = {}): ReactFlowStore => {
|
||||
const nodeLookup = new Map();
|
||||
const connectionLookup = updateConnectionLookup(new Map(), edges);
|
||||
const nextNodes = updateNodes(nodes, nodeLookup, { nodeOrigin: [0, 0], elevateNodesOnSelect: false });
|
||||
const nextNodes = adoptUserProvidedNodes(nodes, nodeLookup, {
|
||||
nodeOrigin: [0, 0],
|
||||
elevateNodesOnSelect: false,
|
||||
});
|
||||
|
||||
let transform: Transform = [0, 0, 1];
|
||||
|
||||
|
||||
@@ -15,9 +15,10 @@ import type {
|
||||
EdgePosition,
|
||||
Optional,
|
||||
StepPathOptions,
|
||||
OnError,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import { Node } from '.';
|
||||
import { EdgeTypes, Node } from '.';
|
||||
|
||||
export type EdgeLabelOptions = {
|
||||
label?: string | ReactNode;
|
||||
@@ -57,7 +58,7 @@ export type Edge<T = any> = DefaultEdge<T> | SmoothStepEdgeType<T> | BezierEdgeT
|
||||
|
||||
export type EdgeMouseHandler = (event: ReactMouseEvent, edge: Edge) => void;
|
||||
|
||||
export type WrapEdgeProps<T = any> = Omit<Edge<T>, 'sourceHandle' | 'targetHandle'> & {
|
||||
export type EdgeWrapperProps<T = any> = Omit<Edge<T>, 'sourceHandle' | 'targetHandle'> & {
|
||||
onClick?: EdgeMouseHandler;
|
||||
onEdgeDoubleClick?: EdgeMouseHandler;
|
||||
sourceHandleId?: string | null;
|
||||
@@ -75,6 +76,8 @@ export type WrapEdgeProps<T = any> = Omit<Edge<T>, 'sourceHandle' | 'targetHandl
|
||||
isUpdatable: EdgeUpdatable;
|
||||
isSelectable: boolean;
|
||||
pathOptions?: BezierPathOptions | SmoothStepPathOptions;
|
||||
edgeTypes?: EdgeTypes;
|
||||
onError?: OnError;
|
||||
};
|
||||
|
||||
export type DefaultEdgeOptions = DefaultEdgeOptionsBase<Edge>;
|
||||
@@ -90,7 +93,7 @@ export type EdgeProps<T = any> = Pick<
|
||||
Edge<T>,
|
||||
'id' | 'animated' | 'data' | 'style' | 'selected' | 'source' | 'target'
|
||||
> &
|
||||
Pick<WrapEdgeProps, 'sourceHandleId' | 'targetHandleId' | 'interactionWidth'> &
|
||||
Pick<EdgeWrapperProps, 'sourceHandleId' | 'targetHandleId' | 'interactionWidth'> &
|
||||
EdgePosition &
|
||||
EdgeLabelOptions & {
|
||||
markerStart?: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type { ComponentType, MemoExoticComponent } from 'react';
|
||||
import type { ComponentType } from 'react';
|
||||
import {
|
||||
FitViewParamsBase,
|
||||
FitViewOptionsBase,
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
XYPosition,
|
||||
} from '@xyflow/system';
|
||||
|
||||
import type { NodeChange, EdgeChange, Node, WrapNodeProps, Edge, EdgeProps, WrapEdgeProps, ReactFlowInstance } from '.';
|
||||
import type { NodeChange, EdgeChange, Node, Edge, EdgeProps, ReactFlowInstance } from '.';
|
||||
|
||||
export type OnNodesChange = (changes: NodeChange[]) => void;
|
||||
export type OnEdgesChange = (changes: EdgeChange[]) => void;
|
||||
@@ -24,9 +24,7 @@ export type OnEdgesDelete = (edges: Edge[]) => void;
|
||||
export type OnDelete = (params: { nodes: Node[]; edges: Edge[] }) => void;
|
||||
|
||||
export type NodeTypes = { [key: string]: ComponentType<NodeProps> };
|
||||
export type NodeTypesWrapped = { [key: string]: MemoExoticComponent<ComponentType<WrapNodeProps>> };
|
||||
export type EdgeTypes = { [key: string]: ComponentType<EdgeProps> };
|
||||
export type EdgeTypesWrapped = { [key: string]: MemoExoticComponent<ComponentType<WrapEdgeProps>> };
|
||||
|
||||
export type UnselectNodesAndEdgesParams = {
|
||||
nodes?: Node[];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
||||
import type { NodeBase, XYPosition } from '@xyflow/system';
|
||||
import type { CoordinateExtent, NodeBase, NodeOrigin, OnError } from '@xyflow/system';
|
||||
import { NodeTypes } from './general';
|
||||
|
||||
export type Node<NodeData = any, NodeType extends string | undefined = string | undefined> = NodeBase<
|
||||
NodeData,
|
||||
@@ -14,33 +15,25 @@ export type NodeMouseHandler = (event: ReactMouseEvent, node: Node) => void;
|
||||
export type NodeDragHandler = (event: ReactMouseEvent, node: Node, nodes: Node[]) => void;
|
||||
export type SelectionDragHandler = (event: ReactMouseEvent, nodes: Node[]) => void;
|
||||
|
||||
export type WrapNodeProps<NodeData = any> = Pick<
|
||||
Node<NodeData>,
|
||||
'id' | 'data' | 'style' | 'className' | 'dragHandle' | 'sourcePosition' | 'targetPosition' | 'hidden' | 'ariaLabel'
|
||||
> &
|
||||
Required<Pick<Node<NodeData>, 'selected' | 'type' | 'zIndex'>> & {
|
||||
isConnectable: boolean;
|
||||
xPos: number;
|
||||
yPos: number;
|
||||
xPosOrigin: number;
|
||||
yPosOrigin: number;
|
||||
positionAbsolute: XYPosition;
|
||||
initialized: boolean;
|
||||
isSelectable: boolean;
|
||||
isDraggable: boolean;
|
||||
isFocusable: boolean;
|
||||
onClick?: NodeMouseHandler;
|
||||
onDoubleClick?: NodeMouseHandler;
|
||||
onMouseEnter?: NodeMouseHandler;
|
||||
onMouseMove?: NodeMouseHandler;
|
||||
onMouseLeave?: NodeMouseHandler;
|
||||
onContextMenu?: NodeMouseHandler;
|
||||
resizeObserver: ResizeObserver | null;
|
||||
isParent: boolean;
|
||||
noDragClassName: string;
|
||||
noPanClassName: string;
|
||||
rfId: string;
|
||||
disableKeyboardA11y: boolean;
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
export type NodeWrapperProps = {
|
||||
id: string;
|
||||
nodesConnectable: boolean;
|
||||
elementsSelectable: boolean;
|
||||
nodesDraggable: boolean;
|
||||
nodesFocusable: boolean;
|
||||
onClick?: NodeMouseHandler;
|
||||
onDoubleClick?: NodeMouseHandler;
|
||||
onMouseEnter?: NodeMouseHandler;
|
||||
onMouseMove?: NodeMouseHandler;
|
||||
onMouseLeave?: NodeMouseHandler;
|
||||
onContextMenu?: NodeMouseHandler;
|
||||
resizeObserver: ResizeObserver | null;
|
||||
noDragClassName: string;
|
||||
noPanClassName: string;
|
||||
rfId: string;
|
||||
disableKeyboardA11y: boolean;
|
||||
nodeTypes?: NodeTypes;
|
||||
nodeExtent?: CoordinateExtent;
|
||||
nodeOrigin: NodeOrigin;
|
||||
onError?: OnError;
|
||||
};
|
||||
|
||||
@@ -55,14 +55,17 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
||||
}
|
||||
|
||||
let remainingChanges = changes;
|
||||
const initElements: any[] = changes.filter((c) => c.type === 'add').map((c) => c.item);
|
||||
const updatedElements: any[] = [];
|
||||
|
||||
return elements.reduce((res: any[], item: any) => {
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const nextChanges: any[] = [];
|
||||
const _remainingChanges: any[] = [];
|
||||
const item = elements[i];
|
||||
|
||||
remainingChanges.forEach((c) => {
|
||||
if (c.id === item.id) {
|
||||
if (c.type === 'add') {
|
||||
updatedElements.push(c.item);
|
||||
} else if (c.id === item.id) {
|
||||
nextChanges.push(c);
|
||||
} else {
|
||||
_remainingChanges.push(c);
|
||||
@@ -71,8 +74,8 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
||||
remainingChanges = _remainingChanges;
|
||||
|
||||
if (nextChanges.length === 0) {
|
||||
res.push(item);
|
||||
return res;
|
||||
updatedElements.push(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
const updateItem = { ...item };
|
||||
@@ -101,7 +104,7 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
||||
}
|
||||
|
||||
if (updateItem.expandParent) {
|
||||
handleParentExpand(res, updateItem);
|
||||
handleParentExpand(updatedElements, updateItem);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -123,20 +126,20 @@ function applyChanges(changes: any[], elements: any[]): any[] {
|
||||
}
|
||||
|
||||
if (updateItem.expandParent) {
|
||||
handleParentExpand(res, updateItem);
|
||||
handleParentExpand(updatedElements, updateItem);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'remove': {
|
||||
return res;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
updatedElements.push(updateItem);
|
||||
}
|
||||
}
|
||||
|
||||
res.push(updateItem);
|
||||
return res;
|
||||
}, initElements);
|
||||
return updatedElements;
|
||||
}
|
||||
|
||||
export function applyNodeChanges<NodeData = any>(changes: NodeChange[], nodes: Node<NodeData>[]): Node<NodeData>[] {
|
||||
|
||||
@@ -191,7 +191,8 @@
|
||||
{dragging}
|
||||
{dragHandle}
|
||||
isConnectable={connectable}
|
||||
positionAbsolute={{ x: positionX, y: positionY }}
|
||||
positionAbsoluteX={positionX}
|
||||
positionAbsoluteY={positionY}
|
||||
{width}
|
||||
{height}
|
||||
/>
|
||||
|
||||
@@ -18,10 +18,8 @@
|
||||
export let zIndex: $$Props['zIndex'] = undefined;
|
||||
export let dragging: $$Props['dragging'] = false;
|
||||
export let dragHandle: $$Props['dragHandle'] = undefined;
|
||||
export let positionAbsolute: $$Props['positionAbsolute'] = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
export let positionAbsoluteX: $$Props['positionAbsoluteX'] = 0;
|
||||
export let positionAbsoluteY: $$Props['positionAbsoluteY'] = 0;
|
||||
export let isConnectable: $$Props['isConnectable'] = undefined;
|
||||
|
||||
// @todo: there must be a better way to do this
|
||||
@@ -33,7 +31,8 @@
|
||||
zIndex;
|
||||
dragging;
|
||||
dragHandle;
|
||||
positionAbsolute;
|
||||
positionAbsoluteX;
|
||||
positionAbsoluteY;
|
||||
isConnectable;
|
||||
</script>
|
||||
|
||||
|
||||
@@ -15,10 +15,8 @@
|
||||
export let zIndex: $$Props['zIndex'] = undefined;
|
||||
export let dragging: $$Props['dragging'] = false;
|
||||
export let dragHandle: $$Props['dragHandle'] = undefined;
|
||||
export let positionAbsolute: $$Props['positionAbsolute'] = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
export let positionAbsoluteX: $$Props['positionAbsoluteX'] = 0;
|
||||
export let positionAbsoluteY: $$Props['positionAbsoluteY'] = 0;
|
||||
export let isConnectable: $$Props['isConnectable'] = undefined;
|
||||
|
||||
// @todo: there must be a better way to do this
|
||||
@@ -33,6 +31,7 @@
|
||||
zIndex;
|
||||
dragging;
|
||||
dragHandle;
|
||||
positionAbsolute;
|
||||
positionAbsoluteX;
|
||||
positionAbsoluteY;
|
||||
isConnectable;
|
||||
</script>
|
||||
|
||||
@@ -18,10 +18,8 @@
|
||||
export let zIndex: $$Props['zIndex'] = undefined;
|
||||
export let dragging: $$Props['dragging'] = false;
|
||||
export let dragHandle: $$Props['dragHandle'] = undefined;
|
||||
export let positionAbsolute: $$Props['positionAbsolute'] = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
export let positionAbsoluteX: $$Props['positionAbsoluteX'] = 0;
|
||||
export let positionAbsoluteY: $$Props['positionAbsoluteY'] = 0;
|
||||
export let isConnectable: $$Props['isConnectable'] = undefined;
|
||||
|
||||
// @todo: there must be a better way to do this
|
||||
@@ -34,7 +32,8 @@
|
||||
zIndex;
|
||||
dragging;
|
||||
dragHandle;
|
||||
positionAbsolute;
|
||||
positionAbsoluteX;
|
||||
positionAbsoluteY;
|
||||
isConnectable;
|
||||
</script>
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
export let zIndex: $$Props['zIndex'] = undefined;
|
||||
export let dragging: $$Props['dragging'] = false;
|
||||
export let dragHandle: $$Props['dragHandle'] = undefined;
|
||||
export let positionAbsolute: $$Props['positionAbsolute'] = { x: 0, y: 0 };
|
||||
export let positionAbsoluteX: $$Props['positionAbsoluteX'] = 0;
|
||||
export let positionAbsoluteY: $$Props['positionAbsoluteY'] = 0;
|
||||
export let isConnectable: $$Props['isConnectable'] = undefined;
|
||||
|
||||
// @todo: there must be a better way to do this
|
||||
@@ -31,7 +32,8 @@
|
||||
zIndex;
|
||||
dragging;
|
||||
dragHandle;
|
||||
positionAbsolute;
|
||||
positionAbsoluteX;
|
||||
positionAbsoluteY;
|
||||
isConnectable;
|
||||
</script>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
type OnError,
|
||||
devWarn,
|
||||
type Viewport,
|
||||
updateNodes,
|
||||
adoptUserProvidedNodes,
|
||||
getNodesBounds,
|
||||
getViewportForBounds,
|
||||
updateConnectionLookup,
|
||||
@@ -78,7 +78,7 @@ export const getInitialStore = ({
|
||||
fitView?: boolean;
|
||||
}) => {
|
||||
const nodeLookup = new Map();
|
||||
const nextNodes = updateNodes(nodes, nodeLookup, {
|
||||
const nextNodes = adoptUserProvidedNodes(nodes, nodeLookup, {
|
||||
nodeOrigin: [0, 0],
|
||||
elevateNodesOnSelect: false
|
||||
});
|
||||
|
||||
@@ -7,11 +7,11 @@ import {
|
||||
get
|
||||
} from 'svelte/store';
|
||||
import {
|
||||
updateNodes,
|
||||
adoptUserProvidedNodes,
|
||||
updateConnectionLookup,
|
||||
type Viewport,
|
||||
type PanZoomInstance,
|
||||
type ConnectionLookup,
|
||||
updateConnectionLookup
|
||||
type ConnectionLookup
|
||||
} from '@xyflow/system';
|
||||
|
||||
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types';
|
||||
@@ -139,7 +139,7 @@ export const createNodesStore = (
|
||||
let elevateNodesOnSelect = true;
|
||||
|
||||
const _set = (nds: Node[]): Node[] => {
|
||||
const nextNodes = updateNodes(nds, nodeLookup, {
|
||||
const nextNodes = adoptUserProvidedNodes(nds, nodeLookup, {
|
||||
elevateNodesOnSelect,
|
||||
defaults
|
||||
});
|
||||
|
||||
@@ -40,6 +40,10 @@ export type NodeBase<T = any, U extends string | undefined = string | undefined>
|
||||
z?: number;
|
||||
handleBounds?: NodeHandleBounds;
|
||||
isParent?: boolean;
|
||||
/** Holds a reference to the original node object provided by the user
|
||||
* (which may lack some fields, like `computed` or `[internalSymbol]`. Used
|
||||
* as an optimization to avoid certain operations. */
|
||||
userProvidedNode: NodeBase<T, U>;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -52,7 +56,8 @@ export type NodeProps<T = any> = {
|
||||
selected: NodeBase['selected'];
|
||||
isConnectable: NodeBase['connectable'];
|
||||
zIndex: NodeBase['zIndex'];
|
||||
positionAbsolute: XYPosition;
|
||||
positionAbsoluteX: number;
|
||||
positionAbsoluteY: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
dragging: boolean;
|
||||
|
||||
@@ -64,7 +64,7 @@ type UpdateNodesOptions<NodeType extends NodeBase> = {
|
||||
defaults?: Partial<NodeType>;
|
||||
};
|
||||
|
||||
export function updateNodes<NodeType extends NodeBase>(
|
||||
export function adoptUserProvidedNodes<NodeType extends NodeBase>(
|
||||
nodes: NodeType[],
|
||||
nodeLookup: Map<string, NodeType>,
|
||||
options: UpdateNodesOptions<NodeType> = {
|
||||
@@ -80,6 +80,11 @@ export function updateNodes<NodeType extends NodeBase>(
|
||||
|
||||
const nextNodes = nodes.map((n) => {
|
||||
const currentStoreNode = tmpLookup.get(n.id);
|
||||
if (n === currentStoreNode?.[internalsSymbol]?.userProvidedNode) {
|
||||
nodeLookup.set(n.id, currentStoreNode);
|
||||
return currentStoreNode;
|
||||
}
|
||||
|
||||
const node: NodeType = {
|
||||
...options.defaults,
|
||||
...n,
|
||||
@@ -101,6 +106,7 @@ export function updateNodes<NodeType extends NodeBase>(
|
||||
value: {
|
||||
handleBounds: currInternals?.handleBounds,
|
||||
z,
|
||||
userProvidedNode: n,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -141,14 +147,14 @@ function calculateXYZPosition<NodeType extends NodeBase>(
|
||||
);
|
||||
}
|
||||
|
||||
export function updateNodeDimensions(
|
||||
export function updateNodeDimensions<NodeType extends NodeBase>(
|
||||
updates: Map<string, NodeDimensionUpdate>,
|
||||
nodes: NodeBase[],
|
||||
nodeLookup: Map<string, NodeBase>,
|
||||
nodes: NodeType[],
|
||||
nodeLookup: Map<string, NodeType>,
|
||||
domNode: HTMLElement | null,
|
||||
nodeOrigin?: NodeOrigin,
|
||||
onUpdate?: (id: string, dimensions: Dimensions) => void
|
||||
): NodeBase[] | null {
|
||||
): NodeType[] | null {
|
||||
const viewportNode = domNode?.querySelector('.xyflow__viewport');
|
||||
|
||||
if (!viewportNode) {
|
||||
|
||||
Generated
+27
-121
@@ -61,10 +61,10 @@ importers:
|
||||
dependencies:
|
||||
'@astrojs/react':
|
||||
specifier: ^3.0.2
|
||||
version: registry.npmjs.org/@astrojs/react@3.0.2(@types/react-dom@18.2.8)(@types/react@18.2.24)(react-dom@18.2.0)(react@18.2.0)(vite@4.5.1)
|
||||
version: registry.npmjs.org/@astrojs/react@3.0.2(@types/react-dom@18.2.8)(@types/react@18.2.24)(react-dom@18.2.0)(react@18.2.0)(vite@4.5.0)
|
||||
'@astrojs/svelte':
|
||||
specifier: ^4.0.2
|
||||
version: registry.npmjs.org/@astrojs/svelte@4.0.2(astro@3.2.2)(svelte@4.2.1)(typescript@5.3.3)(vite@4.5.1)
|
||||
version: registry.npmjs.org/@astrojs/svelte@4.0.2(astro@3.2.2)(svelte@4.2.1)(typescript@5.2.2)(vite@4.5.0)
|
||||
'@types/react':
|
||||
specifier: ^18.2.24
|
||||
version: registry.npmjs.org/@types/react@18.2.24
|
||||
@@ -305,7 +305,7 @@ importers:
|
||||
version: registry.npmjs.org/@sveltejs/adapter-auto@2.1.0(@sveltejs/kit@1.22.6)
|
||||
'@sveltejs/kit':
|
||||
specifier: ^1.22.6
|
||||
version: registry.npmjs.org/@sveltejs/kit@1.22.6(svelte@4.2.1)(vite@4.5.1)
|
||||
version: registry.npmjs.org/@sveltejs/kit@1.22.6(svelte@4.2.1)(vite@4.5.0)
|
||||
'@sveltejs/package':
|
||||
specifier: ^2.2.1
|
||||
version: registry.npmjs.org/@sveltejs/package@2.2.1(svelte@4.2.1)(typescript@5.1.3)
|
||||
@@ -417,7 +417,7 @@ importers:
|
||||
dependencies:
|
||||
'@playwright/experimental-ct-react':
|
||||
specifier: ^1.39.0
|
||||
version: registry.npmjs.org/@playwright/experimental-ct-react@1.39.0(@types/node@18.7.16)(vite@4.5.1)
|
||||
version: registry.npmjs.org/@playwright/experimental-ct-react@1.39.0(@types/node@18.7.16)(vite@4.5.0)
|
||||
'@types/react':
|
||||
specifier: ^18.2.31
|
||||
version: registry.npmjs.org/@types/react@18.2.31
|
||||
@@ -551,7 +551,7 @@ packages:
|
||||
prismjs: registry.npmjs.org/prismjs@1.29.0
|
||||
dev: false
|
||||
|
||||
registry.npmjs.org/@astrojs/react@3.0.2(@types/react-dom@18.2.8)(@types/react@18.2.24)(react-dom@18.2.0)(react@18.2.0)(vite@4.5.1):
|
||||
registry.npmjs.org/@astrojs/react@3.0.2(@types/react-dom@18.2.8)(@types/react@18.2.24)(react-dom@18.2.0)(react@18.2.0)(vite@4.5.0):
|
||||
resolution: {integrity: sha512-aooNIuQxTg+IWGMZPuIEwBeBi4/TCPCMsr3714zuLjAjukVd5ZrX/bCNxJqDWU4HNwUm4XFU1OhcEvYOHa5uMQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@astrojs/react/-/react-3.0.2.tgz}
|
||||
id: registry.npmjs.org/@astrojs/react/3.0.2
|
||||
name: '@astrojs/react'
|
||||
@@ -565,7 +565,7 @@ packages:
|
||||
dependencies:
|
||||
'@types/react': registry.npmjs.org/@types/react@18.2.24
|
||||
'@types/react-dom': registry.npmjs.org/@types/react-dom@18.2.8
|
||||
'@vitejs/plugin-react': registry.npmjs.org/@vitejs/plugin-react@4.1.1(vite@4.5.1)
|
||||
'@vitejs/plugin-react': registry.npmjs.org/@vitejs/plugin-react@4.1.1(vite@4.5.0)
|
||||
react: registry.npmjs.org/react@18.2.0
|
||||
react-dom: registry.npmjs.org/react-dom@18.2.0(react@18.2.0)
|
||||
ultrahtml: registry.npmjs.org/ultrahtml@1.5.2
|
||||
@@ -574,7 +574,7 @@ packages:
|
||||
- vite
|
||||
dev: false
|
||||
|
||||
registry.npmjs.org/@astrojs/svelte@4.0.2(astro@3.2.2)(svelte@4.2.1)(typescript@5.3.3)(vite@4.5.1):
|
||||
registry.npmjs.org/@astrojs/svelte@4.0.2(astro@3.2.2)(svelte@4.2.1)(typescript@5.2.2)(vite@4.5.0):
|
||||
resolution: {integrity: sha512-XB9Sexq+iW5aUpctDk6zuKHbWIAuPlAnZKbfgaS9VOaOUtx1t12crP9YoKEVcTifXYgoRuWWfeEAm2I8pYlLIQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@astrojs/svelte/-/svelte-4.0.2.tgz}
|
||||
id: registry.npmjs.org/@astrojs/svelte/4.0.2
|
||||
name: '@astrojs/svelte'
|
||||
@@ -584,10 +584,10 @@ packages:
|
||||
astro: ^3.0.11
|
||||
svelte: ^3.55.0 || ^4.0.0
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte': registry.npmjs.org/@sveltejs/vite-plugin-svelte@2.4.6(svelte@4.2.1)(vite@4.5.1)
|
||||
'@sveltejs/vite-plugin-svelte': registry.npmjs.org/@sveltejs/vite-plugin-svelte@2.4.6(svelte@4.2.1)(vite@4.5.0)
|
||||
astro: registry.npmjs.org/astro@3.2.2
|
||||
svelte: registry.npmjs.org/svelte@4.2.1
|
||||
svelte2tsx: registry.npmjs.org/svelte2tsx@0.6.23(svelte@4.2.1)(typescript@5.3.3)
|
||||
svelte2tsx: registry.npmjs.org/svelte2tsx@0.6.23(svelte@4.2.1)(typescript@5.2.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
@@ -1756,7 +1756,7 @@ packages:
|
||||
debug: registry.npmjs.org/debug@4.3.4(supports-color@8.1.1)
|
||||
espree: registry.npmjs.org/espree@9.6.1
|
||||
globals: registry.npmjs.org/globals@13.23.0
|
||||
ignore: registry.npmjs.org/ignore@5.2.4
|
||||
ignore: registry.npmjs.org/ignore@5.3.0
|
||||
import-fresh: registry.npmjs.org/import-fresh@3.3.0
|
||||
js-yaml: registry.npmjs.org/js-yaml@4.1.0
|
||||
minimatch: registry.npmjs.org/minimatch@3.1.2
|
||||
@@ -1938,7 +1938,7 @@ packages:
|
||||
- terser
|
||||
dev: false
|
||||
|
||||
registry.npmjs.org/@playwright/experimental-ct-react@1.39.0(@types/node@18.7.16)(vite@4.5.1):
|
||||
registry.npmjs.org/@playwright/experimental-ct-react@1.39.0(@types/node@18.7.16)(vite@4.5.0):
|
||||
resolution: {integrity: sha512-6GaDVVo6x8P9Jdw3yPbVaEP59nesRsETn3kznHAy+2493VsP3IaVATat2G28z0Sivf/K9Tkh5xRYjiawN0ebgw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.39.0.tgz}
|
||||
id: registry.npmjs.org/@playwright/experimental-ct-react/1.39.0
|
||||
name: '@playwright/experimental-ct-react'
|
||||
@@ -1947,7 +1947,7 @@ packages:
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@playwright/experimental-ct-core': registry.npmjs.org/@playwright/experimental-ct-core@1.39.0(@types/node@18.7.16)
|
||||
'@vitejs/plugin-react': registry.npmjs.org/@vitejs/plugin-react@4.1.1(vite@4.5.1)
|
||||
'@vitejs/plugin-react': registry.npmjs.org/@vitejs/plugin-react@4.1.1(vite@4.5.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- less
|
||||
@@ -2134,7 +2134,7 @@ packages:
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': ^1.0.0
|
||||
dependencies:
|
||||
'@sveltejs/kit': registry.npmjs.org/@sveltejs/kit@1.22.6(svelte@4.2.1)(vite@4.5.1)
|
||||
'@sveltejs/kit': registry.npmjs.org/@sveltejs/kit@1.22.6(svelte@4.2.1)(vite@4.5.0)
|
||||
import-meta-resolve: registry.npmjs.org/import-meta-resolve@3.0.0
|
||||
dev: true
|
||||
|
||||
@@ -2150,7 +2150,7 @@ packages:
|
||||
import-meta-resolve: registry.npmjs.org/import-meta-resolve@4.0.0
|
||||
dev: true
|
||||
|
||||
registry.npmjs.org/@sveltejs/kit@1.22.6(svelte@4.2.1)(vite@4.5.1):
|
||||
registry.npmjs.org/@sveltejs/kit@1.22.6(svelte@4.2.1)(vite@4.5.0):
|
||||
resolution: {integrity: sha512-SDKxI/QpsReCwIn5czjT53fKlPBybbmMk67d317gUqfeORroBAFN1Z6s/x0E1JYi+04i7kKllS+Sz9wVfmUkAQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@sveltejs/kit/-/kit-1.22.6.tgz}
|
||||
id: registry.npmjs.org/@sveltejs/kit/1.22.6
|
||||
name: '@sveltejs/kit'
|
||||
@@ -2162,7 +2162,7 @@ packages:
|
||||
svelte: ^3.54.0 || ^4.0.0-next.0
|
||||
vite: ^4.0.0
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte': registry.npmjs.org/@sveltejs/vite-plugin-svelte@2.4.6(svelte@4.2.1)(vite@4.5.1)
|
||||
'@sveltejs/vite-plugin-svelte': registry.npmjs.org/@sveltejs/vite-plugin-svelte@2.4.6(svelte@4.2.1)(vite@4.5.0)
|
||||
'@types/cookie': registry.npmjs.org/@types/cookie@0.5.3
|
||||
cookie: registry.npmjs.org/cookie@0.5.0
|
||||
devalue: registry.npmjs.org/devalue@4.3.2
|
||||
@@ -2175,7 +2175,7 @@ packages:
|
||||
sirv: registry.npmjs.org/sirv@2.0.3
|
||||
svelte: registry.npmjs.org/svelte@4.2.1
|
||||
undici: registry.npmjs.org/undici@5.23.0
|
||||
vite: registry.npmjs.org/vite@4.5.1(@types/node@18.7.16)
|
||||
vite: registry.npmjs.org/vite@4.5.0(@types/node@18.7.16)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
@@ -2231,7 +2231,7 @@ packages:
|
||||
- typescript
|
||||
dev: true
|
||||
|
||||
registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector@1.0.4(@sveltejs/vite-plugin-svelte@2.4.6)(svelte@4.2.1)(vite@4.5.1):
|
||||
registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector@1.0.4(@sveltejs/vite-plugin-svelte@2.4.6)(svelte@4.2.1)(vite@4.5.0):
|
||||
resolution: {integrity: sha512-zjiuZ3yydBtwpF3bj0kQNV0YXe+iKE545QGZVTaylW3eAzFr+pJ/cwK8lZEaRp4JtaJXhD5DyWAV4AxLh6DgaQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.4.tgz}
|
||||
id: registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/1.0.4
|
||||
name: '@sveltejs/vite-plugin-svelte-inspector'
|
||||
@@ -2242,10 +2242,10 @@ packages:
|
||||
svelte: ^3.54.0 || ^4.0.0
|
||||
vite: ^4.0.0
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte': registry.npmjs.org/@sveltejs/vite-plugin-svelte@2.4.6(svelte@4.2.1)(vite@4.5.1)
|
||||
'@sveltejs/vite-plugin-svelte': registry.npmjs.org/@sveltejs/vite-plugin-svelte@2.4.6(svelte@4.2.1)(vite@4.5.0)
|
||||
debug: registry.npmjs.org/debug@4.3.4(supports-color@8.1.1)
|
||||
svelte: registry.npmjs.org/svelte@4.2.1
|
||||
vite: registry.npmjs.org/vite@4.5.1(@types/node@18.7.16)
|
||||
vite: registry.npmjs.org/vite@4.5.0(@types/node@18.7.16)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -2268,7 +2268,7 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
registry.npmjs.org/@sveltejs/vite-plugin-svelte@2.4.6(svelte@4.2.1)(vite@4.5.1):
|
||||
registry.npmjs.org/@sveltejs/vite-plugin-svelte@2.4.6(svelte@4.2.1)(vite@4.5.0):
|
||||
resolution: {integrity: sha512-zO79p0+DZnXPnF0ltIigWDx/ux7Ni+HRaFOw720Qeivc1azFUrJxTl0OryXVibYNx1hCboGia1NRV3x8RNv4cA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.6.tgz}
|
||||
id: registry.npmjs.org/@sveltejs/vite-plugin-svelte/2.4.6
|
||||
name: '@sveltejs/vite-plugin-svelte'
|
||||
@@ -2278,15 +2278,15 @@ packages:
|
||||
svelte: ^3.54.0 || ^4.0.0
|
||||
vite: ^4.0.0
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte-inspector': registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector@1.0.4(@sveltejs/vite-plugin-svelte@2.4.6)(svelte@4.2.1)(vite@4.5.1)
|
||||
'@sveltejs/vite-plugin-svelte-inspector': registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector@1.0.4(@sveltejs/vite-plugin-svelte@2.4.6)(svelte@4.2.1)(vite@4.5.0)
|
||||
debug: registry.npmjs.org/debug@4.3.4(supports-color@8.1.1)
|
||||
deepmerge: registry.npmjs.org/deepmerge@4.3.1
|
||||
kleur: registry.npmjs.org/kleur@4.1.5
|
||||
magic-string: registry.npmjs.org/magic-string@0.30.5
|
||||
svelte: registry.npmjs.org/svelte@4.2.1
|
||||
svelte-hmr: registry.npmjs.org/svelte-hmr@0.15.3(svelte@4.2.1)
|
||||
vite: registry.npmjs.org/vite@4.5.1(@types/node@18.7.16)
|
||||
vitefu: registry.npmjs.org/vitefu@0.2.5(vite@4.5.1)
|
||||
vite: registry.npmjs.org/vite@4.5.0(@types/node@18.7.16)
|
||||
vitefu: registry.npmjs.org/vitefu@0.2.5(vite@4.5.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -2872,6 +2872,7 @@ packages:
|
||||
resolution: {integrity: sha512-EQHhixfu+mkqHMZl1R2Ovuvn47PUw18azMJOTwSZr9/fhzHNGXAJ0ma0dayRVchprpCj0Kc1K1xKoWaATWF1qg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/node/-/node-18.7.16.tgz}
|
||||
name: '@types/node'
|
||||
version: 18.7.16
|
||||
requiresBuild: true
|
||||
|
||||
registry.npmjs.org/@types/normalize-package-data@2.4.3:
|
||||
resolution: {integrity: sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.3.tgz}
|
||||
@@ -2966,12 +2967,6 @@ packages:
|
||||
version: 6.2.5
|
||||
dev: true
|
||||
|
||||
registry.npmjs.org/@types/semver@7.5.4:
|
||||
resolution: {integrity: sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz}
|
||||
name: '@types/semver'
|
||||
version: 7.5.4
|
||||
dev: true
|
||||
|
||||
registry.npmjs.org/@types/semver@7.5.6:
|
||||
resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz}
|
||||
name: '@types/semver'
|
||||
@@ -3381,7 +3376,7 @@ packages:
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': registry.npmjs.org/@eslint-community/eslint-utils@4.4.0(eslint@8.43.0)
|
||||
'@types/json-schema': registry.npmjs.org/@types/json-schema@7.0.15
|
||||
'@types/semver': registry.npmjs.org/@types/semver@7.5.4
|
||||
'@types/semver': registry.npmjs.org/@types/semver@7.5.6
|
||||
'@typescript-eslint/scope-manager': registry.npmjs.org/@typescript-eslint/scope-manager@5.60.0
|
||||
'@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types@5.60.0
|
||||
'@typescript-eslint/typescript-estree': registry.npmjs.org/@typescript-eslint/typescript-estree@5.60.0(typescript@5.1.3)
|
||||
@@ -3504,26 +3499,6 @@ packages:
|
||||
vite: registry.npmjs.org/vite@4.5.0(@types/node@18.7.16)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
registry.npmjs.org/@vitejs/plugin-react@4.1.1(vite@4.5.1):
|
||||
resolution: {integrity: sha512-Jie2HERK+uh27e+ORXXwEP5h0Y2lS9T2PRGbfebiHGlwzDO0dEnd2aNtOR/qjBlPb1YgxwAONeblL1xqLikLag==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.1.1.tgz}
|
||||
id: registry.npmjs.org/@vitejs/plugin-react/4.1.1
|
||||
name: '@vitejs/plugin-react'
|
||||
version: 4.1.1
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
vite: ^4.2.0
|
||||
dependencies:
|
||||
'@babel/core': registry.npmjs.org/@babel/core@7.23.2
|
||||
'@babel/plugin-transform-react-jsx-self': registry.npmjs.org/@babel/plugin-transform-react-jsx-self@7.22.5(@babel/core@7.23.2)
|
||||
'@babel/plugin-transform-react-jsx-source': registry.npmjs.org/@babel/plugin-transform-react-jsx-source@7.22.5(@babel/core@7.23.2)
|
||||
'@types/babel__core': registry.npmjs.org/@types/babel__core@7.20.4
|
||||
react-refresh: registry.npmjs.org/react-refresh@0.14.0
|
||||
vite: registry.npmjs.org/vite@4.5.1(@types/node@18.7.16)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
registry.npmjs.org/acorn-jsx@5.3.2(acorn@8.10.0):
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz}
|
||||
@@ -9520,16 +9495,6 @@ packages:
|
||||
picocolors: registry.npmjs.org/picocolors@1.0.0
|
||||
source-map-js: registry.npmjs.org/source-map-js@1.0.2
|
||||
|
||||
registry.npmjs.org/postcss@8.4.32:
|
||||
resolution: {integrity: sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz}
|
||||
name: postcss
|
||||
version: 8.4.32
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
dependencies:
|
||||
nanoid: registry.npmjs.org/nanoid@3.3.7
|
||||
picocolors: registry.npmjs.org/picocolors@1.0.0
|
||||
source-map-js: registry.npmjs.org/source-map-js@1.0.2
|
||||
|
||||
registry.npmjs.org/prebuild-install@7.1.1:
|
||||
resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz}
|
||||
name: prebuild-install
|
||||
@@ -11249,7 +11214,7 @@ packages:
|
||||
typescript: registry.npmjs.org/typescript@5.1.3
|
||||
dev: true
|
||||
|
||||
registry.npmjs.org/svelte2tsx@0.6.23(svelte@4.2.1)(typescript@5.3.3):
|
||||
registry.npmjs.org/svelte2tsx@0.6.23(svelte@4.2.1)(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-3bwd1PuWUA3oEXy8+85zrLDnmJOsVpShpKVAehGWeYsz/66zMihTpRpUN97VVAKTZbO5tP4wnchHUXYs0zOwdw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.6.23.tgz}
|
||||
id: registry.npmjs.org/svelte2tsx/0.6.23
|
||||
name: svelte2tsx
|
||||
@@ -11261,7 +11226,7 @@ packages:
|
||||
dedent-js: registry.npmjs.org/dedent-js@1.0.1
|
||||
pascal-case: registry.npmjs.org/pascal-case@3.1.2
|
||||
svelte: registry.npmjs.org/svelte@4.2.1
|
||||
typescript: registry.npmjs.org/typescript@5.3.3
|
||||
typescript: registry.npmjs.org/typescript@5.2.2
|
||||
dev: false
|
||||
|
||||
registry.npmjs.org/svelte@4.2.1:
|
||||
@@ -11798,14 +11763,6 @@ packages:
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
registry.npmjs.org/typescript@5.3.3:
|
||||
resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz}
|
||||
name: typescript
|
||||
version: 5.3.3
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
registry.npmjs.org/ultrahtml@1.5.2:
|
||||
resolution: {integrity: sha512-qh4mBffhlkiXwDAOxvSGxhL0QEQsTbnP9BozOK3OYPEGvPvdWzvAUaXNtUSMdNsKDtuyjEbyVUPFZ52SSLhLqw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.5.2.tgz}
|
||||
name: ultrahtml
|
||||
@@ -12141,44 +12098,6 @@ packages:
|
||||
optionalDependencies:
|
||||
fsevents: registry.npmjs.org/fsevents@2.3.3
|
||||
|
||||
registry.npmjs.org/vite@4.5.1(@types/node@18.7.16):
|
||||
resolution: {integrity: sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/vite/-/vite-4.5.1.tgz}
|
||||
id: registry.npmjs.org/vite/4.5.1
|
||||
name: vite
|
||||
version: 4.5.1
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': '>= 14'
|
||||
less: '*'
|
||||
lightningcss: ^1.21.0
|
||||
sass: '*'
|
||||
stylus: '*'
|
||||
sugarss: '*'
|
||||
terser: ^5.4.0
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
lightningcss:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/node': registry.npmjs.org/@types/node@18.7.16
|
||||
esbuild: registry.npmjs.org/esbuild@0.18.20
|
||||
postcss: registry.npmjs.org/postcss@8.4.32
|
||||
rollup: registry.npmjs.org/rollup@3.29.4
|
||||
optionalDependencies:
|
||||
fsevents: registry.npmjs.org/fsevents@2.3.3
|
||||
|
||||
registry.npmjs.org/vitefu@0.2.5(vite@4.5.0):
|
||||
resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz}
|
||||
id: registry.npmjs.org/vitefu/0.2.5
|
||||
@@ -12192,19 +12111,6 @@ packages:
|
||||
dependencies:
|
||||
vite: registry.npmjs.org/vite@4.5.0(@types/node@18.7.16)
|
||||
|
||||
registry.npmjs.org/vitefu@0.2.5(vite@4.5.1):
|
||||
resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz}
|
||||
id: registry.npmjs.org/vitefu/0.2.5
|
||||
name: vitefu
|
||||
version: 0.2.5
|
||||
peerDependencies:
|
||||
vite: ^3.0.0 || ^4.0.0 || ^5.0.0
|
||||
peerDependenciesMeta:
|
||||
vite:
|
||||
optional: true
|
||||
dependencies:
|
||||
vite: registry.npmjs.org/vite@4.5.1(@types/node@18.7.16)
|
||||
|
||||
registry.npmjs.org/vscode-oniguruma@1.7.0:
|
||||
resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz}
|
||||
name: vscode-oniguruma
|
||||
|
||||
Reference in New Issue
Block a user