initial version of middlewares

This commit is contained in:
peterkogo
2025-08-28 10:44:54 +02:00
parent 04055c9625
commit cec7da54df
7 changed files with 172 additions and 5 deletions

View File

@@ -28,7 +28,15 @@ export function BatchProvider<NodeType extends Node = Node, EdgeType extends Edg
const store = useStoreApi<NodeType, EdgeType>();
const nodeQueueHandler = useCallback((queueItems: QueueItem<NodeType>[]) => {
const { nodes = [], setNodes, hasDefaultNodes, onNodesChange, nodeLookup, fitViewQueued } = store.getState();
const {
nodes = [],
setNodes,
hasDefaultNodes,
onNodesChange,
nodeLookup,
fitViewQueued,
nodeChangeMiddleware,
} = store.getState();
/*
* This is essentially an `Array.reduce` in imperative clothing. Processing
@@ -40,11 +48,23 @@ export function BatchProvider<NodeType extends Node = Node, EdgeType extends Edg
next = typeof payload === 'function' ? payload(next) : payload;
}
const changes = getElementsDiffChanges({
let changes = getElementsDiffChanges({
items: next,
lookup: nodeLookup,
}) as NodeChange<NodeType>[];
console.log('this workds');
for (const middleware of nodeChangeMiddleware.values()) {
changes = middleware(changes);
}
// for (const middleware of nodeSyncMiddleware.values()) {
// changes = middleware(changes);
// }
// Do we always want to setNodes(next) or only when there are changes?
if (hasDefaultNodes) {
setNodes(next);
}

View File

@@ -0,0 +1,97 @@
import { NodeChange } from '@xyflow/system';
import { useEffect, useRef } from 'react';
import { useStoreApi } from './useStore';
import { Edge, Node } from '../types';
/**
* We identify each middleware with a symbol that is unique for each hook instance.
*/
function useSymbol() {
const symbol = useRef<symbol>();
if (!symbol.current) {
symbol.current = Symbol();
}
return symbol;
}
export function useNodeChangeMiddleware<NodeType extends Node = Node>(
fn: (changes: NodeChange<NodeType>[]) => NodeChange<NodeType>[]
) {
const store = useStoreApi<NodeType, Edge>();
const symbol = useSymbol();
useEffect(() => {
const { nodeChangeMiddleware } = store.getState();
nodeChangeMiddleware.set(symbol.current!, fn);
}, [fn]);
useEffect(() => {
const { nodeChangeMiddleware } = store.getState();
return () => {
nodeChangeMiddleware.delete(symbol.current!);
};
}, []);
}
export function useNodeChangeMiddlewareDynamic<NodeType extends Node = Node>(
fn: (changes: NodeChange<NodeType>[]) => NodeChange<NodeType>[]
) {
const store = useStoreApi<NodeType, Edge>();
const symbol = useSymbol();
const { nodeChangeMiddleware } = store.getState();
nodeChangeMiddleware.delete(symbol.current!);
nodeChangeMiddleware.set(symbol.current!, fn);
}
// export function useEdgeChangeMiddleware(fn: (changes: EdgeChange[]) => EdgeChange[]) {
// const store = useStoreApi();
// const symbol = useSymbol();
// useEffect(() => {
// const { edgeChangeMiddleware } = store.getState();
// edgeChangeMiddleware.set(symbol.current!, fn);
// }, [fn]);
// useEffect(() => {
// const { edgeChangeMiddleware } = store.getState();
// return () => {
// edgeChangeMiddleware.delete(symbol.current!);
// };
// }, []);
// }
// export function useNodeSyncMiddleware(fn: (changes: NodeChange[]) => NodeChange[]) {
// const store = useStoreApi();
// const symbol = useSymbol();
// useEffect(() => {
// const { nodeSyncMiddleware } = store.getState();
// nodeSyncMiddleware.set(symbol.current!, fn);
// }, [fn]);
// useEffect(() => {
// const { nodeSyncMiddleware } = store.getState();
// return () => {
// nodeSyncMiddleware.delete(symbol.current!);
// };
// }, []);
// }
// export function useEdgeSyncMiddleware(fn: (changes: EdgeChange[]) => EdgeChange[]) {
// const store = useStoreApi();
// const symbol = useSymbol();
// useEffect(() => {
// const { edgeSyncMiddleware } = store.getState();
// edgeSyncMiddleware.set(symbol.current!, fn);
// }, [fn]);
// useEffect(() => {
// const { edgeSyncMiddleware } = store.getState();
// return () => {
// edgeSyncMiddleware.delete(symbol.current!);
// };
// }, []);
// }

View File

@@ -30,6 +30,8 @@ export { useConnection } from './hooks/useConnection';
export { useInternalNode } from './hooks/useInternalNode';
export { useNodeId } from './contexts/NodeIdContext';
export { useNodeChangeMiddleware, useNodeChangeMiddlewareDynamic } from './hooks/useMiddleware';
export { applyNodeChanges, applyEdgeChanges } from './utils/changes';
export { isNode, isEdge } from './utils/general';

View File

@@ -175,8 +175,8 @@ const createStore = ({
},
updateNodePositions: (nodeDragItems, dragging = false) => {
const parentExpandChildren: ParentExpandChild[] = [];
const changes = [];
const { nodeLookup, triggerNodeChanges } = get();
let changes = [];
const { nodeLookup, triggerNodeChanges, nodeChangeMiddleware } = get();
for (const [id, dragItem] of nodeDragItems) {
// we are using the nodelookup to be sure to use the current expandParent and parentId value
@@ -216,6 +216,10 @@ const createStore = ({
changes.push(...parentExpandChanges);
}
for (const middleware of nodeChangeMiddleware.values()) {
changes = middleware(changes);
}
triggerNodeChanges(changes);
},
triggerNodeChanges: (changes) => {

View File

@@ -146,6 +146,11 @@ const getInitialState = ({
lib: 'react',
debug: false,
ariaLabelConfig: defaultAriaLabelConfig,
nodeChangeMiddleware: new Map(),
edgeChangeMiddleware: new Map(),
nodeSyncMiddleware: new Map(),
edgeSyncMiddleware: new Map(),
};
};

View File

@@ -152,6 +152,11 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
lib: string;
debug: boolean;
ariaLabelConfig: AriaLabelConfig;
nodeChangeMiddleware: Map<symbol, (changes: NodeChange<NodeType>[]) => NodeChange<NodeType>[]>;
edgeChangeMiddleware: Map<symbol, (changes: EdgeChange<EdgeType>[]) => EdgeChange<EdgeType>[]>;
nodeSyncMiddleware: Map<symbol, (changes: NodeChange<NodeType>[]) => NodeChange<NodeType>[]>;
edgeSyncMiddleware: Map<symbol, (changes: EdgeChange<EdgeType>[]) => EdgeChange<EdgeType>[]>;
};
export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {