diff --git a/example/src_oldapi/Overview/index.tsx b/example/src/Overview/index.tsx similarity index 86% rename from example/src_oldapi/Overview/index.tsx rename to example/src/Overview/index.tsx index 17e0c9b5..4cb1e045 100644 --- a/example/src_oldapi/Overview/index.tsx +++ b/example/src/Overview/index.tsx @@ -1,20 +1,18 @@ -import React, { useState, MouseEvent, CSSProperties } from 'react'; - +import { MouseEvent, CSSProperties } from 'react'; import ReactFlow, { - removeElements, addEdge, MiniMap, Controls, Background, - isNode, Node, - Elements, - FlowElement, - OnLoadParams, FlowTransform, SnapGrid, Connection, Edge, + ReactFlowInstance, + useNodesState, + useEdgesState, + OnSelectionChangeParams, } from 'react-flow-renderer'; const onNodeDragStart = (_: MouseEvent, node: Node) => console.log('drag start', node); @@ -31,10 +29,10 @@ const onSelectionContextMenu = (event: MouseEvent, nodes: Node[]) => { event.preventDefault(); console.log('selection context menu', nodes); }; -const onElementClick = (_: MouseEvent, element: FlowElement) => - console.log(`${isNode(element) ? 'node' : 'edge'} click:`, element); -const onSelectionChange = (elements: Elements | null) => console.log('selection change', elements); -const onLoad = (reactFlowInstance: OnLoadParams) => { +const onNodeClick = (_: MouseEvent, node: Node) => console.log('node click:', node); + +const onSelectionChange = ({ nodes, edges }: OnSelectionChangeParams) => console.log('selection change', nodes, edges); +const onPaneReady = (reactFlowInstance: ReactFlowInstance) => { console.log('flow loaded:', reactFlowInstance); reactFlowInstance.fitView(); }; @@ -47,7 +45,7 @@ const onEdgeMouseMove = (_: MouseEvent, edge: Edge) => console.log('edge mouse m const onEdgeMouseLeave = (_: MouseEvent, edge: Edge) => console.log('edge mouse leave', edge); const onEdgeDoubleClick = (_: MouseEvent, edge: Edge) => console.log('edge double click', edge); -const initialElements: Elements = [ +const initialNodes: Node[] = [ { id: '1', type: 'input', @@ -121,6 +119,9 @@ const initialElements: Elements = [ position: { x: 100, y: 480 }, }, { id: '7', type: 'output', data: { label: 'Another output node' }, position: { x: 400, y: 450 } }, +]; + +const initialEdges: Edge[] = [ { id: 'e1-2', source: '1', target: '2', label: 'this is an edge label' }, { id: 'e1-3', source: '1', target: '3' }, { id: 'e3-4', source: '3', target: '4', animated: true, label: 'animated edge' }, @@ -157,15 +158,17 @@ const nodeColor = (n: Node): string => { }; const OverviewFlow = () => { - const [elements, setElements] = useState(initialElements); - const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); - const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); + const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + const onConnect = (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)); return ( { onSelectionChange={onSelectionChange} onMoveStart={onMoveStart} onMoveEnd={onMoveEnd} - onLoad={onLoad} + onPaneReady={onPaneReady} connectionLineStyle={connectionLineStyle} snapToGrid={true} snapGrid={snapGrid} diff --git a/example/src/TouchDevice/index.tsx b/example/src/TouchDevice/index.tsx new file mode 100644 index 00000000..1a75e7d4 --- /dev/null +++ b/example/src/TouchDevice/index.tsx @@ -0,0 +1,50 @@ +import { useCallback } from 'react'; +import ReactFlow, { + Node, + Edge, + useNodesState, + useEdgesState, + Position, + Connection, + addEdge, +} from 'react-flow-renderer'; + +import './touch-device.css'; + +const initialNodes: Node[] = [ + { + id: '1', + data: { label: 'Node 1' }, + position: { x: 100, y: 100 }, + sourcePosition: Position.Right, + targetPosition: Position.Left, + }, + { + id: '2', + data: { label: 'Node 2' }, + position: { x: 300, y: 100 }, + sourcePosition: Position.Right, + targetPosition: Position.Left, + }, +]; + +const initialEdges: Edge[] = []; + +const TouchDeviceFlow = () => { + const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + const onConnect = useCallback((connection: Connection) => setEdges((eds) => addEdge(connection, eds)), []); + + return ( + + ); +}; + +export default TouchDeviceFlow; diff --git a/example/src/TouchDevice/touch-device.css b/example/src/TouchDevice/touch-device.css new file mode 100644 index 00000000..ac3f372d --- /dev/null +++ b/example/src/TouchDevice/touch-device.css @@ -0,0 +1,19 @@ +.touchdevice-flow .react-flow__handle { + width: 20px; + height: 20px; + border-radius: 3px; + background-color: #9f7aea; +} + +.touchdevice-flow .react-flow__handle.connecting { + animation: bounce 1600ms infinite ease-out; +} + +@keyframes bounce { + 0% { + transform: translate(0, -50%) scale(1); + } + 50% { + transform: translate(0, -50%) scale(1.1); + } +} diff --git a/example/src/index.tsx b/example/src/index.tsx index a4e81a87..5588eee0 100644 --- a/example/src/index.tsx +++ b/example/src/index.tsx @@ -2,6 +2,7 @@ import { ChangeEvent } from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter as Router, Route, Switch, withRouter } from 'react-router-dom'; +import Overview from './Overview'; import Basic from './Basic'; import UpdateNode from './UpdateNode'; import Stress from './Stress'; @@ -11,12 +12,17 @@ import Layouting from './Layouting'; import NestedNodes from './NestedNodes'; import Hidden from './Hidden'; import UpdatableEdge from './UpdatableEdge'; +import TouchDevice from './TouchDevice'; import './index.css'; const routes = [ { path: '/', + component: Overview, + }, + { + path: '/basic', component: Basic, }, { @@ -51,6 +57,10 @@ const routes = [ path: '/updatable-edge', component: UpdatableEdge, }, + { + path: '/touch-device', + component: TouchDevice, + }, ]; const Header = withRouter(({ history, location }) => { diff --git a/example/src_oldapi/Basic/index.tsx b/example/src_oldapi/Basic/index.tsx deleted file mode 100644 index 19523f9b..00000000 --- a/example/src_oldapi/Basic/index.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React, { useState, MouseEvent } from 'react'; - -import ReactFlow, { - removeElements, - addEdge, - isNode, - Background, - Elements, - BackgroundVariant, - FlowElement, - Node, - Edge, - Connection, - OnLoadParams, -} from 'react-flow-renderer'; - -const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); -const onElementClick = (_: MouseEvent, element: FlowElement) => console.log('click', element); - -const initialNodes: Node[] = [ - { id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 }, className: 'light' }, - { id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 }, className: 'light' }, - { id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 }, className: 'light' }, - { id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 }, className: 'light' }, -]; - -const initialEdges: Edge[] = [ - { id: 'e1-2', source: '1', target: '2', animated: true }, - { id: 'e1-3', source: '1', target: '3' }, -]; - -const BasicFlow = () => { - const [rfInstance, setRfInstance] = useState(null); - const [nodes, setNodes] = useState(initialNodes); - const [edges, setEdges] = useState(initialEdges); - // const onElementsRemove = (elementsToRemove: Elements) => setNodes((els) => removeElements(elementsToRemove, els)); - // const onConnect = (params: Edge | Connection) => setNodes((els) => addEdge(params, els)); - const onLoad = (reactFlowInstance: OnLoadParams) => setRfInstance(reactFlowInstance); - - const updatePos = () => { - setNodes((nds) => { - return nds.map((n) => { - n.position = { - x: Math.random() * 400, - y: Math.random() * 400, - }; - - return n; - }); - }); - }; - - const logToObject = () => console.log(rfInstance?.toObject()); - const resetTransform = () => rfInstance?.setTransform({ x: 0, y: 0, zoom: 1 }); - - const toggleClassnames = () => { - setNodes((nds) => { - return nds.map((n) => { - n.className = n.className === 'light' ? 'dark' : 'light'; - - return n; - }); - }); - }; - - return ( - - - -
- - - - -
-
- ); -}; - -export default BasicFlow; diff --git a/example/src_oldapi/CustomNode/ColorSelectorNode.tsx b/example/src_oldapi/CustomNode/ColorSelectorNode.tsx deleted file mode 100644 index 7ad20340..00000000 --- a/example/src_oldapi/CustomNode/ColorSelectorNode.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React, { memo, FC, CSSProperties } from 'react'; - -import { Handle, Position, NodeProps, Connection, Edge } from 'react-flow-renderer'; - -const targetHandleStyle: CSSProperties = { background: '#555' }; -const sourceHandleStyleA: CSSProperties = { ...targetHandleStyle, top: 10 }; -const sourceHandleStyleB: CSSProperties = { ...targetHandleStyle, bottom: 10, top: 'auto' }; - -const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params); - -const ColorSelectorNode: FC = ({ data, isConnectable }) => { - return ( - <> - -
- Custom Color Picker Node: {data.color} -
- - - - - ); -}; - -export default memo(ColorSelectorNode); diff --git a/example/src_oldapi/CustomNode/index.tsx b/example/src_oldapi/CustomNode/index.tsx deleted file mode 100644 index 804aabea..00000000 --- a/example/src_oldapi/CustomNode/index.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import React, { useState, useEffect, MouseEvent } from 'react'; -import { ChangeEvent } from 'react'; - -import ReactFlow, { - isEdge, - removeElements, - addEdge, - MiniMap, - Controls, - Node, - FlowElement, - OnLoadParams, - Elements, - Position, - SnapGrid, - Connection, - Edge, -} from 'react-flow-renderer'; - -import ColorSelectorNode from './ColorSelectorNode'; - -const onLoad = (reactFlowInstance: OnLoadParams) => console.log('flow loaded:', reactFlowInstance); -const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); -const onElementClick = (_: MouseEvent, element: FlowElement) => console.log('click', element); - -const initBgColor = '#1A192B'; - -const connectionLineStyle = { stroke: '#fff' }; -const snapGrid: SnapGrid = [16, 16]; -const nodeTypes = { - selectorNode: ColorSelectorNode, -}; - -const CustomNodeFlow = () => { - const [elements, setElements] = useState([]); - const [bgColor, setBgColor] = useState(initBgColor); - - useEffect(() => { - const onChange = (event: ChangeEvent) => { - setElements((els) => - els.map((e) => { - if (isEdge(e) || e.id !== '2') { - return e; - } - - const color = event.target.value; - - setBgColor(color); - - return { - ...e, - data: { - ...e.data, - color, - }, - }; - }) - ); - }; - - setElements([ - { - id: '1', - type: 'input', - data: { label: 'An input node' }, - position: { x: 0, y: 50 }, - sourcePosition: Position.Right, - }, - { - id: '2', - type: 'selectorNode', - data: { onChange: onChange, color: initBgColor }, - style: { border: '1px solid #777', padding: 10 }, - position: { x: 250, y: 50 }, - }, - { - id: '3', - type: 'output', - data: { label: 'Output A' }, - position: { x: 550, y: 25 }, - targetPosition: Position.Left, - }, - { - id: '4', - type: 'output', - data: { label: 'Output B' }, - position: { x: 550, y: 100 }, - targetPosition: Position.Left, - }, - - { id: 'e1-2', source: '1', target: '2', animated: true, style: { stroke: '#fff' } }, - { id: 'e2a-3', source: '2', sourceHandle: 'a', target: '3', animated: true, style: { stroke: '#fff' } }, - { id: 'e2b-4', source: '2', sourceHandle: 'b', target: '4', animated: true, style: { stroke: '#fff' } }, - ]); - }, []); - - const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); - const onConnect = (params: Connection | Edge) => - setElements((els) => addEdge({ ...params, animated: true, style: { stroke: '#fff' } }, els)); - - return ( - - { - if (n.type === 'input') return '#0041d0'; - if (n.type === 'selectorNode') return bgColor; - if (n.type === 'output') return '#ff0072'; - - return '#eee'; - }} - nodeColor={(n: Node): string => { - if (n.type === 'selectorNode') return bgColor; - - return '#fff'; - }} - /> - - - ); -}; - -export default CustomNodeFlow; diff --git a/example/src_oldapi/Stress/index.tsx b/example/src_oldapi/Stress/index.tsx deleted file mode 100644 index b734c6b6..00000000 --- a/example/src_oldapi/Stress/index.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React, { useState, CSSProperties } from 'react'; -import ReactFlow, { - removeElements, - addEdge, - MiniMap, - isNode, - Controls, - Background, - OnLoadParams, - Elements, - Connection, - Edge, -} from 'react-flow-renderer'; - -import { getElements } from './utils'; - -const buttonWrapperStyles: CSSProperties = { position: 'absolute', right: 10, top: 10, zIndex: 4 }; - -const onLoad = (reactFlowInstance: OnLoadParams) => { - reactFlowInstance.fitView(); - console.log(reactFlowInstance.getElements()); -}; - -const initialElements: Elements = getElements(30, 30); - -const StressFlow = () => { - const [elements, setElements] = useState(initialElements); - const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); - const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); - - const updatePos = () => { - setElements((elms) => { - return elms.map((el) => { - if (isNode(el)) { - return { - ...el, - position: { - x: Math.random() * window.innerWidth, - y: Math.random() * window.innerHeight, - }, - }; - } - - return el; - }); - }); - }; - - const updateElements = () => { - const grid = Math.ceil(Math.random() * 10); - setElements(getElements(grid, grid)); - }; - - return ( - - - - - -
- - -
-
- ); -}; - -export default StressFlow; diff --git a/example/src_oldapi/Stress/utils.ts b/example/src_oldapi/Stress/utils.ts deleted file mode 100644 index a37eb8de..00000000 --- a/example/src_oldapi/Stress/utils.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Elements } from 'react-flow-renderer'; - -export function getElements(xElements: number = 10, yElements: number = 10): Elements { - const initialElements = []; - let nodeId = 1; - let recentNodeId = null; - - for (let y = 0; y < yElements; y++) { - for (let x = 0; x < xElements; x++) { - const position = { x: x * 100, y: y * 50 }; - const data = { label: `Node ${nodeId}` }; - const node = { - id: nodeId.toString(), - style: { width: 50, fontSize: 11 }, - data, - position, - }; - initialElements.push(node); - - if (recentNodeId && nodeId <= xElements * yElements) { - initialElements.push({ id: `${x}-${y}`, source: recentNodeId.toString(), target: nodeId.toString() }); - } - - recentNodeId = nodeId; - nodeId++; - } - } - - return initialElements; -} diff --git a/package-lock.json b/package-lock.json index 3a6cf159..cd221c85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@babel/preset-env": "^7.16.4", "@babel/preset-react": "^7.16.0", "@babel/preset-typescript": "^7.16.0", + "@rollup/plugin-alias": "^3.1.8", "@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-commonjs": "^21.0.1", "@rollup/plugin-node-resolve": "^13.0.6", @@ -2103,6 +2104,21 @@ "@octokit/openapi-types": "^11.2.0" } }, + "node_modules/@rollup/plugin-alias": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-3.1.8.tgz", + "integrity": "sha512-tf7HeSs/06wO2LPqKNY3Ckbvy0JRe7Jyn98bXnt/gfrxbe+AJucoNJlsEVi9sdgbQtXemjbakCpO/76JVgnHpA==", + "dev": true, + "dependencies": { + "slash": "^3.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz", @@ -12374,6 +12390,15 @@ "@octokit/openapi-types": "^11.2.0" } }, + "@rollup/plugin-alias": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-3.1.8.tgz", + "integrity": "sha512-tf7HeSs/06wO2LPqKNY3Ckbvy0JRe7Jyn98bXnt/gfrxbe+AJucoNJlsEVi9sdgbQtXemjbakCpO/76JVgnHpA==", + "dev": true, + "requires": { + "slash": "^3.0.0" + } + }, "@rollup/plugin-babel": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz", diff --git a/package.json b/package.json index 891be9e2..aad3ca3d 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@babel/preset-env": "^7.16.4", "@babel/preset-react": "^7.16.0", "@babel/preset-typescript": "^7.16.0", + "@rollup/plugin-alias": "^3.1.8", "@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-commonjs": "^21.0.1", "@rollup/plugin-node-resolve": "^13.0.6", diff --git a/rollup.config.js b/rollup.config.js index 844f2252..23a1f62b 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -4,6 +4,7 @@ import babel from '@rollup/plugin-babel'; import postcss from 'rollup-plugin-postcss'; import bundleSize from 'rollup-plugin-bundle-size'; import replace from '@rollup/plugin-replace'; +import alias from '@rollup/plugin-alias'; import svgr from '@svgr/rollup'; import typescript from 'rollup-plugin-typescript2'; import { DEFAULT_EXTENSIONS as DEFAULT_BABEL_EXTENSIONS } from '@babel/core'; @@ -38,6 +39,9 @@ export const baseConfig = ({ mainFile = pkg.main, moduleFile = pkg.module, injec }, ], plugins: [ + alias({ + entries: [{ find: 'd3-color', replacement: __dirname + '/src/d3-color-alias' }], + }), replace({ __ENV__: JSON.stringify(processEnv), __REACT_FLOW_VERSION__: JSON.stringify(pkg.version), diff --git a/src/components/Handle/handler.ts b/src/components/Handle/handler.ts index 8e7e31ae..c695c557 100644 --- a/src/components/Handle/handler.ts +++ b/src/components/Handle/handler.ts @@ -23,7 +23,7 @@ type Result = { }; // checks if element below mouse is a handle and returns connection in form of an object { source: 123, target: 312 } -function checkElementBelowIsValid( +export function checkElementBelowIsValid( event: MouseEvent, connectionMode: ConnectionMode, isTarget: boolean, diff --git a/src/components/Handle/index.tsx b/src/components/Handle/index.tsx index 4d1e14f3..2bb21e51 100644 --- a/src/components/Handle/index.tsx +++ b/src/components/Handle/index.tsx @@ -5,7 +5,8 @@ import shallow from 'zustand/shallow'; import { useStore, useStoreApi } from '../../store'; import NodeIdContext from '../../contexts/NodeIdContext'; import { HandleProps, Connection, ReactFlowState, Position } from '../../types'; -import { onMouseDown } from './handler'; +import { checkElementBelowIsValid, onMouseDown } from './handler'; +import { getHostForElement } from '../../utils'; const alwaysValid = () => true; @@ -17,6 +18,8 @@ const selector = (s: ReactFlowState) => ({ onConnectStop: s.onConnectStop, onConnectEnd: s.onConnectEnd, connectionMode: s.connectionMode, + connectionStartHandle: s.connectionStartHandle, + connectOnClick: s.connectOnClick, }); const Handle = forwardRef( @@ -36,10 +39,15 @@ const Handle = forwardRef( ) => { const store = useStoreApi(); const nodeId = useContext(NodeIdContext) as string; - const { onConnectAction, onConnectStart, onConnectStop, onConnectEnd, connectionMode } = useStore( - selector, - shallow - ); + const { + onConnectAction, + onConnectStart, + onConnectStop, + onConnectEnd, + connectionMode, + connectionStartHandle, + connectOnClick, + } = useStore(selector, shallow); const handleId = id || null; const isTarget = type === 'target'; @@ -83,6 +91,47 @@ const Handle = forwardRef( ] ); + const onClick = useCallback( + (event: React.MouseEvent) => { + if (!connectionStartHandle) { + onConnectStart?.(event, { nodeId, handleId, handleType: type }); + store.setState({ connectionStartHandle: { nodeId, type, handleId } }); + } else { + const doc = getHostForElement(event.target as HTMLElement); + const { connection, isValid } = checkElementBelowIsValid( + event as unknown as MouseEvent, + connectionMode, + connectionStartHandle.type === 'target', + connectionStartHandle.nodeId, + connectionStartHandle.handleId || null, + isValidConnection, + doc + ); + + onConnectStop?.(event as unknown as MouseEvent); + + if (isValid) { + onConnectExtended(connection); + } + + onConnectEnd?.(event as unknown as MouseEvent); + + store.setState({ connectionStartHandle: null }); + } + }, + [ + connectionStartHandle, + onConnectStart, + onConnectExtended, + onConnectStop, + onConnectEnd, + isTarget, + nodeId, + handleId, + type, + ] + ); + const handleClasses = cc([ 'react-flow__handle', `react-flow__handle-${position}`, @@ -92,6 +141,10 @@ const Handle = forwardRef( source: !isTarget, target: isTarget, connectable: isConnectable, + connecting: + connectionStartHandle?.nodeId === nodeId && + connectionStartHandle?.handleId === handleId && + connectionStartHandle?.type === type, }, ]); @@ -102,6 +155,7 @@ const Handle = forwardRef( data-handlepos={position} className={handleClasses} onMouseDown={onMouseDownHandler} + onClick={connectOnClick ? onClick : undefined} ref={ref} {...rest} > diff --git a/src/components/StoreUpdater/index.tsx b/src/components/StoreUpdater/index.tsx index d2b5a37b..a1e8bc75 100644 --- a/src/components/StoreUpdater/index.tsx +++ b/src/components/StoreUpdater/index.tsx @@ -38,6 +38,7 @@ interface StoreUpdaterProps { snapGrid?: SnapGrid; translateExtent?: CoordinateExtent; fitViewOnInit: boolean; + connectOnClick: boolean; } const selector = (s: ReactFlowState) => ({ @@ -87,6 +88,7 @@ const StoreUpdater = ({ snapToGrid, translateExtent, fitViewOnInit, + connectOnClick, }: StoreUpdaterProps) => { const { setNodes, setEdges, setMinZoom, setMaxZoom, setTranslateExtent, setNodeExtent, reset } = useStore( selector, @@ -120,6 +122,7 @@ const StoreUpdater = ({ useDirectStoreUpdater('snapGrid', snapGrid, store.setState); useDirectStoreUpdater('onNodesChange', onNodesChange, store.setState); useDirectStoreUpdater('onEdgesChange', onEdgesChange, store.setState); + useDirectStoreUpdater('connectOnClick', connectOnClick, store.setState); return null; }; diff --git a/src/container/ReactFlow/index.tsx b/src/container/ReactFlow/index.tsx index 0fa29f63..b6100292 100644 --- a/src/container/ReactFlow/index.tsx +++ b/src/container/ReactFlow/index.tsx @@ -134,6 +134,7 @@ export interface ReactFlowProps extends Omit, 'on noWheelClassName?: string; noPanClassName?: string; fitViewOnInit?: boolean; + connectOnClick?: boolean; } export type ReactFlowRefType = HTMLDivElement; @@ -221,6 +222,7 @@ const ReactFlow: FunctionComponent = forwardRef = forwardRef {onSelectionChange && } {children} diff --git a/src/d3-color-alias.js b/src/d3-color-alias.js new file mode 100644 index 00000000..4a079d72 --- /dev/null +++ b/src/d3-color-alias.js @@ -0,0 +1,10 @@ +// this helps us to reduce bundle size by ~10kb. d3-color is not used but can't be treeshaked in this case. + +const obj = {}; +export const rgb = {}; +export const color = {}; +export const hsl = {}; +export const lab = {}; +export const hcl = {}; +export const cubehelix = {}; +export default obj; diff --git a/src/store/initialState.ts b/src/store/initialState.ts index 98ea8c50..7d7326c0 100644 --- a/src/store/initialState.ts +++ b/src/store/initialState.ts @@ -42,6 +42,9 @@ const initialState: ReactFlowStore = { fitViewOnInit: false, fitViewOnInitDone: false, + + connectionStartHandle: null, + connectOnClick: true, }; export default initialState; diff --git a/src/types/general.ts b/src/types/general.ts index 1f67dbc9..bd6a3903 100644 --- a/src/types/general.ts +++ b/src/types/general.ts @@ -5,7 +5,7 @@ import { XYPosition, Rect, Transform, CoordinateExtent } from './utils'; import { NodeChange, EdgeChange } from './changes'; import { Node, NodeInternals, NodeDimensionUpdate, NodeDiffUpdate } from './nodes'; import { Edge } from './edges'; -import { HandleType } from './handles'; +import { HandleType, StartHandle } from './handles'; export type FlowElement = Node | Edge; @@ -178,10 +178,14 @@ export type ReactFlowStore = { fitViewOnInit: boolean; fitViewOnInitDone: boolean; + connectionStartHandle: StartHandle | null; + onConnect?: OnConnect; onConnectStart?: OnConnectStart; onConnectStop?: OnConnectStop; onConnectEnd?: OnConnectEnd; + + connectOnClick: boolean; }; export type ReactFlowActions = { @@ -204,4 +208,9 @@ export type ReactFlowState = ReactFlowStore & ReactFlowActions; export type UpdateNodeInternals = (nodeId: string) => void; -export type OnSelectionChangeFunc = (params: { nodes: Node[]; edges: Edge[] }) => void; +export type OnSelectionChangeParams = { + nodes: Node[]; + edges: Edge[]; +}; + +export type OnSelectionChangeFunc = (params: OnSelectionChangeParams) => void; diff --git a/src/types/handles.ts b/src/types/handles.ts index 9114b360..37d1ea41 100644 --- a/src/types/handles.ts +++ b/src/types/handles.ts @@ -8,6 +8,12 @@ export interface HandleElement extends XYPosition, Dimensions { position: Position; } +export interface StartHandle { + nodeId: string; + type: HandleType; + handleId?: string | null; +} + export interface HandleProps { type: HandleType; position: Position;