fix(react): re-measure nodes correctly
This commit is contained in:
@@ -101,12 +101,12 @@ export function EdgeWrapper({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const markerStartUrl = useMemo(
|
const markerStartUrl = useMemo(
|
||||||
() => (edge.markerStart ? `url(#${getMarkerId(edge.markerStart, rfId)})` : undefined),
|
() => (edge.markerStart ? `url('#${getMarkerId(edge.markerStart, rfId)}')` : undefined),
|
||||||
[edge.markerStart, rfId]
|
[edge.markerStart, rfId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const markerEndUrl = useMemo(
|
const markerEndUrl = useMemo(
|
||||||
() => (edge.markerEnd ? `url(#${getMarkerId(edge.markerEnd, rfId)})` : undefined),
|
() => (edge.markerEnd ? `url('#${getMarkerId(edge.markerEnd, rfId)}')` : undefined),
|
||||||
[edge.markerEnd, rfId]
|
[edge.markerEnd, rfId]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -84,22 +84,28 @@ export function NodeWrapper({
|
|||||||
const computedWidth = node.computed?.width;
|
const computedWidth = node.computed?.width;
|
||||||
const computedHeight = node.computed?.height;
|
const computedHeight = node.computed?.height;
|
||||||
const initialized = (!!computedWidth && !!computedHeight) || (!!width && !!height);
|
const initialized = (!!computedWidth && !!computedHeight) || (!!width && !!height);
|
||||||
|
const hasHandleBounds = !!node[internalsSymbol]?.handleBounds;
|
||||||
|
|
||||||
const moveSelectedNodes = useMoveSelectedNodes();
|
const moveSelectedNodes = useMoveSelectedNodes();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (nodeRef.current) {
|
||||||
|
resizeObserver?.unobserve(nodeRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (nodeRef.current && !node.hidden) {
|
if (nodeRef.current && !node.hidden) {
|
||||||
const currNode = nodeRef.current;
|
const currNode = nodeRef.current;
|
||||||
|
|
||||||
if (!initialized) {
|
if (!initialized || !hasHandleBounds) {
|
||||||
resizeObserver?.observe(currNode);
|
|
||||||
} else {
|
|
||||||
resizeObserver?.unobserve(currNode);
|
resizeObserver?.unobserve(currNode);
|
||||||
|
resizeObserver?.observe(currNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => resizeObserver?.unobserve(currNode);
|
|
||||||
}
|
}
|
||||||
}, [node.hidden, initialized]);
|
}, [node.hidden, initialized, hasHandleBounds]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// when the user programmatically changes the source or handle position, we re-initialize the node
|
// when the user programmatically changes the source or handle position, we re-initialize the node
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { infiniteExtent, type CoordinateExtent } from '@xyflow/system';
|
|||||||
|
|
||||||
import { useStore, useStoreApi } from '../../hooks/useStore';
|
import { useStore, useStoreApi } from '../../hooks/useStore';
|
||||||
import type { Node, Edge, ReactFlowState, ReactFlowProps, FitViewOptions } from '../../types';
|
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
|
// these fields exist in the global store and we need to keep them up to date
|
||||||
const reactFlowFieldsToTrack = [
|
const reactFlowFieldsToTrack = [
|
||||||
@@ -84,6 +84,19 @@ const selector = (s: ReactFlowState) => ({
|
|||||||
setDefaultNodesAndEdges: s.setDefaultNodesAndEdges,
|
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) {
|
export function StoreUpdater(props: StoreUpdaterProps) {
|
||||||
const {
|
const {
|
||||||
setNodes,
|
setNodes,
|
||||||
@@ -99,23 +112,15 @@ export function StoreUpdater(props: StoreUpdaterProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDefaultNodesAndEdges(props.defaultNodes, props.defaultEdges);
|
setDefaultNodesAndEdges(props.defaultNodes, props.defaultEdges);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
// when we reset the store we also need to reset the previous fields
|
||||||
|
previousFields.current = initPrevValues;
|
||||||
reset();
|
reset();
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const previousFields = useRef<Partial<StoreUpdaterProps>>({
|
const previousFields = useRef<Partial<StoreUpdaterProps>>(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: initNodeOrigin,
|
|
||||||
minZoom: 0.5,
|
|
||||||
maxZoom: 2,
|
|
||||||
elementsSelectable: true,
|
|
||||||
noPanClassName: 'nopan',
|
|
||||||
rfId: '1',
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
import { forwardRef, type CSSProperties } from 'react';
|
import { forwardRef, type CSSProperties } from 'react';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
import {
|
import { ConnectionLineType, PanOnScrollMode, SelectionMode, infiniteExtent, isMacOs } from '@xyflow/system';
|
||||||
ConnectionLineType,
|
|
||||||
PanOnScrollMode,
|
|
||||||
SelectionMode,
|
|
||||||
infiniteExtent,
|
|
||||||
isMacOs,
|
|
||||||
type NodeOrigin,
|
|
||||||
type Viewport,
|
|
||||||
} from '@xyflow/system';
|
|
||||||
|
|
||||||
import { A11yDescriptions } from '../../components/A11yDescriptions';
|
import { A11yDescriptions } from '../../components/A11yDescriptions';
|
||||||
import { Attribution } from '../../components/Attribution';
|
import { Attribution } from '../../components/Attribution';
|
||||||
@@ -18,9 +10,7 @@ import { useColorModeClass } from '../../hooks/useColorModeClass';
|
|||||||
import { GraphView } from '../GraphView';
|
import { GraphView } from '../GraphView';
|
||||||
import { Wrapper } from './Wrapper';
|
import { Wrapper } from './Wrapper';
|
||||||
import type { ReactFlowProps, ReactFlowRefType } from '../../types';
|
import type { ReactFlowProps, ReactFlowRefType } from '../../types';
|
||||||
|
import { defaultViewport as initViewport, defaultNodeOrigin } from './init-values';
|
||||||
export const initNodeOrigin: NodeOrigin = [0, 0];
|
|
||||||
const initDefaultViewport: Viewport = { x: 0, y: 0, zoom: 1 };
|
|
||||||
|
|
||||||
const wrapperStyle: CSSProperties = {
|
const wrapperStyle: CSSProperties = {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -89,11 +79,11 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
|||||||
nodesDraggable,
|
nodesDraggable,
|
||||||
nodesConnectable,
|
nodesConnectable,
|
||||||
nodesFocusable,
|
nodesFocusable,
|
||||||
nodeOrigin = initNodeOrigin,
|
nodeOrigin = defaultNodeOrigin,
|
||||||
edgesFocusable,
|
edgesFocusable,
|
||||||
edgesUpdatable,
|
edgesUpdatable,
|
||||||
elementsSelectable = true,
|
elementsSelectable = true,
|
||||||
defaultViewport = initDefaultViewport,
|
defaultViewport = initViewport,
|
||||||
minZoom = 0.5,
|
minZoom = 0.5,
|
||||||
maxZoom = 2,
|
maxZoom = 2,
|
||||||
translateExtent = infiniteExtent,
|
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);
|
set(currentConnection);
|
||||||
},
|
},
|
||||||
|
|
||||||
reset: () => {
|
reset: () => set({ ...getInitialState() }),
|
||||||
// @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() });
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
Object.is
|
Object.is
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user