feat(nested-nodes): simplify nested node rendering

This commit is contained in:
Christopher Möller
2021-10-27 15:38:29 +02:00
parent 3e0b673e90
commit 241f604125
7 changed files with 47 additions and 65 deletions
+10 -17
View File
@@ -26,38 +26,40 @@ const initialNodes: Node[] = [
{ {
id: '4', id: '4',
data: { label: 'Node 4' }, data: { label: 'Node 4' },
position: { x: 0, y: 0 }, position: { x: 100, y: 200 },
className: 'light', className: 'light',
style: { backgroundColor: 'rgba(255, 0, 0, .2)' }, style: { backgroundColor: 'rgba(255, 0, 0, .2)' },
width: 0, width: 600,
height: 0, height: 300,
}, },
{ {
id: '4a', id: '4a',
data: { label: 'Node 4a' }, data: { label: 'Node 4a' },
position: { x: 70, y: 200 }, position: { x: 115, y: 215 },
className: 'light', className: 'light',
parentNode: '4', parentNode: '4',
}, },
{ {
id: '4b', id: '4b',
data: { label: 'Node 4b' }, data: { label: 'Node 4b' },
position: { x: 0, y: 0 }, position: { x: 250, y: 250 },
className: 'light', className: 'light',
style: { backgroundColor: 'rgba(255, 0, 0, .2)' }, style: { backgroundColor: 'rgba(255, 0, 0, .2)' },
parentNode: '4', parentNode: '4',
height: 200,
width: 350,
}, },
{ {
id: '4b1', id: '4b1',
data: { label: 'Node 4b1' }, data: { label: 'Node 4b1' },
position: { x: 150, y: 270 }, position: { x: 270, y: 270 },
className: 'light', className: 'light',
parentNode: '4b', parentNode: '4b',
}, },
{ {
id: '4b2', id: '4b2',
data: { label: 'Node 4b2' }, data: { label: 'Node 4b2' },
position: { x: 420, y: 370 }, position: { x: 500, y: 400 },
className: 'light', className: 'light',
parentNode: '4b', parentNode: '4b',
}, },
@@ -129,18 +131,9 @@ const BasicFlow = () => {
setEdges((es) => applyEdgeChanges(changes, es)); setEdges((es) => applyEdgeChanges(changes, es));
}, []); }, []);
const nodesWithLabel = useMemo(
() =>
nodes.map((n) => {
n.data = { ...n.data, label: `${n.width}x${n.height}` };
return n;
}),
[nodes]
);
return ( return (
<ReactFlow <ReactFlow
nodes={nodesWithLabel} nodes={nodes}
edges={edges} edges={edges}
onLoad={onLoad} onLoad={onLoad}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
+9 -2
View File
@@ -23,6 +23,8 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
scale, scale,
xPos, xPos,
yPos, yPos,
width,
height,
isSelected, isSelected,
onClick, onClick,
onMouseEnter, onMouseEnter,
@@ -49,6 +51,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
resizeObserver, resizeObserver,
dragHandle, dragHandle,
zIndex, zIndex,
isParentNode,
}: WrapNodeProps) => { }: WrapNodeProps) => {
const { const {
addSelectedElements, addSelectedElements,
@@ -70,6 +73,8 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave ? 'all' : 'none', isSelectable || isDraggable || onClick || onMouseEnter || onMouseMove || onMouseLeave ? 'all' : 'none',
// prevents jumping of nodes on start // prevents jumping of nodes on start
// opacity: isInitialized ? 1 : 0, // opacity: isInitialized ? 1 : 0,
width: isParentNode && width !== null ? width : 'auto',
height: isParentNode && height !== null ? height : 'auto',
...style, ...style,
}), }),
[ [
@@ -84,6 +89,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
onMouseEnter, onMouseEnter,
onMouseMove, onMouseMove,
onMouseLeave, onMouseLeave,
isParentNode,
] ]
); );
@@ -200,10 +206,10 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
); );
useEffect(() => { useEffect(() => {
if (nodeElement.current && !isHidden && !isInitialized) { if (nodeElement.current && !isHidden && (!isInitialized || isParentNode)) {
updateNodeDimensions([{ id, nodeElement: nodeElement.current, forceUpdate: true }]); updateNodeDimensions([{ id, nodeElement: nodeElement.current, forceUpdate: true }]);
} }
}, [id, isHidden, sourcePosition, targetPosition, isInitialized]); }, [id, isHidden, sourcePosition, targetPosition, isInitialized, isParentNode]);
useEffect(() => { useEffect(() => {
if (nodeElement.current) { if (nodeElement.current) {
@@ -225,6 +231,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
{ {
selected: isSelected, selected: isSelected,
selectable: isSelectable, selectable: isSelectable,
parent: isParentNode,
}, },
]); ]);
+7 -31
View File
@@ -1,9 +1,8 @@
import React, { memo, useMemo, ComponentType, MouseEvent, Fragment, useEffect } from 'react'; import React, { memo, useMemo, ComponentType, MouseEvent, Fragment } from 'react';
import shallow from 'zustand/shallow'; import shallow from 'zustand/shallow';
import { useStore } from '../../store'; import { useStore } from '../../store';
import { Node, NodeTypesType, ReactFlowState, WrapNodeProps, SnapGrid } from '../../types'; import { Node, NodeTypesType, ReactFlowState, WrapNodeProps, SnapGrid } from '../../types';
import { getRectOfNodes } from '../../utils/graph';
import useVisibleNodes from '../../hooks/useVisibleNodes'; import useVisibleNodes from '../../hooks/useVisibleNodes';
interface NodeRendererProps { interface NodeRendererProps {
nodeTypes: NodeTypesType; nodeTypes: NodeTypesType;
@@ -63,7 +62,7 @@ function Node({
recursionDepth, recursionDepth,
...props ...props
}: NodeProps) { }: NodeProps) {
const onNodesChange = useStore((s) => s.onNodesChange); // const onNodesChange = useStore((s) => s.onNodesChange);
const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType<WrapNodeProps>; const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType<WrapNodeProps>;
const isNodeDraggable = const isNodeDraggable =
typeof isDraggable !== 'undefined' typeof isDraggable !== 'undefined'
@@ -77,39 +76,13 @@ function Node({
typeof node.width !== 'undefined' && typeof node.width !== 'undefined' &&
typeof node.height !== 'undefined'; typeof node.height !== 'undefined';
const { childNodes = [] } = node; const isParentNode = !!node.childNodes?.length;
const childRect = useMemo(() => getRectOfNodes(childNodes), [childNodes]);
const isParentNode = !!childNodes.length;
const style = useMemo(() => {
if (isParentNode) {
return {
...node.style,
width: Math.floor(childRect.width) + 20,
height: Math.floor(childRect.height) + 20,
boxSizing: 'border-box',
} as React.CSSProperties;
}
return node.style;
}, [childRect.width, childRect.height, isParentNode, node.style]);
useEffect(() => {
if (onNodesChange && isParentNode) {
onNodesChange([
{
type: 'position',
id: node.id,
position: { x: Math.floor(childRect.x) - 10, y: Math.floor(childRect.y) - 10 },
},
]);
}
}, [childRect.width, childRect.height, childRect.x, childRect.y, isParentNode]);
return ( return (
<NodeComponent <NodeComponent
id={node.id} id={node.id}
className={node.className} className={node.className}
style={style} style={node.style}
type={nodeType} type={nodeType}
data={node.data} data={node.data}
sourcePosition={node.sourcePosition} sourcePosition={node.sourcePosition}
@@ -117,6 +90,8 @@ function Node({
isHidden={node.isHidden} isHidden={node.isHidden}
xPos={node.position.x} xPos={node.position.x}
yPos={node.position.y} yPos={node.position.y}
width={node.width}
height={node.height}
isDragging={node.isDragging} isDragging={node.isDragging}
isInitialized={isInitialized} isInitialized={isInitialized}
snapGrid={snapGrid} snapGrid={snapGrid}
@@ -139,6 +114,7 @@ function Node({
resizeObserver={resizeObserver} resizeObserver={resizeObserver}
dragHandle={node.dragHandle} dragHandle={node.dragHandle}
zIndex={3 + recursionDepth} zIndex={3 + recursionDepth}
isParentNode={isParentNode}
/> />
); );
} }
+3 -3
View File
@@ -25,7 +25,7 @@ import {
OnNodesChange, OnNodesChange,
OnEdgesChange, OnEdgesChange,
EdgeChange, EdgeChange,
NodePositionChange, NodeDimensionChange,
} from '../types'; } from '../types';
import { isNode, isEdge, getRectOfNodes, getNodesInside, getConnectedEdges } from '../utils/graph'; import { isNode, isEdge, getRectOfNodes, getNodesInside, getConnectedEdges } from '../utils/graph';
import { getHandleBounds } from '../components/Nodes/utils'; import { getHandleBounds } from '../components/Nodes/utils';
@@ -194,9 +194,9 @@ const createStore = () =>
if (matchingNodes?.length) { if (matchingNodes?.length) {
onNodesChange( onNodesChange(
matchingNodes.map((n) => { matchingNodes.map((n) => {
const change: NodePositionChange = { const change: NodeDimensionChange = {
id: n.id, id: n.id,
type: 'position', type: 'dimensions',
isDragging: !!isDragging, isDragging: !!isDragging,
}; };
+1
View File
@@ -95,6 +95,7 @@
user-select: none; user-select: none;
pointer-events: all; pointer-events: all;
transform-origin: 0 0; transform-origin: 0 0;
box-sizing: border-box;
} }
.react-flow__nodesselection { .react-flow__nodesselection {
+7 -7
View File
@@ -12,15 +12,12 @@ export type Transform = [number, number, number];
export type NodeDimensionChange = { export type NodeDimensionChange = {
id: string; id: string;
type: 'dimensions'; type: 'dimensions';
dimensions: Dimensions; dimensions?: Dimensions;
handleBounds?: NodeHandleBounds;
};
export type NodePositionChange = {
id: string;
type: 'position';
position?: XYPosition; position?: XYPosition;
handleBounds?: NodeHandleBounds;
isDragging?: boolean; isDragging?: boolean;
}; };
export type NodeSelectionChange = { export type NodeSelectionChange = {
id: string; id: string;
type: 'select'; type: 'select';
@@ -30,7 +27,7 @@ export type NodeRemoveChange = {
id: string; id: string;
type: 'remove'; type: 'remove';
}; };
export type NodeChange = NodeDimensionChange | NodePositionChange | NodeSelectionChange | NodeRemoveChange; export type NodeChange = NodeDimensionChange | NodeSelectionChange | NodeRemoveChange;
export type EdgeSelectionChange = NodeSelectionChange; export type EdgeSelectionChange = NodeSelectionChange;
export type EdgeRemoveChange = NodeRemoveChange; export type EdgeRemoveChange = NodeRemoveChange;
@@ -277,6 +274,8 @@ export interface WrapNodeProps<T = any> {
scale: number; scale: number;
xPos: number; xPos: number;
yPos: number; yPos: number;
width?: number | null;
height?: number | null;
isSelectable: boolean; isSelectable: boolean;
isDraggable: boolean; isDraggable: boolean;
isConnectable: boolean; isConnectable: boolean;
@@ -302,6 +301,7 @@ export interface WrapNodeProps<T = any> {
resizeObserver: ResizeObserver | null; resizeObserver: ResizeObserver | null;
dragHandle?: string; dragHandle?: string;
zIndex: number; zIndex: number;
isParentNode: boolean;
} }
export type FitViewParams = { export type FitViewParams = {
+10 -5
View File
@@ -301,17 +301,22 @@ function applyChanges(changes: NodeChange[] | EdgeChange[], elements: any[]): an
if (currentChange) { if (currentChange) {
switch (currentChange.type) { switch (currentChange.type) {
case 'dimensions': {
res.push({ ...item, ...currentChange.dimensions, handleBounds: currentChange.handleBounds });
return res;
}
case 'select': { case 'select': {
res.push({ ...item, isSelected: currentChange.isSelected }); res.push({ ...item, isSelected: currentChange.isSelected });
return res; return res;
} }
case 'position': { case 'dimensions': {
const updateItem = { ...item }; const updateItem = { ...item };
if (typeof currentChange.dimensions !== 'undefined') {
updateItem.width = currentChange.dimensions.width;
updateItem.height = currentChange.dimensions.height;
}
if (typeof currentChange.handleBounds !== 'undefined') {
updateItem.handleBounds = currentChange.handleBounds;
}
if (typeof currentChange.position !== 'undefined') { if (typeof currentChange.position !== 'undefined') {
updateItem.position = currentChange.position; updateItem.position = currentChange.position;
} }