diff --git a/packages/react/src/additional-components/Controls/Controls.tsx b/packages/react/src/additional-components/Controls/Controls.tsx index 99139b81..29fcb268 100644 --- a/packages/react/src/additional-components/Controls/Controls.tsx +++ b/packages/react/src/additional-components/Controls/Controls.tsx @@ -1,4 +1,4 @@ -import { memo, useEffect, useState, type FC, type PropsWithChildren } from 'react'; +import { memo, type FC, type PropsWithChildren } from 'react'; import cc from 'classcat'; import { shallow } from 'zustand/shallow'; @@ -37,18 +37,9 @@ const Controls: FC> = ({ position = 'bottom-left', }) => { const store = useStoreApi(); - const [isVisible, setIsVisible] = useState(false); const { isInteractive, minZoomReached, maxZoomReached } = useStore(selector, shallow); const { zoomIn, zoomOut, fitView } = useReactFlow(); - useEffect(() => { - setIsVisible(true); - }, []); - - if (!isVisible) { - return null; - } - const onZoomInHandler = () => { zoomIn(); onZoomIn?.(); diff --git a/packages/react/src/components/Nodes/wrapNode.tsx b/packages/react/src/components/Nodes/wrapNode.tsx index 43e6334a..858188cb 100644 --- a/packages/react/src/components/Nodes/wrapNode.tsx +++ b/packages/react/src/components/Nodes/wrapNode.tsx @@ -52,6 +52,8 @@ export default (NodeComponent: ComponentType) => { disableKeyboardA11y, ariaLabel, rfId, + dimensionWidth, + dimensionHeight, }: WrapNodeProps) => { const store = useStoreApi(); const nodeRef = useRef(null); @@ -181,7 +183,9 @@ export default (NodeComponent: ComponentType) => { zIndex, transform: `translate(${xPosOrigin}px,${yPosOrigin}px)`, pointerEvents: hasPointerEvents ? 'all' : 'none', - visibility: initialized ? 'visible' : 'visible', + visibility: initialized ? 'visible' : 'hidden', + width: dimensionWidth, + height: dimensionHeight, ...style, }} data-id={id} diff --git a/packages/react/src/components/ReactFlowProvider/index.tsx b/packages/react/src/components/ReactFlowProvider/index.tsx index 4a1904bb..cd3b2139 100644 --- a/packages/react/src/components/ReactFlowProvider/index.tsx +++ b/packages/react/src/components/ReactFlowProvider/index.tsx @@ -6,11 +6,23 @@ import { Provider } from '../../contexts/RFStoreContext'; import { createRFStore } from '../../store'; import type { ReactFlowState, Node, Edge } from '../../types'; -function ReactFlowProvider({ children, nodes, edges }: { children: ReactNode; nodes?: Node[]; edges?: Edge[] }) { +function ReactFlowProvider({ + children, + nodes, + edges, + width, + height, +}: { + children: ReactNode; + nodes?: Node[]; + edges?: Edge[]; + width?: number; + height?: number; +}) { const storeRef = useRef> | null>(null); if (!storeRef.current) { - storeRef.current = createRFStore({ nodes, edges }); + storeRef.current = createRFStore({ nodes, edges, width, height }); } return {children}; diff --git a/packages/react/src/container/NodeRenderer/index.tsx b/packages/react/src/container/NodeRenderer/index.tsx index 34fb4c55..8096a9ab 100644 --- a/packages/react/src/container/NodeRenderer/index.tsx +++ b/packages/react/src/container/NodeRenderer/index.tsx @@ -98,6 +98,7 @@ const NodeRenderer = (props: NodeRendererProps) => { height: node.height ?? 0, origin: node.origin || props.nodeOrigin, }); + const initialized = (!!node.width && !!node.height) || (!!node.dimensions?.width && !!node.dimensions?.height); return ( { id={node.id} className={node.className} style={node.style} + dimensionWidth={node.dimensions?.width} + dimensionHeight={node.dimensions?.height} type={nodeType} data={node.data} sourcePosition={node.sourcePosition || Position.Bottom} @@ -131,7 +134,7 @@ const NodeRenderer = (props: NodeRendererProps) => { isParent={!!node[internalsSymbol]?.isParent} noDragClassName={props.noDragClassName} noPanClassName={props.noPanClassName} - initialized={!!node.width && !!node.height} + initialized={initialized} rfId={props.rfId} disableKeyboardA11y={props.disableKeyboardA11y} ariaLabel={node.ariaLabel} diff --git a/packages/react/src/container/ReactFlow/Wrapper.tsx b/packages/react/src/container/ReactFlow/Wrapper.tsx index b4ff1199..37b465fb 100644 --- a/packages/react/src/container/ReactFlow/Wrapper.tsx +++ b/packages/react/src/container/ReactFlow/Wrapper.tsx @@ -4,7 +4,19 @@ import StoreContext from '../../contexts/RFStoreContext'; import ReactFlowProvider from '../../components/ReactFlowProvider'; import type { Node, Edge } from '../../types'; -function Wrapper({ children, nodes, edges }: { children: ReactNode; nodes?: Node[]; edges?: Edge[] }) { +function Wrapper({ + children, + nodes, + edges, + width, + height, +}: { + children: ReactNode; + nodes?: Node[]; + edges?: Edge[]; + width?: number; + height?: number; +}) { const isWrapped = useContext(StoreContext); if (isWrapped) { @@ -14,7 +26,7 @@ function Wrapper({ children, nodes, edges }: { children: ReactNode; nodes?: Node } return ( - + {children} ); diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx index 20d62cbe..8f748831 100644 --- a/packages/react/src/container/ReactFlow/index.tsx +++ b/packages/react/src/container/ReactFlow/index.tsx @@ -166,6 +166,8 @@ const ReactFlow = forwardRef( nodeDragThreshold, viewport, onViewportChange, + width, + height, ...rest }, ref @@ -181,7 +183,7 @@ const ReactFlow = forwardRef( data-testid="rf__wrapper" id={id} > - + +const createRFStore = ({ + nodes, + edges, + width, + height, +}: { + nodes?: Node[]; + edges?: Edge[]; + width?: number; + height?: number; +}) => createWithEqualityFn( (set, get) => ({ - ...getInitialState({ nodes, edges }), + ...getInitialState({ nodes, edges, width, height }), setNodes: (nodes: Node[]) => { const { nodes: storeNodes, nodeOrigin, elevateNodesOnSelect } = get(); const nextNodes = updateNodes(nodes, storeNodes, { nodeOrigin, elevateNodesOnSelect }); diff --git a/packages/react/src/store/initialState.ts b/packages/react/src/store/initialState.ts index 4e6ed8e7..c70072a1 100644 --- a/packages/react/src/store/initialState.ts +++ b/packages/react/src/store/initialState.ts @@ -1,15 +1,45 @@ -import { devWarn, infiniteExtent, ConnectionMode, updateNodes } from '@xyflow/system'; +import { + devWarn, + infiniteExtent, + ConnectionMode, + updateNodes, + getRectOfNodes, + getTransformForBounds, + Transform, +} from '@xyflow/system'; import type { Edge, Node, ReactFlowStore } from '../types'; -const getInitialState = ({ nodes = [], edges = [] }: { nodes?: Node[]; edges?: Edge[] }): ReactFlowStore => { +const getInitialState = ({ + nodes = [], + edges = [], + width, + height, +}: { + nodes?: Node[]; + edges?: Edge[]; + width?: number; + height?: number; +}): ReactFlowStore => { const nextNodes = updateNodes(nodes, [], { nodeOrigin: [0, 0], elevateNodesOnSelect: false }); + let transform: Transform = [0, 0, 1]; + + if (width && height) { + const nodesWithDimensions = nextNodes.map((node) => ({ + ...node, + width: node.dimensions?.width, + height: node.dimensions?.height, + })); + const bounds = getRectOfNodes(nodesWithDimensions, [0, 0]); + transform = getTransformForBounds(bounds, width, height, 0.5, 2, 0.1); + } + return { rfId: '1', width: 0, height: 0, - transform: [0, 0, 1], + transform, nodes: nextNodes, edges: edges, onNodesChange: null, diff --git a/packages/react/src/types/component-props.ts b/packages/react/src/types/component-props.ts index 3fe97f07..e9c7a5ff 100644 --- a/packages/react/src/types/component-props.ts +++ b/packages/react/src/types/component-props.ts @@ -151,6 +151,8 @@ export type ReactFlowProps = HTMLAttributes & { onError?: OnError; isValidConnection?: IsValidConnection; nodeDragThreshold?: number; + width?: number; + height?: number; }; export type ReactFlowRefType = HTMLDivElement; diff --git a/packages/react/src/types/nodes.ts b/packages/react/src/types/nodes.ts index 9f5edc0a..f33de682 100644 --- a/packages/react/src/types/nodes.ts +++ b/packages/react/src/types/nodes.ts @@ -40,4 +40,6 @@ export type WrapNodeProps = Pick< noPanClassName: string; rfId: string; disableKeyboardA11y: boolean; + dimensionWidth?: number; + dimensionHeight?: number; };