From 71f038a319c2c3f3aa60a6b728780618ecbe3ac6 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 14 Dec 2023 12:32:41 +0100 Subject: [PATCH] refactor(node-renderer): simplify selectors, remove reselect, cleanup --- examples/react/src/examples/Stress/index.tsx | 1 - packages/react/package.json | 1 - .../MiniMap/MiniMapNodes.tsx | 30 ++-- .../react/src/components/Nodes/wrapNode.tsx | 10 +- .../NodeRenderer/NodeComponentWrapper.tsx | 104 +++++++++++++ .../src/container/NodeRenderer/index.tsx | 143 +----------------- .../NodeRenderer/useResizeObserver.ts | 44 ++++++ packages/react/src/hooks/useVisibleNodes.ts | 29 ++-- packages/react/src/types/nodes.ts | 7 +- .../components/NodeWrapper/NodeWrapper.svelte | 3 +- packages/system/src/types/nodes.ts | 3 +- pnpm-lock.yaml | 9 -- 12 files changed, 185 insertions(+), 199 deletions(-) create mode 100644 packages/react/src/container/NodeRenderer/NodeComponentWrapper.tsx create mode 100644 packages/react/src/container/NodeRenderer/useResizeObserver.ts diff --git a/examples/react/src/examples/Stress/index.tsx b/examples/react/src/examples/Stress/index.tsx index c1c24d86..9091a687 100644 --- a/examples/react/src/examples/Stress/index.tsx +++ b/examples/react/src/examples/Stress/index.tsx @@ -64,7 +64,6 @@ const StressFlow = () => { minZoom={0.2} fitView > - diff --git a/packages/react/package.json b/packages/react/package.json index d089272d..1c3194d5 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -57,7 +57,6 @@ "d3-drag": "^3.0.0", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0", - "reselect": "^4.1.8", "zustand": "^4.4.0" }, "peerDependencies": { diff --git a/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx b/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx index 0f22de5f..162d0b41 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx @@ -1,30 +1,18 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { ComponentType, memo, MouseEvent } from 'react'; -import { shallow } from 'zustand/shallow'; +import { ComponentType, memo } from 'react'; import { NodeOrigin, getNodePositionWithOrigin } from '@xyflow/system'; +import { shallow } from 'zustand/shallow'; import { useStore } from '../../hooks/useStore'; import type { ReactFlowState } from '../../types'; import MiniMapNode from './MiniMapNode'; import type { MiniMapNodes as MiniMapNodesProps, GetMiniMapNodeAttribute, MiniMapNodeProps } from './types'; -import { createSelector } from 'reselect'; declare const window: any; const selector = (s: ReactFlowState) => s.nodeOrigin; -const selectorNodeIds = createSelector( - [(s: ReactFlowState) => s.nodes], - (nodes) => - nodes - .filter((node) => !node.hidden && (node.computed?.width || node.width) && (node.computed?.height || node.height)) - .map((node) => node.id), - { - memoizeOptions: { - resultEqualityCheck: shallow, - }, - } -); +const selectorNodeIds = (s: ReactFlowState) => s.nodes.map((node) => node.id); const getAttrFunction = (func: any): GetMiniMapNodeAttribute => (func instanceof Function ? func : () => func); function MiniMapNodes({ @@ -38,7 +26,7 @@ function MiniMapNodes({ nodeComponent: NodeComponent = MiniMapNode, onClick, }: MiniMapNodesProps) { - const nodes = useStore(selectorNodeIds); + const nodeIds = useStore(selectorNodeIds, shallow); const nodeOrigin = useStore(selector); const nodeColorFunc = getAttrFunction(nodeColor); const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor); @@ -48,7 +36,7 @@ function MiniMapNodes({ return ( <> - {nodes.map((nodeId) => ( + {nodeIds.map((nodeId) => ( // The split of responsibilities between MiniMapNodes and // NodeComponentWrapper may appear weird. However, it’s designed to // minimize the cost of updates when individual nodes change. @@ -96,16 +84,16 @@ const NodeComponentWrapper = memo(function NodeComponentWrapper({ shapeRendering: string; }) { const node = useStore((s) => s.nodeLookup.get(id)); - if (!node) { + if (!node || node.hidden || !(node.computed?.width || node.width) || !(node.computed?.height || node.height)) { return null; } - const { x, y } = getNodePositionWithOrigin(node, node.origin || nodeOrigin).positionAbsolute; + const positionOrigin = getNodePositionWithOrigin(node, node.origin || nodeOrigin).positionAbsolute; return ( ) => { id, type, data, - xPos, - yPos, xPosOrigin, yPosOrigin, + positionAbsoluteX, + positionAbsoluteY, selected, onClick, onMouseEnter, @@ -52,7 +52,6 @@ export default (NodeComponent: ComponentType) => { disableKeyboardA11y, ariaLabel, rfId, - positionAbsolute, width, height, }: WrapNodeProps) => { @@ -111,7 +110,7 @@ export default (NodeComponent: ComponentType) => { store.setState({ ariaLiveMessage: `Moved selected node ${event.key .replace('Arrow', '') - .toLowerCase()}. New position, x: ${~~xPos}, y: ${~~yPos}`, + .toLowerCase()}. New position, x: ${~~positionAbsoluteX}, y: ${~~positionAbsoluteY}`, }); updatePositions({ @@ -212,7 +211,8 @@ export default (NodeComponent: ComponentType) => { type={type} width={width} height={height} - positionAbsolute={positionAbsolute} + positionAbsoluteX={positionAbsoluteX} + positionAbsoluteY={positionAbsoluteY} selected={selected} isConnectable={isConnectable} sourcePosition={sourcePosition} diff --git a/packages/react/src/container/NodeRenderer/NodeComponentWrapper.tsx b/packages/react/src/container/NodeRenderer/NodeComponentWrapper.tsx new file mode 100644 index 00000000..f833ff08 --- /dev/null +++ b/packages/react/src/container/NodeRenderer/NodeComponentWrapper.tsx @@ -0,0 +1,104 @@ +import { memo, type ComponentType } from 'react'; +import { internalsSymbol, errorMessages, Position, clampPosition, getPositionWithOrigin } from '@xyflow/system'; + +import { useStore } from '../../hooks/useStore'; +import type { ReactFlowState, WrapNodeProps } from '../../types'; +import { NodeRendererProps } from '.'; + +function NodeComponentWrapper(props: { + id: string; + nodeExtent: NodeRendererProps['nodeExtent']; + nodeTypes: NodeRendererProps['nodeTypes']; + nodeOrigin: NodeRendererProps['nodeOrigin']; + onNodeClick: NodeRendererProps['onNodeClick']; + onNodeMouseEnter: NodeRendererProps['onNodeMouseEnter']; + onNodeMouseMove: NodeRendererProps['onNodeMouseMove']; + onNodeMouseLeave: NodeRendererProps['onNodeMouseLeave']; + onNodeContextMenu: NodeRendererProps['onNodeContextMenu']; + onNodeDoubleClick: NodeRendererProps['onNodeDoubleClick']; + noDragClassName: NodeRendererProps['noDragClassName']; + noPanClassName: NodeRendererProps['noPanClassName']; + rfId: NodeRendererProps['rfId']; + disableKeyboardA11y: NodeRendererProps['disableKeyboardA11y']; + resizeObserver: ResizeObserver | null; + nodesDraggable: boolean; + nodesConnectable: boolean; + nodesFocusable: boolean; + elementsSelectable: boolean; + onError: ReactFlowState['onError']; +}) { + const node = useStore((s) => s.nodeLookup.get(props.id)); + if (!node) return null; + + let nodeType = node.type || 'default'; + + if (!props.nodeTypes[nodeType]) { + props.onError?.('003', errorMessages['error003'](nodeType)); + + nodeType = 'default'; + } + + const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType; + const isDraggable = !!(node.draggable || (props.nodesDraggable && typeof node.draggable === 'undefined')); + const isSelectable = !!(node.selectable || (props.elementsSelectable && typeof node.selectable === 'undefined')); + const isConnectable = !!(node.connectable || (props.nodesConnectable && typeof node.connectable === 'undefined')); + const isFocusable = !!(node.focusable || (props.nodesFocusable && typeof node.focusable === 'undefined')); + + const absolutePositionClamped = props.nodeExtent + ? clampPosition(node.computed?.positionAbsolute, props.nodeExtent) + : node.computed?.positionAbsolute || { x: 0, y: 0 }; + + const posOrigin = getPositionWithOrigin({ + x: absolutePositionClamped.x, + y: absolutePositionClamped.y, + width: node.computed?.width ?? node.width ?? 0, + height: node.computed?.height ?? node.height ?? 0, + origin: node.origin || props.nodeOrigin, + }); + const initialized = (!!node.computed?.width && !!node.computed?.height) || (!!node.width && !!node.height); + + return ( +