Merge pull request #3919 from xyflow/fix/remeasure-nodes
Fix/remeasure nodes
This commit is contained in:
@@ -27,9 +27,9 @@ const buttonStyle: CSSProperties = {
|
||||
zIndex: 4,
|
||||
};
|
||||
|
||||
const CustomMiniMapNode = ({ x, y, width, height, color }: MiniMapNodeProps) => (
|
||||
<circle cx={x} cy={y} r={Math.max(width, height) / 2} fill={color} />
|
||||
);
|
||||
const CustomMiniMapNode = ({ x, y, width, height }: MiniMapNodeProps) => {
|
||||
return <circle cx={x} cy={y} r={Math.max(width, height) / 2} fill="#ffcc00" />;
|
||||
};
|
||||
|
||||
const CustomMiniMapNodeFlow = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]);
|
||||
|
||||
@@ -90,7 +90,7 @@ const initialNodes: Node[] = [
|
||||
data: { label: 'Node 5' },
|
||||
position: { x: 650, y: 250 },
|
||||
className: 'light',
|
||||
style: { width: 400, height: 150 },
|
||||
style: { width: 100, height: 100 },
|
||||
zIndex: 1000,
|
||||
},
|
||||
{
|
||||
@@ -161,9 +161,12 @@ const Subflow = () => {
|
||||
setNodes((nds) => {
|
||||
return nds.map((n) => {
|
||||
if (!n.parentNode) {
|
||||
n.position = {
|
||||
x: Math.random() * 400,
|
||||
y: Math.random() * 400,
|
||||
return {
|
||||
...n,
|
||||
position: {
|
||||
x: Math.random() * 400,
|
||||
y: Math.random() * 400,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -178,8 +181,10 @@ const Subflow = () => {
|
||||
const toggleClassnames = () => {
|
||||
setNodes((nds) => {
|
||||
return nds.map((n) => {
|
||||
n.className = n.className === 'light' ? 'dark' : 'light';
|
||||
return n;
|
||||
return {
|
||||
...n,
|
||||
className: n.className === 'light' ? 'dark' : 'light',
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -187,8 +192,10 @@ const Subflow = () => {
|
||||
const toggleChildNodes = () => {
|
||||
setNodes((nds) => {
|
||||
return nds.map((n) => {
|
||||
n.hidden = !!n.parentNode && !n.hidden;
|
||||
return n;
|
||||
return {
|
||||
...n,
|
||||
hidden: !!n.parentNode && !n.hidden,
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -215,19 +222,12 @@ const Subflow = () => {
|
||||
<Background />
|
||||
|
||||
<Panel position="top-right">
|
||||
<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={resetTransform}>reset transform</button>
|
||||
<button onClick={updatePos}>change pos</button>
|
||||
<button onClick={toggleClassnames}>toggle classnames</button>
|
||||
<button onClick={toggleChildNodes}>toggleChildNodes</button>
|
||||
<button onClick={logToObject}>toObject</button>
|
||||
<button onClick={() => setNodes(initialNodes)}>setNodes</button>
|
||||
</Panel>
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
@@ -101,12 +101,12 @@ export function EdgeWrapper({
|
||||
);
|
||||
|
||||
const markerStartUrl = useMemo(
|
||||
() => (edge.markerStart ? `url(#${getMarkerId(edge.markerStart, rfId)})` : undefined),
|
||||
() => (edge.markerStart ? `url('#${getMarkerId(edge.markerStart, rfId)}')` : undefined),
|
||||
[edge.markerStart, rfId]
|
||||
);
|
||||
|
||||
const markerEndUrl = useMemo(
|
||||
() => (edge.markerEnd ? `url(#${getMarkerId(edge.markerEnd, rfId)})` : undefined),
|
||||
() => (edge.markerEnd ? `url('#${getMarkerId(edge.markerEnd, rfId)}')` : undefined),
|
||||
[edge.markerEnd, rfId]
|
||||
);
|
||||
|
||||
|
||||
@@ -79,16 +79,33 @@ export function NodeWrapper({
|
||||
const prevTargetPosition = useRef(node.targetPosition);
|
||||
const prevType = useRef(nodeType);
|
||||
|
||||
const width = node.width ?? undefined;
|
||||
const height = node.height ?? undefined;
|
||||
const computedWidth = node.computed?.width;
|
||||
const computedHeight = node.computed?.height;
|
||||
const initialized = (!!computedWidth && !!computedHeight) || (!!width && !!height);
|
||||
const hasHandleBounds = !!node[internalsSymbol]?.handleBounds;
|
||||
|
||||
const moveSelectedNodes = useMoveSelectedNodes();
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (nodeRef.current) {
|
||||
resizeObserver?.unobserve(nodeRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (nodeRef.current && !node.hidden) {
|
||||
const currNode = nodeRef.current;
|
||||
resizeObserver?.observe(currNode);
|
||||
|
||||
return () => resizeObserver?.unobserve(currNode);
|
||||
if (!initialized || !hasHandleBounds) {
|
||||
resizeObserver?.unobserve(currNode);
|
||||
resizeObserver?.observe(currNode);
|
||||
}
|
||||
}
|
||||
}, [node.hidden]);
|
||||
}, [node.hidden, initialized, hasHandleBounds]);
|
||||
|
||||
useEffect(() => {
|
||||
// when the user programmatically changes the source or handle position, we re-initialize the node
|
||||
@@ -123,11 +140,6 @@ export function NodeWrapper({
|
||||
return null;
|
||||
}
|
||||
|
||||
const width = node.width ?? undefined;
|
||||
const height = node.height ?? undefined;
|
||||
const computedWidth = node.computed?.width;
|
||||
const computedHeight = node.computed?.height;
|
||||
|
||||
const positionAbsoluteOrigin = getPositionWithOrigin({
|
||||
x: positionAbsoluteX,
|
||||
y: positionAbsoluteY,
|
||||
@@ -135,7 +147,6 @@ export function NodeWrapper({
|
||||
height: computedHeight ?? height ?? 0,
|
||||
origin: node.origin || nodeOrigin,
|
||||
});
|
||||
const initialized = (!!computedWidth && !!computedHeight) || (!!width && !!height);
|
||||
const hasPointerEvents = isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave;
|
||||
|
||||
const onMouseEnterHandler = onMouseEnter ? (event: MouseEvent) => onMouseEnter(event, { ...node }) : undefined;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { infiniteExtent, type CoordinateExtent } from '@xyflow/system';
|
||||
|
||||
import { useStore, useStoreApi } from '../../hooks/useStore';
|
||||
import type { Node, Edge, ReactFlowState, ReactFlowProps, FitViewOptions } from '../../types';
|
||||
import { initNodeOrigin } from '../../container/ReactFlow';
|
||||
import { defaultNodeOrigin } from '../../container/ReactFlow/init-values';
|
||||
|
||||
// these fields exist in the global store and we need to keep them up to date
|
||||
const reactFlowFieldsToTrack = [
|
||||
@@ -84,6 +84,19 @@ const selector = (s: ReactFlowState) => ({
|
||||
setDefaultNodesAndEdges: s.setDefaultNodesAndEdges,
|
||||
});
|
||||
|
||||
const initPrevValues = {
|
||||
// these are values that are also passed directly to other components
|
||||
// than the StoreUpdater. We can reduce the number of setStore calls
|
||||
// by setting the same values here as prev fields.
|
||||
translateExtent: infiniteExtent,
|
||||
nodeOrigin: defaultNodeOrigin,
|
||||
minZoom: 0.5,
|
||||
maxZoom: 2,
|
||||
elementsSelectable: true,
|
||||
noPanClassName: 'nopan',
|
||||
rfId: '1',
|
||||
};
|
||||
|
||||
export function StoreUpdater(props: StoreUpdaterProps) {
|
||||
const {
|
||||
setNodes,
|
||||
@@ -99,23 +112,15 @@ export function StoreUpdater(props: StoreUpdaterProps) {
|
||||
|
||||
useEffect(() => {
|
||||
setDefaultNodesAndEdges(props.defaultNodes, props.defaultEdges);
|
||||
|
||||
return () => {
|
||||
// when we reset the store we also need to reset the previous fields
|
||||
previousFields.current = initPrevValues;
|
||||
reset();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const previousFields = useRef<Partial<StoreUpdaterProps>>({
|
||||
// these are values that are also passed directly to other components
|
||||
// than the StoreUpdater. We can reduce the number of setStore calls
|
||||
// by setting the same values here as prev fields.
|
||||
translateExtent: infiniteExtent,
|
||||
nodeOrigin: initNodeOrigin,
|
||||
minZoom: 0.5,
|
||||
maxZoom: 2,
|
||||
elementsSelectable: true,
|
||||
noPanClassName: 'nopan',
|
||||
rfId: '1',
|
||||
});
|
||||
const previousFields = useRef<Partial<StoreUpdaterProps>>(initPrevValues);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import { forwardRef, type CSSProperties } from 'react';
|
||||
import cc from 'classcat';
|
||||
import {
|
||||
ConnectionLineType,
|
||||
PanOnScrollMode,
|
||||
SelectionMode,
|
||||
infiniteExtent,
|
||||
isMacOs,
|
||||
type NodeOrigin,
|
||||
type Viewport,
|
||||
} from '@xyflow/system';
|
||||
import { ConnectionLineType, PanOnScrollMode, SelectionMode, infiniteExtent, isMacOs } from '@xyflow/system';
|
||||
|
||||
import { A11yDescriptions } from '../../components/A11yDescriptions';
|
||||
import { Attribution } from '../../components/Attribution';
|
||||
@@ -18,9 +10,7 @@ import { useColorModeClass } from '../../hooks/useColorModeClass';
|
||||
import { GraphView } from '../GraphView';
|
||||
import { Wrapper } from './Wrapper';
|
||||
import type { ReactFlowProps, ReactFlowRefType } from '../../types';
|
||||
|
||||
export const initNodeOrigin: NodeOrigin = [0, 0];
|
||||
const initDefaultViewport: Viewport = { x: 0, y: 0, zoom: 1 };
|
||||
import { defaultViewport as initViewport, defaultNodeOrigin } from './init-values';
|
||||
|
||||
const wrapperStyle: CSSProperties = {
|
||||
width: '100%',
|
||||
@@ -89,11 +79,11 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
||||
nodesDraggable,
|
||||
nodesConnectable,
|
||||
nodesFocusable,
|
||||
nodeOrigin = initNodeOrigin,
|
||||
nodeOrigin = defaultNodeOrigin,
|
||||
edgesFocusable,
|
||||
edgesUpdatable,
|
||||
elementsSelectable = true,
|
||||
defaultViewport = initDefaultViewport,
|
||||
defaultViewport = initViewport,
|
||||
minZoom = 0.5,
|
||||
maxZoom = 2,
|
||||
translateExtent = infiniteExtent,
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { type NodeOrigin, Viewport } from '@xyflow/system';
|
||||
|
||||
export const defaultNodeOrigin: NodeOrigin = [0, 0];
|
||||
export const defaultViewport: Viewport = { x: 0, y: 0, zoom: 1 };
|
||||
@@ -303,13 +303,7 @@ const createRFStore = ({
|
||||
set(currentConnection);
|
||||
},
|
||||
|
||||
reset: () => {
|
||||
// @todo: what should we do about this? Do we still need it?
|
||||
// if you are on a SPA with multiple flows, we want to make sure that the store gets resetted
|
||||
// when you switch pages. Does this reset solves this? Currently it always gets called. This
|
||||
// leads to an emtpy nodes array at the beginning.
|
||||
// set({ ...getInitialState() });
|
||||
},
|
||||
reset: () => set({ ...getInitialState() }),
|
||||
}),
|
||||
Object.is
|
||||
);
|
||||
|
||||
@@ -94,8 +94,8 @@ export function adoptUserProvidedNodes<NodeType extends NodeBase>(
|
||||
...n,
|
||||
computed: {
|
||||
positionAbsolute: n.position,
|
||||
width: n.computed?.width || currentStoreNode?.computed?.width,
|
||||
height: n.computed?.height || currentStoreNode?.computed?.height,
|
||||
width: n.computed?.width,
|
||||
height: n.computed?.height,
|
||||
},
|
||||
};
|
||||
const z = (isNumeric(n.zIndex) ? n.zIndex : 0) + (n.selected ? selectedNodeZ : 0);
|
||||
|
||||
Reference in New Issue
Block a user