refactor(nodes): use NodeWrapper instead of wrapped node types

This commit is contained in:
moklick
2023-12-15 15:21:15 +01:00
parent 9b128bf184
commit b861b47783
7 changed files with 314 additions and 409 deletions
@@ -1,104 +0,0 @@
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<WrapNodeProps>;
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 (
<NodeComponent
key={node.id}
id={node.id}
className={node.className}
style={node.style}
width={node.width ?? undefined}
height={node.height ?? undefined}
type={nodeType}
data={node.data}
sourcePosition={node.sourcePosition || Position.Bottom}
targetPosition={node.targetPosition || Position.Top}
hidden={node.hidden}
xPosOrigin={posOrigin.x}
yPosOrigin={posOrigin.y}
positionAbsoluteX={absolutePositionClamped.x}
positionAbsoluteY={absolutePositionClamped.y}
onClick={props.onNodeClick}
onMouseEnter={props.onNodeMouseEnter}
onMouseMove={props.onNodeMouseMove}
onMouseLeave={props.onNodeMouseLeave}
onContextMenu={props.onNodeContextMenu}
onDoubleClick={props.onNodeDoubleClick}
selected={!!node.selected}
isDraggable={isDraggable}
isSelectable={isSelectable}
isConnectable={isConnectable}
isFocusable={isFocusable}
resizeObserver={props.resizeObserver}
dragHandle={node.dragHandle}
zIndex={node[internalsSymbol]?.z ?? 0}
isParent={!!node[internalsSymbol]?.isParent}
noDragClassName={props.noDragClassName}
noPanClassName={props.noPanClassName}
initialized={initialized}
rfId={props.rfId}
disableKeyboardA11y={props.disableKeyboardA11y}
ariaLabel={node.ariaLabel}
/>
);
}
NodeComponentWrapper.displayName = 'NodeComponentWrapper';
export default memo(NodeComponentWrapper);
@@ -5,9 +5,9 @@ import useVisibleNodesIds from '../../hooks/useVisibleNodes';
import { useStore } from '../../hooks/useStore';
import { containerStyle } from '../../styles/utils';
import { GraphViewProps } from '../GraphView';
import type { NodeTypesWrapped, ReactFlowState } from '../../types';
import NodeComponentWrapper from './NodeComponentWrapper';
import type { ReactFlowState } from '../../types';
import useResizeObserver from './useResizeObserver';
import NodeWrapper from '../../components/Nodes/NodeWrapper';
export type NodeRendererProps = Pick<
GraphViewProps,
@@ -24,9 +24,8 @@ export type NodeRendererProps = Pick<
| 'disableKeyboardA11y'
| 'nodeOrigin'
| 'nodeExtent'
> & {
nodeTypes: NodeTypesWrapped;
};
| 'nodeTypes'
>;
const selector = (s: ReactFlowState) => ({
nodesDraggable: s.nodesDraggable,
@@ -68,18 +67,18 @@ const NodeRenderer = (props: NodeRendererProps) => {
// moved into `NodeComponentWrapper`. This ensures they are
// memorized so if `NodeRenderer` *has* to rerender, it only
// needs to regenerate the list of nodes, nothing else.
<NodeComponentWrapper
<NodeWrapper
key={nodeId}
id={nodeId}
nodeTypes={props.nodeTypes}
nodeExtent={props.nodeExtent}
nodeOrigin={props.nodeOrigin}
onNodeClick={props.onNodeClick}
onNodeMouseEnter={props.onNodeMouseEnter}
onNodeMouseMove={props.onNodeMouseMove}
onNodeMouseLeave={props.onNodeMouseLeave}
onNodeContextMenu={props.onNodeContextMenu}
onNodeDoubleClick={props.onNodeDoubleClick}
onClick={props.onNodeClick}
onMouseEnter={props.onNodeMouseEnter}
onMouseMove={props.onNodeMouseMove}
onMouseLeave={props.onNodeMouseLeave}
onContextMenu={props.onNodeContextMenu}
onDoubleClick={props.onNodeDoubleClick}
noDragClassName={props.noDragClassName}
noPanClassName={props.noPanClassName}
rfId={props.rfId}
@@ -5,30 +5,28 @@ import DefaultNode from '../../components/Nodes/DefaultNode';
import InputNode from '../../components/Nodes/InputNode';
import OutputNode from '../../components/Nodes/OutputNode';
import GroupNode from '../../components/Nodes/GroupNode';
import wrapNode from '../../components/Nodes/wrapNode';
import type { NodeTypes, NodeTypesWrapped } from '../../types';
import type { NodeTypes } from '../../types';
export type CreateNodeTypes = (nodeTypes: NodeTypes) => NodeTypesWrapped;
export type CreateNodeTypes = (nodeTypes: NodeTypes) => NodeTypes;
export function createNodeTypes(nodeTypes: NodeTypes): NodeTypesWrapped {
const standardTypes: NodeTypesWrapped = {
input: wrapNode((nodeTypes.input || InputNode) as ComponentType<NodeProps>),
default: wrapNode((nodeTypes.default || DefaultNode) as ComponentType<NodeProps>),
output: wrapNode((nodeTypes.output || OutputNode) as ComponentType<NodeProps>),
group: wrapNode((nodeTypes.group || GroupNode) as ComponentType<NodeProps>),
export function createNodeTypes(nodeTypes: NodeTypes): NodeTypes {
const builtinTypes: NodeTypes = {
input: (nodeTypes.input || InputNode) as ComponentType<NodeProps>,
default: (nodeTypes.default || DefaultNode) as ComponentType<NodeProps>,
output: (nodeTypes.output || OutputNode) as ComponentType<NodeProps>,
group: (nodeTypes.group || GroupNode) as ComponentType<NodeProps>,
};
const wrappedTypes = {} as NodeTypesWrapped;
const specialTypes: NodeTypesWrapped = Object.keys(nodeTypes)
const userProvidedTypes = Object.keys(nodeTypes)
.filter((k) => !['input', 'default', 'output', 'group'].includes(k))
.reduce((res, key) => {
res[key] = wrapNode((nodeTypes[key] || DefaultNode) as ComponentType<NodeProps>);
.reduce<NodeTypes>((res, key) => {
res[key] = (nodeTypes[key] || DefaultNode) as ComponentType<NodeProps>;
return res;
}, wrappedTypes);
}, {});
return {
...standardTypes,
...specialTypes,
...builtinTypes,
...userProvidedTypes,
};
}