Merge branch 'next' into refactor/edge-rendering
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { ColorMode, ColorModeClass } from '@xyflow/system';
|
||||
|
||||
function getMediaQuery() {
|
||||
if (typeof window === 'undefined' || !window.matchMedia) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return window.matchMedia('(prefers-color-scheme: dark)');
|
||||
}
|
||||
|
||||
export default function useColorModeClass(colorMode: ColorMode): ColorModeClass {
|
||||
const [colorModeClass, setColorModeClass] = useState<ColorModeClass | null>(
|
||||
colorMode === 'system' ? null : colorMode
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (colorMode !== 'system') {
|
||||
setColorModeClass(colorMode);
|
||||
return;
|
||||
}
|
||||
|
||||
const mediaQuery = getMediaQuery();
|
||||
const updateColorModeClass = () => setColorModeClass(mediaQuery?.matches ? 'dark' : 'light');
|
||||
|
||||
updateColorModeClass();
|
||||
mediaQuery?.addEventListener('change', updateColorModeClass);
|
||||
|
||||
return () => {
|
||||
mediaQuery?.removeEventListener('change', updateColorModeClass);
|
||||
};
|
||||
}, [colorMode]);
|
||||
|
||||
return colorModeClass !== null ? colorModeClass : getMediaQuery()?.matches ? 'dark' : 'light';
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import { Connection, HandleType, areConnectionMapsEqual, handleConnectionChange } from '@xyflow/system';
|
||||
|
||||
import { useStore } from './useStore';
|
||||
import { useNodeId } from '../contexts/NodeIdContext';
|
||||
|
||||
type useHandleConnectionsParams = {
|
||||
type: HandleType;
|
||||
id?: string | null;
|
||||
nodeId?: string;
|
||||
onConnect?: (connections: Connection[]) => void;
|
||||
onDisconnect?: (connections: Connection[]) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to check if a <Handle /> is connected to another <Handle /> and get the connections.
|
||||
*
|
||||
* @public
|
||||
* @param param.type - handle type 'source' or 'target'
|
||||
* @param param.id - the handle id (this is only needed if the node has multiple handles of the same type)
|
||||
* @param param.nodeId - node id - if not provided, the node id from the NodeIdContext is used
|
||||
* @param param.onConnect - gets called when a connection is established
|
||||
* @param param.onDisconnect - gets called when a connection is removed
|
||||
* @returns an array with connections
|
||||
*/
|
||||
export function useHandleConnections({
|
||||
type,
|
||||
id = null,
|
||||
nodeId,
|
||||
onConnect,
|
||||
onDisconnect,
|
||||
}: useHandleConnectionsParams): Connection[] {
|
||||
const _nodeId = useNodeId();
|
||||
const prevConnections = useRef<Map<string, Connection> | null>(null);
|
||||
const currentNodeId = nodeId || _nodeId;
|
||||
|
||||
const connections = useStore(
|
||||
(state) => state.connectionLookup.get(`${currentNodeId}-${type}-${id}`),
|
||||
areConnectionMapsEqual
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// @todo dicuss if onConnect/onDisconnect should be called when the component mounts/unmounts
|
||||
if (prevConnections.current && prevConnections.current !== connections) {
|
||||
const _connections = connections ?? new Map();
|
||||
handleConnectionChange(prevConnections.current, _connections, onDisconnect);
|
||||
handleConnectionChange(_connections, prevConnections.current, onConnect);
|
||||
}
|
||||
|
||||
prevConnections.current = connections ?? new Map();
|
||||
}, [connections, onConnect, onDisconnect]);
|
||||
|
||||
return useMemo(() => Array.from(connections?.values() ?? []), [connections]);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { useCallback } from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { useStore } from '../hooks/useStore';
|
||||
import type { Node } from '../types';
|
||||
|
||||
export function useNodesData<NodeType extends Node = Node>(nodeId: string): NodeType['data'] | null;
|
||||
export function useNodesData<NodeType extends Node = Node>(nodeIds: string[]): NodeType['data'][];
|
||||
export function useNodesData<NodeType extends Node = Node>(
|
||||
nodeIds: string[],
|
||||
guard: (node: Node) => node is NodeType
|
||||
): NodeType['data'][];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function useNodesData(nodeIds: any): any {
|
||||
const nodesData = useStore(
|
||||
useCallback(
|
||||
(s) => {
|
||||
if (!Array.isArray(nodeIds)) {
|
||||
return s.nodeLookup.get(nodeIds)?.data || null;
|
||||
}
|
||||
|
||||
const data = [];
|
||||
|
||||
for (const nodeId of nodeIds) {
|
||||
const nodeData = s.nodeLookup.get(nodeId)?.data;
|
||||
|
||||
if (nodeData) {
|
||||
data.push(nodeData);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
[nodeIds]
|
||||
),
|
||||
shallow
|
||||
);
|
||||
|
||||
return nodesData;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from '@xyflow/system';
|
||||
|
||||
import useViewportHelper from './useViewportHelper';
|
||||
import { useStoreApi } from '../hooks/useStore';
|
||||
import { useStoreApi } from './useStore';
|
||||
import type {
|
||||
ReactFlowInstance,
|
||||
Instance,
|
||||
@@ -24,6 +24,7 @@ import type {
|
||||
Node,
|
||||
Edge,
|
||||
} from '../types';
|
||||
import { isNode } from '../utils';
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlowInstance<NodeData, EdgeData> {
|
||||
@@ -206,7 +207,7 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
|
||||
}
|
||||
|
||||
return (nodes || store.getState().nodes).filter((n) => {
|
||||
if (!isRect && (n.id === node!.id || !n.positionAbsolute)) {
|
||||
if (!isRect && (n.id === node!.id || !n.computed?.positionAbsolute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -271,6 +272,36 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
|
||||
return getOutgoersBase(node, nodes, edges);
|
||||
}, []);
|
||||
|
||||
const updateNode = useCallback<Instance.UpdateNode>(
|
||||
(id, nodeUpdate, options = { replace: true }) => {
|
||||
setNodes((prevNodes) =>
|
||||
prevNodes.map((node) => {
|
||||
if (node.id === id) {
|
||||
const nextNode = typeof nodeUpdate === 'function' ? nodeUpdate(node as Node) : nodeUpdate;
|
||||
return options.replace && isNode(nextNode) ? nextNode : { ...node, ...nextNode };
|
||||
}
|
||||
|
||||
return node;
|
||||
})
|
||||
);
|
||||
},
|
||||
[setNodes]
|
||||
);
|
||||
|
||||
const updateNodeData = useCallback<Instance.UpdateNodeData>(
|
||||
(id, dataUpdate, options = { replace: false }) => {
|
||||
updateNode(
|
||||
id,
|
||||
(node) => {
|
||||
const nextData = typeof dataUpdate === 'function' ? dataUpdate(node) : dataUpdate;
|
||||
return options.replace ? { ...node, data: nextData } : { ...node, data: { ...node.data, ...nextData } };
|
||||
},
|
||||
options
|
||||
);
|
||||
},
|
||||
[updateNode]
|
||||
);
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
...viewportHelper,
|
||||
@@ -289,6 +320,8 @@ export default function useReactFlow<NodeData = any, EdgeData = any>(): ReactFlo
|
||||
getConnectedEdges,
|
||||
getIncomers,
|
||||
getOutgoers,
|
||||
updateNode,
|
||||
updateNodeData,
|
||||
};
|
||||
}, [
|
||||
viewportHelper,
|
||||
|
||||
@@ -23,8 +23,11 @@ function useUpdateNodePositions() {
|
||||
const yDiff = params.y * yVelo * factor;
|
||||
|
||||
const nodeUpdates = selectedNodes.map((node) => {
|
||||
if (node.positionAbsolute) {
|
||||
let nextPosition = { x: node.positionAbsolute.x + xDiff, y: node.positionAbsolute.y + yDiff };
|
||||
if (node.computed?.positionAbsolute) {
|
||||
let nextPosition = {
|
||||
x: node.computed?.positionAbsolute.x + xDiff,
|
||||
y: node.computed?.positionAbsolute.y + yDiff,
|
||||
};
|
||||
|
||||
if (snapToGrid) {
|
||||
nextPosition = snapPosition(nextPosition, snapGrid);
|
||||
@@ -40,7 +43,10 @@ function useUpdateNodePositions() {
|
||||
);
|
||||
|
||||
node.position = position;
|
||||
node.positionAbsolute = positionAbsolute;
|
||||
if (!node.computed) {
|
||||
node.computed = {};
|
||||
}
|
||||
node.computed.positionAbsolute = positionAbsolute;
|
||||
}
|
||||
|
||||
return node;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user