diff --git a/packages/react/src/components/EdgeWrapper/index.tsx b/packages/react/src/components/EdgeWrapper/index.tsx new file mode 100644 index 00000000..929c87e0 --- /dev/null +++ b/packages/react/src/components/EdgeWrapper/index.tsx @@ -0,0 +1,307 @@ +import { memo, useState, useMemo, useRef, type KeyboardEvent, useCallback } from 'react'; +import cc from 'classcat'; +import { shallow } from 'zustand/shallow'; +import { + getMarkerId, + elementSelectionKeys, + XYHandle, + type Connection, + getEdgePosition, + errorMessages, +} from '@xyflow/system'; + +import { useStoreApi, useStore } from '../../hooks/useStore'; +import { ARIA_EDGE_DESC_KEY } from '../A11yDescriptions'; +import { EdgeAnchor } from '../Edges/EdgeAnchor'; +import { getMouseHandler } from '../Edges/utils'; +import type { EdgeWrapperProps } from '../../types'; +import { builtinEdgeTypes } from './utils'; + +function EdgeWrapper({ + id, + className, + type, + data, + onClick, + onEdgeDoubleClick, + selected, + animated, + label, + labelStyle, + labelShowBg, + labelBgStyle, + labelBgPadding, + labelBgBorderRadius, + style, + source, + target, + isSelectable, + hidden, + sourceHandleId, + targetHandleId, + onContextMenu, + onMouseEnter, + onMouseMove, + onMouseLeave, + edgeUpdaterRadius, + onEdgeUpdate, + onEdgeUpdateStart, + onEdgeUpdateEnd, + markerEnd, + markerStart, + rfId, + ariaLabel, + isFocusable, + isUpdatable, + pathOptions, + interactionWidth, + edgeTypes, + onError, +}: EdgeWrapperProps): JSX.Element | null { + let edgeType = type || 'default'; + let EdgeComponent = edgeTypes?.[edgeType] || builtinEdgeTypes[edgeType]; + + if (EdgeComponent === undefined) { + onError?.('011', errorMessages['error011'](edgeType)); + edgeType = 'default'; + EdgeComponent = builtinEdgeTypes.default; + } + + const edgeRef = useRef(null); + const [updateHover, setUpdateHover] = useState(false); + const [updating, setUpdating] = useState(false); + const store = useStoreApi(); + const edgePosition = useStore( + useCallback( + (state) => { + const sourceNode = state.nodeLookup.get(source); + const targetNode = state.nodeLookup.get(target); + + if (!sourceNode || !targetNode) { + return null; + } + + return getEdgePosition({ + id, + sourceNode, + targetNode, + sourceHandle: sourceHandleId || null, + targetHandle: targetHandleId || null, + connectionMode: state.connectionMode, + onError: state.onError, + }); + }, + [source, target] + ), + shallow + ); + + const markerStartUrl = useMemo(() => `url(#${getMarkerId(markerStart, rfId)})`, [markerStart, rfId]); + const markerEndUrl = useMemo(() => `url(#${getMarkerId(markerEnd, rfId)})`, [markerEnd, rfId]); + + if (hidden || !edgePosition) { + return null; + } + + const onEdgeClick = (event: React.MouseEvent): void => { + const { edges, addSelectedEdges, unselectNodesAndEdges, multiSelectionActive } = store.getState(); + const edge = edges.find((e) => e.id === id); + + if (!edge) { + return; + } + + if (isSelectable) { + store.setState({ nodesSelectionActive: false }); + + if (edge.selected && multiSelectionActive) { + unselectNodesAndEdges({ nodes: [], edges: [edge] }); + edgeRef.current?.blur(); + } else { + addSelectedEdges([id]); + } + } + + if (onClick) { + onClick(event, edge); + } + }; + + const onEdgeDoubleClickHandler = getMouseHandler(id, store.getState, onEdgeDoubleClick); + const onEdgeContextMenu = getMouseHandler(id, store.getState, onContextMenu); + const onEdgeMouseEnter = getMouseHandler(id, store.getState, onMouseEnter); + const onEdgeMouseMove = getMouseHandler(id, store.getState, onMouseMove); + const onEdgeMouseLeave = getMouseHandler(id, store.getState, onMouseLeave); + + const handleEdgeUpdater = (event: React.MouseEvent, isSourceHandle: boolean) => { + // avoid triggering edge updater if mouse btn is not left + if (event.button !== 0) { + return; + } + + const { + autoPanOnConnect, + domNode, + edges, + isValidConnection, + connectionMode, + connectionRadius, + lib, + onConnectStart, + onConnectEnd, + cancelConnection, + nodes, + panBy, + updateConnection, + } = store.getState(); + const nodeId = isSourceHandle ? target : source; + const handleId = (isSourceHandle ? targetHandleId : sourceHandleId) || null; + const handleType = isSourceHandle ? 'target' : 'source'; + + const isTarget = isSourceHandle; + const edge = edges.find((e) => e.id === id)!; + + setUpdating(true); + onEdgeUpdateStart?.(event, edge, handleType); + + const _onEdgeUpdateEnd = (evt: MouseEvent | TouchEvent) => { + setUpdating(false); + onEdgeUpdateEnd?.(evt, edge, handleType); + }; + + const onConnectEdge = (connection: Connection) => onEdgeUpdate?.(edge, connection); + + XYHandle.onPointerDown(event.nativeEvent, { + autoPanOnConnect, + connectionMode, + connectionRadius, + domNode, + handleId, + nodeId, + nodes, + isTarget, + edgeUpdaterType: handleType, + lib, + cancelConnection, + panBy, + isValidConnection, + onConnect: onConnectEdge, + onConnectStart, + onConnectEnd, + onEdgeUpdateEnd: _onEdgeUpdateEnd, + updateConnection, + getTransform: () => store.getState().transform, + }); + }; + + const onEdgeUpdaterSourceMouseDown = (event: React.MouseEvent): void => + handleEdgeUpdater(event, true); + const onEdgeUpdaterTargetMouseDown = (event: React.MouseEvent): void => + handleEdgeUpdater(event, false); + + const onEdgeUpdaterMouseEnter = () => setUpdateHover(true); + const onEdgeUpdaterMouseOut = () => setUpdateHover(false); + + const inactive = !isSelectable && !onClick; + + const onKeyDown = (event: KeyboardEvent) => { + if (elementSelectionKeys.includes(event.key) && isSelectable) { + const { unselectNodesAndEdges, addSelectedEdges, edges } = store.getState(); + const unselect = event.key === 'Escape'; + + if (unselect) { + edgeRef.current?.blur(); + unselectNodesAndEdges({ edges: [edges.find((e) => e.id === id)!] }); + } else { + addSelectedEdges([id]); + } + } + }; + + return ( + + {!updating && ( + + )} + {isUpdatable && ( + <> + {(isUpdatable === 'source' || isUpdatable === true) && ( + + )} + {(isUpdatable === 'target' || isUpdatable === true) && ( + + )} + + )} + + ); +} + +EdgeWrapper.displayName = 'EdgeWrapper'; + +export default memo(EdgeWrapper); diff --git a/packages/react/src/components/EdgeWrapper/utils.ts b/packages/react/src/components/EdgeWrapper/utils.ts new file mode 100644 index 00000000..294592c9 --- /dev/null +++ b/packages/react/src/components/EdgeWrapper/utils.ts @@ -0,0 +1,17 @@ +import type { ComponentType } from 'react'; +import type { EdgeProps, EdgeTypes } from '../../types'; +import { + BezierEdgeInternal, + StraightEdgeInternal, + StepEdgeInternal, + SmoothStepEdgeInternal, + SimpleBezierEdgeInternal, +} from '../Edges'; + +export const builtinEdgeTypes: EdgeTypes = { + default: BezierEdgeInternal as ComponentType, + straight: StraightEdgeInternal as ComponentType, + step: StepEdgeInternal as ComponentType, + smoothstep: SmoothStepEdgeInternal as ComponentType, + simplebezier: SimpleBezierEdgeInternal as ComponentType, +}; diff --git a/packages/react/src/components/Edges/wrapEdge.tsx b/packages/react/src/components/Edges/wrapEdge.tsx deleted file mode 100644 index b0a876b0..00000000 --- a/packages/react/src/components/Edges/wrapEdge.tsx +++ /dev/null @@ -1,290 +0,0 @@ -import { memo, useState, useMemo, useRef, type ComponentType, type KeyboardEvent, useCallback } from 'react'; -import cc from 'classcat'; -import { shallow } from 'zustand/shallow'; -import { getMarkerId, elementSelectionKeys, XYHandle, type Connection, getEdgePosition } from '@xyflow/system'; - -import { useStoreApi, useStore } from '../../hooks/useStore'; -import { ARIA_EDGE_DESC_KEY } from '../A11yDescriptions'; -import { EdgeAnchor } from './EdgeAnchor'; -import { getMouseHandler } from './utils'; -import type { EdgeProps, WrapEdgeProps } from '../../types'; - -export default (EdgeComponent: ComponentType) => { - const EdgeWrapper = ({ - id, - className, - type, - data, - onClick, - onEdgeDoubleClick, - selected, - animated, - label, - labelStyle, - labelShowBg, - labelBgStyle, - labelBgPadding, - labelBgBorderRadius, - style, - source, - target, - isSelectable, - hidden, - sourceHandleId, - targetHandleId, - onContextMenu, - onMouseEnter, - onMouseMove, - onMouseLeave, - edgeUpdaterRadius, - onEdgeUpdate, - onEdgeUpdateStart, - onEdgeUpdateEnd, - markerEnd, - markerStart, - rfId, - ariaLabel, - isFocusable, - isUpdatable, - pathOptions, - interactionWidth, - }: WrapEdgeProps): JSX.Element | null => { - const edgeRef = useRef(null); - const [updateHover, setUpdateHover] = useState(false); - const [updating, setUpdating] = useState(false); - const store = useStoreApi(); - const edgePosition = useStore( - useCallback( - (state) => { - const sourceNode = state.nodeLookup.get(source); - const targetNode = state.nodeLookup.get(target); - - if (!sourceNode || !targetNode) { - return null; - } - - return getEdgePosition({ - id, - sourceNode, - targetNode, - sourceHandle: sourceHandleId || null, - targetHandle: targetHandleId || null, - connectionMode: state.connectionMode, - onError: state.onError, - }); - }, - [source, target] - ), - shallow - ); - - const markerStartUrl = useMemo(() => `url(#${getMarkerId(markerStart, rfId)})`, [markerStart, rfId]); - const markerEndUrl = useMemo(() => `url(#${getMarkerId(markerEnd, rfId)})`, [markerEnd, rfId]); - - if (hidden || !edgePosition) { - return null; - } - - const onEdgeClick = (event: React.MouseEvent): void => { - const { edges, addSelectedEdges, unselectNodesAndEdges, multiSelectionActive } = store.getState(); - const edge = edges.find((e) => e.id === id); - - if (!edge) { - return; - } - - if (isSelectable) { - store.setState({ nodesSelectionActive: false }); - - if (edge.selected && multiSelectionActive) { - unselectNodesAndEdges({ nodes: [], edges: [edge] }); - edgeRef.current?.blur(); - } else { - addSelectedEdges([id]); - } - } - - if (onClick) { - onClick(event, edge); - } - }; - - const onEdgeDoubleClickHandler = getMouseHandler(id, store.getState, onEdgeDoubleClick); - const onEdgeContextMenu = getMouseHandler(id, store.getState, onContextMenu); - const onEdgeMouseEnter = getMouseHandler(id, store.getState, onMouseEnter); - const onEdgeMouseMove = getMouseHandler(id, store.getState, onMouseMove); - const onEdgeMouseLeave = getMouseHandler(id, store.getState, onMouseLeave); - - const handleEdgeUpdater = (event: React.MouseEvent, isSourceHandle: boolean) => { - // avoid triggering edge updater if mouse btn is not left - if (event.button !== 0) { - return; - } - - const { - autoPanOnConnect, - domNode, - edges, - isValidConnection, - connectionMode, - connectionRadius, - lib, - onConnectStart, - onConnectEnd, - cancelConnection, - nodes, - panBy, - updateConnection, - } = store.getState(); - const nodeId = isSourceHandle ? target : source; - const handleId = (isSourceHandle ? targetHandleId : sourceHandleId) || null; - const handleType = isSourceHandle ? 'target' : 'source'; - - const isTarget = isSourceHandle; - const edge = edges.find((e) => e.id === id)!; - - setUpdating(true); - onEdgeUpdateStart?.(event, edge, handleType); - - const _onEdgeUpdateEnd = (evt: MouseEvent | TouchEvent) => { - setUpdating(false); - onEdgeUpdateEnd?.(evt, edge, handleType); - }; - - const onConnectEdge = (connection: Connection) => onEdgeUpdate?.(edge, connection); - - XYHandle.onPointerDown(event.nativeEvent, { - autoPanOnConnect, - connectionMode, - connectionRadius, - domNode, - handleId, - nodeId, - nodes, - isTarget, - edgeUpdaterType: handleType, - lib, - cancelConnection, - panBy, - isValidConnection, - onConnect: onConnectEdge, - onConnectStart, - onConnectEnd, - onEdgeUpdateEnd: _onEdgeUpdateEnd, - updateConnection, - getTransform: () => store.getState().transform, - }); - }; - - const onEdgeUpdaterSourceMouseDown = (event: React.MouseEvent): void => - handleEdgeUpdater(event, true); - const onEdgeUpdaterTargetMouseDown = (event: React.MouseEvent): void => - handleEdgeUpdater(event, false); - - const onEdgeUpdaterMouseEnter = () => setUpdateHover(true); - const onEdgeUpdaterMouseOut = () => setUpdateHover(false); - - const inactive = !isSelectable && !onClick; - - const onKeyDown = (event: KeyboardEvent) => { - if (elementSelectionKeys.includes(event.key) && isSelectable) { - const { unselectNodesAndEdges, addSelectedEdges, edges } = store.getState(); - const unselect = event.key === 'Escape'; - - if (unselect) { - edgeRef.current?.blur(); - unselectNodesAndEdges({ edges: [edges.find((e) => e.id === id)!] }); - } else { - addSelectedEdges([id]); - } - } - }; - - return ( - - {!updating && ( - - )} - {isUpdatable && ( - <> - {(isUpdatable === 'source' || isUpdatable === true) && ( - - )} - {(isUpdatable === 'target' || isUpdatable === true) && ( - - )} - - )} - - ); - }; - - EdgeWrapper.displayName = 'EdgeWrapper'; - - return memo(EdgeWrapper); -}; diff --git a/packages/react/src/components/NodeWrapper/NodeWrapper.tsx b/packages/react/src/components/NodeWrapper/index.tsx similarity index 97% rename from packages/react/src/components/NodeWrapper/NodeWrapper.tsx rename to packages/react/src/components/NodeWrapper/index.tsx index 479e0afe..de7ce1d0 100644 --- a/packages/react/src/components/NodeWrapper/NodeWrapper.tsx +++ b/packages/react/src/components/NodeWrapper/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, memo, type MouseEvent, type KeyboardEvent, ComponentType } from 'react'; +import { useEffect, useRef, memo, type MouseEvent, type KeyboardEvent } from 'react'; import cc from 'classcat'; import { clampPosition, @@ -7,7 +7,6 @@ import { getPositionWithOrigin, internalsSymbol, isInputDOMNode, - NodeProps, } from '@xyflow/system'; import { useStore, useStoreApi } from '../../hooks/useStore'; @@ -17,7 +16,7 @@ import useDrag from '../../hooks/useDrag'; import useUpdateNodePositions from '../../hooks/useUpdateNodePositions'; import { handleNodeClick } from '../Nodes/utils'; import type { NodeWrapperProps } from '../../types'; -import { arrowKeyDiffs } from './utils'; +import { arrowKeyDiffs, builtinNodeTypes } from './utils'; const NodeWrapper = ({ id, @@ -44,10 +43,12 @@ const NodeWrapper = ({ const node = useStore((s) => s.nodeLookup.get(id)!); let nodeType = node.type || 'default'; + let NodeComponent = nodeTypes?.[nodeType] || builtinNodeTypes[nodeType]; - if (!nodeTypes[nodeType]) { + if (NodeComponent === undefined) { onError?.('003', errorMessages['error003'](nodeType)); nodeType = 'default'; + NodeComponent = builtinNodeTypes.default; } const isDraggable = !!(node.draggable || (nodesDraggable && typeof node.draggable === 'undefined')); @@ -105,7 +106,6 @@ const NodeWrapper = ({ return null; } - const NodeComponent = (nodeTypes[nodeType] || nodeTypes.default) as ComponentType; const width = node.width ?? undefined; const height = node.height ?? undefined; const computedWidth = node.computed?.width; diff --git a/packages/react/src/components/NodeWrapper/utils.tsx b/packages/react/src/components/NodeWrapper/utils.tsx index 77842972..6ae5f994 100644 --- a/packages/react/src/components/NodeWrapper/utils.tsx +++ b/packages/react/src/components/NodeWrapper/utils.tsx @@ -1,4 +1,11 @@ -import { XYPosition } from '@xyflow/system'; +import type { ComponentType } from 'react'; +import type { NodeProps, XYPosition } from '@xyflow/system'; + +import InputNode from '../Nodes/InputNode'; +import DefaultNode from '../Nodes/DefaultNode'; +import GroupNode from '../Nodes/GroupNode'; +import OutputNode from '../Nodes/OutputNode'; +import type { NodeTypes } from '../../types'; export const arrowKeyDiffs: Record = { ArrowUp: { x: 0, y: -1 }, @@ -6,3 +13,10 @@ export const arrowKeyDiffs: Record = { ArrowLeft: { x: -1, y: 0 }, ArrowRight: { x: 1, y: 0 }, }; + +export const builtinNodeTypes: NodeTypes = { + input: InputNode as ComponentType, + default: DefaultNode as ComponentType, + output: OutputNode as ComponentType, + group: GroupNode as ComponentType, +}; diff --git a/packages/react/src/container/EdgeRenderer/index.tsx b/packages/react/src/container/EdgeRenderer/index.tsx index 24825fe6..99b53d34 100644 --- a/packages/react/src/container/EdgeRenderer/index.tsx +++ b/packages/react/src/container/EdgeRenderer/index.tsx @@ -1,13 +1,13 @@ import { memo, ReactNode } from 'react'; import { shallow } from 'zustand/shallow'; import cc from 'classcat'; -import { errorMessages } from '@xyflow/system'; import { useStore } from '../../hooks/useStore'; import useVisibleEdges from '../../hooks/useVisibleEdges'; import MarkerDefinitions from './MarkerDefinitions'; import { GraphViewProps } from '../GraphView'; -import type { EdgeTypesWrapped, ReactFlowState } from '../../types'; +import type { ReactFlowState } from '../../types'; +import EdgeWrapper from '../../components/EdgeWrapper'; type EdgeRendererProps = Pick< GraphViewProps, @@ -27,8 +27,8 @@ type EdgeRendererProps = Pick< | 'elevateEdgesOnSelect' | 'rfId' | 'disableKeyboardA11y' + | 'edgeTypes' > & { - edgeTypes: EdgeTypesWrapped; elevateEdgesOnSelect: boolean; children: ReactNode; }; @@ -74,14 +74,6 @@ const EdgeRenderer = ({ {isMaxLevel && } <> {edges.map((edge) => { - let edgeType = edge.type || 'default'; - - if (!edgeTypes[edgeType]) { - onError?.('011', errorMessages['error011'](edgeType)); - edgeType = 'default'; - } - - const EdgeComponent = edgeTypes[edgeType]; const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined')); const isUpdatable = typeof onEdgeUpdate !== 'undefined' && @@ -92,7 +84,7 @@ const EdgeRenderer = ({ ); return ( - ); })} diff --git a/packages/react/src/container/EdgeRenderer/utils.ts b/packages/react/src/container/EdgeRenderer/utils.ts deleted file mode 100644 index 433bb0f0..00000000 --- a/packages/react/src/container/EdgeRenderer/utils.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { ComponentType } from 'react'; - -import { - BezierEdgeInternal, - SmoothStepEdgeInternal, - StepEdgeInternal, - StraightEdgeInternal, - SimpleBezierEdgeInternal, -} from '../../components/Edges'; -import wrapEdge from '../../components/Edges/wrapEdge'; -import type { EdgeProps, EdgeTypes, EdgeTypesWrapped } from '../../types'; - -export type CreateEdgeTypes = (edgeTypes: EdgeTypes) => EdgeTypesWrapped; - -export function createEdgeTypes(edgeTypes: EdgeTypes): EdgeTypesWrapped { - const standardTypes: EdgeTypesWrapped = { - default: wrapEdge((edgeTypes.default || BezierEdgeInternal) as ComponentType), - straight: wrapEdge((edgeTypes.bezier || StraightEdgeInternal) as ComponentType), - step: wrapEdge((edgeTypes.step || StepEdgeInternal) as ComponentType), - smoothstep: wrapEdge((edgeTypes.step || SmoothStepEdgeInternal) as ComponentType), - simplebezier: wrapEdge((edgeTypes.simplebezier || SimpleBezierEdgeInternal) as ComponentType), - }; - - const wrappedTypes = {} as EdgeTypesWrapped; - const specialTypes: EdgeTypesWrapped = Object.keys(edgeTypes) - .filter((k) => !['default', 'bezier'].includes(k)) - .reduce((res, key) => { - res[key] = wrapEdge((edgeTypes[key] || BezierEdgeInternal) as ComponentType); - - return res; - }, wrappedTypes); - - return { - ...standardTypes, - ...specialTypes, - }; -} diff --git a/packages/react/src/container/GraphView/index.tsx b/packages/react/src/container/GraphView/index.tsx index 014d754f..116b4d26 100644 --- a/packages/react/src/container/GraphView/index.tsx +++ b/packages/react/src/container/GraphView/index.tsx @@ -8,19 +8,15 @@ import useOnInitHandler from '../../hooks/useOnInitHandler'; import useViewportSync from '../../hooks/useViewportSync'; import ConnectionLine from '../../components/ConnectionLine'; import type { ReactFlowProps } from '../../types'; -import { createNodeTypes } from '../NodeRenderer/utils'; -import { createEdgeTypes } from '../EdgeRenderer/utils'; -import { useNodeOrEdgeTypes } from './utils'; +import useNodeOrEdgeTypesWarning from './useNodeOrEdgeTypesWarning'; export type GraphViewProps = Omit< ReactFlowProps, - 'onSelectionChange' | 'nodes' | 'edges' | 'nodeTypes' | 'edgeTypes' | 'onMove' | 'onMoveStart' | 'onMoveEnd' + 'onSelectionChange' | 'nodes' | 'edges' | 'onMove' | 'onMoveStart' | 'onMoveEnd' > & Required< Pick< ReactFlowProps, - | 'nodeTypes' - | 'edgeTypes' | 'selectionKeyCode' | 'deleteKeyCode' | 'multiSelectionKeyCode' @@ -108,8 +104,8 @@ const GraphView = ({ viewport, onViewportChange, }: GraphViewProps) => { - const nodeTypesWrapped = useNodeOrEdgeTypes(nodeTypes, createNodeTypes); - const edgeTypesWrapped = useNodeOrEdgeTypes(edgeTypes, createEdgeTypes); + useNodeOrEdgeTypesWarning(nodeTypes); + useNodeOrEdgeTypesWarning(edgeTypes); useOnInitHandler(onInit); useViewportSync(viewport); @@ -154,7 +150,7 @@ const GraphView = ({ > { + if (process.env.NODE_ENV === 'development') { + if (updateCount.current > 1) { + store.getState().onError?.('002', errorMessages['error002']()); + } + updateCount.current += 1; + } + }, [nodeOrEdgeTypes]); +} diff --git a/packages/react/src/container/GraphView/utils.ts b/packages/react/src/container/GraphView/utils.ts deleted file mode 100644 index 15dbaafe..00000000 --- a/packages/react/src/container/GraphView/utils.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useMemo, useRef } from 'react'; -import { shallow } from 'zustand/shallow'; -import { errorMessages } from '@xyflow/system'; - -import { CreateEdgeTypes } from '../EdgeRenderer/utils'; -import { CreateNodeTypes } from '../NodeRenderer/utils'; -import type { EdgeTypes, EdgeTypesWrapped, NodeTypes, NodeTypesWrapped } from '../../types'; -import { useStoreApi } from '../../hooks/useStore'; - -export function useNodeOrEdgeTypes(nodeOrEdgeTypes: NodeTypes, createTypes: CreateNodeTypes): NodeTypesWrapped; -export function useNodeOrEdgeTypes(nodeOrEdgeTypes: EdgeTypes, createTypes: CreateEdgeTypes): EdgeTypesWrapped; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function useNodeOrEdgeTypes(nodeOrEdgeTypes: any, createTypes: any): any { - const typesKeysRef = useRef(null); - const store = useStoreApi(); - - const typesParsed = useMemo(() => { - if (process.env.NODE_ENV === 'development') { - const typeKeys = Object.keys(nodeOrEdgeTypes); - - if (shallow(typesKeysRef.current, typeKeys)) { - store.getState().onError?.('002', errorMessages['error002']()); - } - - typesKeysRef.current = typeKeys; - } - return createTypes(nodeOrEdgeTypes); - }, [nodeOrEdgeTypes]); - - return typesParsed; -} diff --git a/packages/react/src/container/NodeRenderer/index.tsx b/packages/react/src/container/NodeRenderer/index.tsx index 83ff6b54..fa877984 100644 --- a/packages/react/src/container/NodeRenderer/index.tsx +++ b/packages/react/src/container/NodeRenderer/index.tsx @@ -7,7 +7,7 @@ import { containerStyle } from '../../styles/utils'; import { GraphViewProps } from '../GraphView'; import type { ReactFlowState } from '../../types'; import useResizeObserver from './useResizeObserver'; -import NodeWrapper from '../../components/NodeWrapper/NodeWrapper'; +import NodeWrapper from '../../components/NodeWrapper'; export type NodeRendererProps = Pick< GraphViewProps, diff --git a/packages/react/src/container/NodeRenderer/utils.ts b/packages/react/src/container/NodeRenderer/utils.ts deleted file mode 100644 index e39190d1..00000000 --- a/packages/react/src/container/NodeRenderer/utils.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { ComponentType } from 'react'; -import type { NodeProps } from '@xyflow/system'; - -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 type { NodeTypes } from '../../types'; - -export type CreateNodeTypes = (nodeTypes: NodeTypes) => NodeTypes; - -export function createNodeTypes(nodeTypes: NodeTypes): NodeTypes { - const builtinTypes: NodeTypes = { - input: (nodeTypes.input || InputNode) as ComponentType, - default: (nodeTypes.default || DefaultNode) as ComponentType, - output: (nodeTypes.output || OutputNode) as ComponentType, - group: (nodeTypes.group || GroupNode) as ComponentType, - }; - - const userProvidedTypes = Object.keys(nodeTypes) - .filter((k) => !['input', 'default', 'output', 'group'].includes(k)) - .reduce((res, key) => { - res[key] = (nodeTypes[key] || DefaultNode) as ComponentType; - - return res; - }, {}); - - return { - ...builtinTypes, - ...userProvidedTypes, - }; -} diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx index a7360a46..3adacfeb 100644 --- a/packages/react/src/container/ReactFlow/index.tsx +++ b/packages/react/src/container/ReactFlow/index.tsx @@ -12,40 +12,15 @@ import { } from '@xyflow/system'; import Attribution from '../../components/Attribution'; -import { - BezierEdgeInternal, - SmoothStepEdgeInternal, - StepEdgeInternal, - StraightEdgeInternal, - SimpleBezierEdgeInternal, -} from '../../components/Edges'; -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 SelectionListener from '../../components/SelectionListener'; import StoreUpdater from '../../components/StoreUpdater'; import A11yDescriptions from '../../components/A11yDescriptions'; import GraphView from '../GraphView'; import Wrapper from './Wrapper'; -import type { EdgeTypes, NodeTypes, ReactFlowProps, ReactFlowRefType } from '../../types'; +import type { ReactFlowProps, ReactFlowRefType } from '../../types'; import useColorModeClass from '../../hooks/useColorModeClass'; -const defaultNodeTypes: NodeTypes = { - input: InputNode, - default: DefaultNode, - output: OutputNode, - group: GroupNode, -}; - -const defaultEdgeTypes: EdgeTypes = { - default: BezierEdgeInternal, - straight: StraightEdgeInternal, - step: StepEdgeInternal, - smoothstep: SmoothStepEdgeInternal, - simplebezier: SimpleBezierEdgeInternal, -}; - const initNodeOrigin: NodeOrigin = [0, 0]; const initSnapGrid: [number, number] = [15, 15]; const initDefaultViewport: Viewport = { x: 0, y: 0, zoom: 1 }; @@ -66,8 +41,8 @@ const ReactFlow = forwardRef( defaultNodes, defaultEdges, className, - nodeTypes = defaultNodeTypes, - edgeTypes = defaultEdgeTypes, + nodeTypes, + edgeTypes, onNodeClick, onEdgeClick, onInit, diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index 287c277d..5af61d1e 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -15,9 +15,10 @@ import type { EdgePosition, Optional, StepPathOptions, + OnError, } from '@xyflow/system'; -import { Node } from '.'; +import { EdgeTypes, Node } from '.'; export type EdgeLabelOptions = { label?: string | ReactNode; @@ -57,7 +58,7 @@ export type Edge = DefaultEdge | SmoothStepEdgeType | BezierEdgeT export type EdgeMouseHandler = (event: ReactMouseEvent, edge: Edge) => void; -export type WrapEdgeProps = Omit, 'sourceHandle' | 'targetHandle'> & { +export type EdgeWrapperProps = Omit, 'sourceHandle' | 'targetHandle'> & { onClick?: EdgeMouseHandler; onEdgeDoubleClick?: EdgeMouseHandler; sourceHandleId?: string | null; @@ -75,6 +76,8 @@ export type WrapEdgeProps = Omit, 'sourceHandle' | 'targetHandl isUpdatable: EdgeUpdatable; isSelectable: boolean; pathOptions?: BezierPathOptions | SmoothStepPathOptions; + edgeTypes?: EdgeTypes; + onError?: OnError; }; export type DefaultEdgeOptions = DefaultEdgeOptionsBase; @@ -90,7 +93,7 @@ export type EdgeProps = Pick< Edge, 'id' | 'animated' | 'data' | 'style' | 'selected' | 'source' | 'target' > & - Pick & + Pick & EdgePosition & EdgeLabelOptions & { markerStart?: string; diff --git a/packages/react/src/types/general.ts b/packages/react/src/types/general.ts index b2e7098a..96859d96 100644 --- a/packages/react/src/types/general.ts +++ b/packages/react/src/types/general.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { ComponentType, MemoExoticComponent } from 'react'; +import type { ComponentType } from 'react'; import { FitViewParamsBase, FitViewOptionsBase, @@ -14,7 +14,7 @@ import { XYPosition, } from '@xyflow/system'; -import type { NodeChange, EdgeChange, Node, WrapNodeProps, Edge, EdgeProps, WrapEdgeProps, ReactFlowInstance } from '.'; +import type { NodeChange, EdgeChange, Node, Edge, EdgeProps, ReactFlowInstance } from '.'; export type OnNodesChange = (changes: NodeChange[]) => void; export type OnEdgesChange = (changes: EdgeChange[]) => void; @@ -24,9 +24,7 @@ export type OnEdgesDelete = (edges: Edge[]) => void; export type OnDelete = (params: { nodes: Node[]; edges: Edge[] }) => void; export type NodeTypes = { [key: string]: ComponentType }; -export type NodeTypesWrapped = { [key: string]: MemoExoticComponent> }; export type EdgeTypes = { [key: string]: ComponentType }; -export type EdgeTypesWrapped = { [key: string]: MemoExoticComponent> }; export type UnselectNodesAndEdgesParams = { nodes?: Node[]; diff --git a/packages/react/src/types/nodes.ts b/packages/react/src/types/nodes.ts index 03f02930..00c87704 100644 --- a/packages/react/src/types/nodes.ts +++ b/packages/react/src/types/nodes.ts @@ -32,7 +32,7 @@ export type NodeWrapperProps = { noPanClassName: string; rfId: string; disableKeyboardA11y: boolean; - nodeTypes: NodeTypes; + nodeTypes?: NodeTypes; nodeExtent?: CoordinateExtent; nodeOrigin: NodeOrigin; onError?: OnError;