diff --git a/examples/vite-app/src/App/index.tsx b/examples/vite-app/src/App/index.tsx index 02eaac1e..8ecdd8b8 100644 --- a/examples/vite-app/src/App/index.tsx +++ b/examples/vite-app/src/App/index.tsx @@ -5,6 +5,7 @@ import Basic from '../examples/Basic'; import Backgrounds from '../examples/Backgrounds'; import ControlledUncontrolled from '../examples/ControlledUncontrolled'; import CustomConnectionLine from '../examples/CustomConnectionLine'; +import CustomMiniMapNode from '../examples/CustomMiniMapNode'; import CustomNode from '../examples/CustomNode'; import DefaultNodes from '../examples/DefaultNodes'; import DragHandle from '../examples/DragHandle'; @@ -77,6 +78,11 @@ const routes: IRoute[] = [ path: '/custom-connectionline', component: CustomConnectionLine, }, + { + name: 'Custom Minimap Node', + path: '/custom-minimap-node', + component: CustomMiniMapNode, + }, { name: 'Custom Node', path: '/custom-node', diff --git a/examples/vite-app/src/examples/CustomMiniMapNode/index.tsx b/examples/vite-app/src/examples/CustomMiniMapNode/index.tsx new file mode 100644 index 00000000..6a8e5807 --- /dev/null +++ b/examples/vite-app/src/examples/CustomMiniMapNode/index.tsx @@ -0,0 +1,74 @@ +import { MouseEvent, CSSProperties, useCallback } from 'react'; + +import ReactFlow, { + addEdge, + Background, + BackgroundVariant, + Connection, + Controls, + Edge, + MiniMap, + MiniMapNodeProps, + Node, + ReactFlowInstance, + useEdgesState, + useNodesState, +} from 'reactflow'; + +const onInit = (reactFlowInstance: ReactFlowInstance) => console.log('flow loaded:', reactFlowInstance); +const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); +const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); + +const buttonStyle: CSSProperties = { + position: 'absolute', + left: 10, + top: 10, + zIndex: 4, +}; + +const CustomMiniMapNode = ({ x, y, width, height, color }: MiniMapNodeProps) => ( + +); + +const CustomMiniMapNodeFlow = () => { + const [nodes, setNodes, onNodesChange] = useNodesState([]); + const [edges, setEdges, onEdgesChange] = useEdgesState([]); + + const onConnect = useCallback((params: Connection | Edge) => setEdges((els) => addEdge(params, els)), [setEdges]); + const addRandomNode = () => { + const nodeId = (nodes.length + 1).toString(); + const newNode: Node = { + id: nodeId, + data: { label: `Node: ${nodeId}` }, + position: { + x: Math.random() * window.innerWidth, + y: Math.random() * window.innerHeight, + }, + }; + setNodes((nds) => nds.concat(newNode)); + }; + + return ( + onConnect(p)} + onNodeDragStop={onNodeDragStop} + onlyRenderVisibleElements={false} + > + + + + + + + ); +}; + +export default CustomMiniMapNodeFlow; diff --git a/examples/vite-app/src/examples/Figma/index.tsx b/examples/vite-app/src/examples/Figma/index.tsx index 22f0dcce..64cfb4bb 100644 --- a/examples/vite-app/src/examples/Figma/index.tsx +++ b/examples/vite-app/src/examples/Figma/index.tsx @@ -38,6 +38,9 @@ const BasicFlow = () => { onSelectionContextMenu={onPaneContextMenu} > +
+ +
); }; diff --git a/examples/vite-app/src/examples/NodeResizer/CustomResizer.tsx b/examples/vite-app/src/examples/NodeResizer/CustomResizer.tsx index 6cf58fd9..a36d9f18 100644 --- a/examples/vite-app/src/examples/NodeResizer/CustomResizer.tsx +++ b/examples/vite-app/src/examples/NodeResizer/CustomResizer.tsx @@ -10,10 +10,21 @@ const controlStyle = { border: 'none', }; -const CustomNode: FC = ({ id, data }) => { +const CustomResizerNode: FC = ({ data }) => { return ( <> - + @@ -24,4 +35,4 @@ const CustomNode: FC = ({ id, data }) => { ); }; -export default memo(CustomNode); +export default memo(CustomResizerNode); diff --git a/examples/vite-app/src/examples/NodeResizer/CustomResizer2.tsx b/examples/vite-app/src/examples/NodeResizer/CustomResizer2.tsx deleted file mode 100644 index 63ff061b..00000000 --- a/examples/vite-app/src/examples/NodeResizer/CustomResizer2.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { memo, FC } from 'react'; -import { Handle, Position, NodeProps } from 'reactflow'; - -import { NodeResizeControl } from '@reactflow/node-resizer'; -import '@reactflow/node-resizer/dist/style.css'; - -const CustomNode: FC = ({ id, data }) => { - return ( - <> - - - -
{data.label}
- - - ); -}; - -export default memo(CustomNode); diff --git a/examples/vite-app/src/examples/NodeResizer/CustomResizer3.tsx b/examples/vite-app/src/examples/NodeResizer/CustomResizer3.tsx deleted file mode 100644 index 422f2982..00000000 --- a/examples/vite-app/src/examples/NodeResizer/CustomResizer3.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { memo, FC } from 'react'; -import { Handle, Position, NodeProps } from 'reactflow'; - -import { NodeResizeControl, OnResize, ShouldResize } from '@reactflow/node-resizer'; -import '@reactflow/node-resizer/dist/style.css'; - -const shouldResize: ShouldResize = (event, params) => { - console.log('before resize', params); - - if (params.width > 100) { - return false; - } - - return true; -}; - -const onResize: OnResize = (event, params) => { - console.log('resize', params.direction); -}; - -const CustomNode: FC = ({ id, data }) => { - return ( - <> - - - -
{data.label}
- - - ); -}; - -export default memo(CustomNode); diff --git a/examples/vite-app/src/examples/NodeResizer/DefaultResizer.tsx b/examples/vite-app/src/examples/NodeResizer/DefaultResizer.tsx new file mode 100644 index 00000000..3d9649a5 --- /dev/null +++ b/examples/vite-app/src/examples/NodeResizer/DefaultResizer.tsx @@ -0,0 +1,29 @@ +import { memo, FC } from 'react'; +import { Handle, Position, NodeProps } from 'reactflow'; +import { NodeResizer } from '@reactflow/node-resizer'; + +import '@reactflow/node-resizer/dist/style.css'; + +const DefaultResizerNode: FC = ({ data, selected }) => { + return ( + <> + + +
{data.label}
+ + + ); +}; + +export default memo(DefaultResizerNode); diff --git a/examples/vite-app/src/examples/NodeResizer/HorizontalResizer.tsx b/examples/vite-app/src/examples/NodeResizer/HorizontalResizer.tsx new file mode 100644 index 00000000..2798ca3f --- /dev/null +++ b/examples/vite-app/src/examples/NodeResizer/HorizontalResizer.tsx @@ -0,0 +1,43 @@ +import { memo, FC } from 'react'; +import { Handle, Position, NodeProps } from 'reactflow'; +import { NodeResizeControl } from '@reactflow/node-resizer'; + +import '@reactflow/node-resizer/dist/style.css'; + +const HorizontalResizerNode: FC = ({ data }) => { + return ( + <> + + + +
{data.label}
+ + + ); +}; + +export default memo(HorizontalResizerNode); diff --git a/examples/vite-app/src/examples/NodeResizer/NodeResizerNode.tsx b/examples/vite-app/src/examples/NodeResizer/NodeResizerNode.tsx deleted file mode 100644 index 03e4960f..00000000 --- a/examples/vite-app/src/examples/NodeResizer/NodeResizerNode.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { memo, FC } from 'react'; -import { Handle, Position, NodeProps } from 'reactflow'; - -import { NodeResizer, ShouldResize, OnResize, OnResizeEnd, OnResizeStart } from '@reactflow/node-resizer'; -import '@reactflow/node-resizer/dist/style.css'; - -const onResizeStart: OnResizeStart = (_, params) => { - console.log('resize start', params); -}; - -const onResize: OnResize = (_, params) => { - console.log('resize', params); -}; - -const onResizeEnd: OnResizeEnd = (_, params) => { - console.log('resize end', params); -}; - -const shouldResize: ShouldResize = (_, params) => { - console.log('should resize', params); - - return true; -}; - -const CustomNode: FC = ({ data, selected }) => { - return ( - <> - - -
{data.label}
- - - ); -}; - -export default memo(CustomNode); diff --git a/examples/vite-app/src/examples/NodeResizer/VerticalResizer.tsx b/examples/vite-app/src/examples/NodeResizer/VerticalResizer.tsx new file mode 100644 index 00000000..a3d6515e --- /dev/null +++ b/examples/vite-app/src/examples/NodeResizer/VerticalResizer.tsx @@ -0,0 +1,43 @@ +import { memo, FC } from 'react'; +import { Handle, Position, NodeProps } from 'reactflow'; + +import { NodeResizeControl } from '@reactflow/node-resizer'; +import '@reactflow/node-resizer/dist/style.css'; + +const CustomNode: FC = ({ id, data }) => { + return ( + <> + + + +
{data.label}
+ + + ); +}; + +export default memo(CustomNode); diff --git a/examples/vite-app/src/examples/NodeResizer/index.tsx b/examples/vite-app/src/examples/NodeResizer/index.tsx index 01bb0851..d94a8d8b 100644 --- a/examples/vite-app/src/examples/NodeResizer/index.tsx +++ b/examples/vite-app/src/examples/NodeResizer/index.tsx @@ -1,73 +1,110 @@ import { useCallback, useState } from 'react'; -import ReactFlow, { Controls, addEdge, Position, Connection, useNodesState, useEdgesState, Panel } from 'reactflow'; +import ReactFlow, { Controls, addEdge, Connection, useNodesState, useEdgesState, Panel, Node, Edge } from 'reactflow'; -import NodeResizerNode from './NodeResizerNode'; +import DefaultResizer from './DefaultResizer'; import CustomResizer from './CustomResizer'; -import CustomResizer2 from './CustomResizer2'; -import CustomResizer3 from './CustomResizer3'; +import VerticalResizer from './VerticalResizer'; +import HorizontalResizer from './HorizontalResizer'; const nodeTypes = { - resizer: NodeResizerNode, + defaultResizer: DefaultResizer, customResizer: CustomResizer, - customResizer2: CustomResizer2, - customResizer3: CustomResizer3, + verticalResizer: VerticalResizer, + horizontalResizer: HorizontalResizer, }; -const initialEdges = [ - { - id: 'e1-2', - source: '1', - target: '2', - }, -]; +const nodeStyle = { + border: '1px solid #222', + fontSize: 10, + backgroundColor: '#ddd', +}; -const initialNodes = [ +const initialEdges: Edge[] = []; + +const initialNodes: Node[] = [ { id: '1', - type: 'input', - data: { label: 'An input node' }, + type: 'defaultResizer', + data: { label: 'default resizer' }, position: { x: 0, y: 0 }, - sourcePosition: Position.Right, + style: { ...nodeStyle }, }, { - id: '2', - type: 'resizer', - data: { label: 'default resizer' }, + id: '1a', + type: 'defaultResizer', + data: { + label: 'default resizer with min and max dimensions', + minWidth: 100, + minHeight: 80, + maxWidth: 200, + maxHeight: 200, + }, + position: { x: 0, y: 60 }, + style: { ...nodeStyle, width: 100, height: 80 }, + }, + { + id: '1b', + type: 'defaultResizer', + data: { + label: 'default resizer with initial size and aspect ratio', + keepAspectRatio: true, + minWidth: 100, + minHeight: 60, + maxWidth: 400, + maxHeight: 400, + }, position: { x: 250, y: 0 }, style: { - width: 200, - height: 150, - border: '1px solid #222', - fontSize: 10, + width: 174, + height: 123, + ...nodeStyle, }, }, { - id: '3', + id: '2', type: 'customResizer', - data: { label: 'resize control with child component' }, + data: { label: 'custom resize icon' }, + position: { x: 0, y: 200 }, + style: { width: 100, height: 60, ...nodeStyle }, + }, + { + id: '3', + type: 'verticalResizer', + data: { label: 'vertical resizer' }, position: { x: 250, y: 200 }, - style: { border: '1px solid #222', fontSize: 10, width: 100 }, + style: { ...nodeStyle }, + }, + { + id: '3a', + type: 'verticalResizer', + data: { + label: 'vertical resizer with min/maxHeight and aspect ratio', + minHeight: 50, + maxHeight: 200, + keepAspectRatio: true, + }, + position: { x: 400, y: 200 }, + style: { ...nodeStyle, height: 50 }, }, { id: '4', - type: 'customResizer2', - data: { label: 'resize controls' }, - position: { x: 100, y: 150 }, - style: { border: '1px solid #222', fontSize: 10 }, + type: 'horizontalResizer', + data: { + label: 'horizontal resizer with aspect ratio', + keepAspectRatio: true, + minHeight: 20, + maxHeight: 80, + maxWidth: 300, + }, + position: { x: 250, y: 300 }, + style: { ...nodeStyle }, }, { - id: '5', - type: 'customResizer2', - data: { label: 'min width and height' }, - position: { x: 250, y: 250 }, - style: { border: '1px solid #222', fontSize: 10 }, - }, - { - id: '6', - type: 'customResizer3', - data: { label: 'resize controls' }, - position: { x: 400, y: 200 }, - style: { border: '1px solid #222', fontSize: 10 }, + id: '4a', + type: 'horizontalResizer', + data: { label: 'horizontal resizer with maxWidth', maxWidth: 300 }, + position: { x: 250, y: 400 }, + style: { ...nodeStyle }, }, ]; diff --git a/examples/vite-app/src/examples/UseOnSelectionChange/index.tsx b/examples/vite-app/src/examples/UseOnSelectionChange/index.tsx index 160ca769..b6f5531f 100644 --- a/examples/vite-app/src/examples/UseOnSelectionChange/index.tsx +++ b/examples/vite-app/src/examples/UseOnSelectionChange/index.tsx @@ -18,6 +18,20 @@ const initialNodes: Node[] = [ data: { label: 'Node 1' }, position: { x: 250, y: 5 }, }, + { + id: '2', + type: 'default', + data: { label: 'Node 2' }, + position: { x: 250, y: 100 }, + }, +]; + +const initialEdges: Edge[] = [ + { + id: 'e1-2', + source: '1', + target: '2', + }, ]; const SelectionLogger = () => { @@ -34,7 +48,7 @@ const SelectionLogger = () => { const Flow = () => { const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); - const [edges, setEdges, onEdgesChange] = useEdgesState([]); + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const onConnect = useCallback((params: Edge | Connection) => setEdges((els) => addEdge(params, els)), [setEdges]); return ( @@ -52,6 +66,9 @@ const WrappedFlow = () => ( +
+ +
); diff --git a/examples/vite-app/src/examples/Validation/index.tsx b/examples/vite-app/src/examples/Validation/index.tsx index 1488b48c..c55d8178 100644 --- a/examples/vite-app/src/examples/Validation/index.tsx +++ b/examples/vite-app/src/examples/Validation/index.tsx @@ -12,6 +12,8 @@ import ReactFlow, { OnConnectStart, OnConnectEnd, OnConnect, + updateEdge, + Edge, } from 'reactflow'; import styles from './validation.module.css'; @@ -28,15 +30,15 @@ const isValidConnection = (connection: Connection) => connection.target === 'B'; const CustomInput: FC = () => ( <>
Only connectable with B
- + ); const CustomNode: FC = ({ id }) => ( <> - +
{id}
- + ); @@ -74,6 +76,11 @@ const ValidationFlow = () => { [value] ); + const onEdgeUpdate = useCallback( + (oldEdge: Edge, newConnection: Connection) => setEdges((els) => updateEdge(oldEdge, newConnection, els)), + [setEdges] + ); + return ( { nodeTypes={nodeTypes} onConnectStart={onConnectStart} onConnectEnd={onConnectEnd} + onEdgeUpdate={onEdgeUpdate} + isValidConnection={isValidConnection} fitView /> ); diff --git a/packages/background/CHANGELOG.md b/packages/background/CHANGELOG.md index 4729e9a4..d179fca5 100644 --- a/packages/background/CHANGELOG.md +++ b/packages/background/CHANGELOG.md @@ -1,5 +1,14 @@ # @reactflow/background +## 11.1.9 + +### Patch Changes + +- [#2895](https://github.com/wbkd/react-flow/pull/2895) [`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425) Thanks [@moklick](https://github.com/moklick)! - add data-testid for controls, minimap and background + +- Updated dependencies [[`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425), [`83fc4675`](https://github.com/wbkd/react-flow/commit/83fc467545527729633e817dbccfe59d0040da4b), [`b1190837`](https://github.com/wbkd/react-flow/commit/b11908370bc438ca8d4179497cd4eb1f8c656798), [`5fabd272`](https://github.com/wbkd/react-flow/commit/5fabd2720f6367f75f79a45822d8f675a3b8e1cf), [`8f080bd5`](https://github.com/wbkd/react-flow/commit/8f080bd5e0e7e6c71f51eee9c9f2bc4b25182861), [`b8886514`](https://github.com/wbkd/react-flow/commit/b88865140c72fa7e92a883498768000cb2cc96a7), [`16bf89f2`](https://github.com/wbkd/react-flow/commit/16bf89f2b7bbf8449c00d0e2c07c19c3ff6d2533)]: + - @reactflow/core@11.6.0 + ## 11.1.8 ### Patch Changes diff --git a/packages/background/package.json b/packages/background/package.json index 8dcd3148..5f2a889f 100644 --- a/packages/background/package.json +++ b/packages/background/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/background", - "version": "11.1.8", + "version": "11.1.9", "description": "Background component with different variants for React Flow", "keywords": [ "react", diff --git a/packages/background/src/Background.tsx b/packages/background/src/Background.tsx index 10702d81..bf8a4336 100644 --- a/packages/background/src/Background.tsx +++ b/packages/background/src/Background.tsx @@ -59,6 +59,7 @@ function Background({ left: 0, }} ref={ref} + data-testid="rf__background" > > = ({ }; return ( - + {showZoom && ( <> true; + export default (EdgeComponent: ComponentType) => { const EdgeWrapper = ({ id, @@ -94,12 +96,14 @@ export default (EdgeComponent: ComponentType) => { return; } + const { edges, isValidConnection: isValidConnectionStore } = store.getState(); const nodeId = isSourceHandle ? target : source; const handleId = (isSourceHandle ? targetHandleId : sourceHandleId) || null; const handleType = isSourceHandle ? 'target' : 'source'; - const isValidConnection = () => true; + const isValidConnection = isValidConnectionStore || alwaysValidConnection; + const isTarget = isSourceHandle; - const edge = store.getState().edges.find((e) => e.id === id)!; + const edge = edges.find((e) => e.id === id)!; setUpdating(true); onEdgeUpdateStart?.(event, edge, handleType); diff --git a/packages/core/src/components/Handle/index.tsx b/packages/core/src/components/Handle/index.tsx index dab16653..e2d57d4d 100644 --- a/packages/core/src/components/Handle/index.tsx +++ b/packages/core/src/components/Handle/index.tsx @@ -27,7 +27,7 @@ const Handle = forwardRef( { type = 'source', position = Position.Top, - isValidConnection = alwaysValid, + isValidConnection, isConnectable = true, id, onConnect, @@ -61,8 +61,8 @@ const Handle = forwardRef( ...params, }; if (hasDefaultEdges) { - const { edges } = store.getState(); - store.setState({ edges: addEdge(edgeParams, edges) }); + const { edges, setEdges } = store.getState(); + setEdges(addEdge(edgeParams, edges)); } onConnectAction?.(edgeParams); @@ -81,7 +81,7 @@ const Handle = forwardRef( isTarget, getState: store.getState, setState: store.setState, - isValidConnection, + isValidConnection: isValidConnection || store.getState().isValidConnection || alwaysValid, }); } @@ -93,7 +93,12 @@ const Handle = forwardRef( }; const onClick = (event: ReactMouseEvent) => { - const { onClickConnectStart, onClickConnectEnd, connectionMode } = store.getState(); + const { + onClickConnectStart, + onClickConnectEnd, + connectionMode, + isValidConnection: isValidConnectionStore, + } = store.getState(); if (!connectionStartHandle) { onClickConnectStart?.(event, { nodeId, handleId, handleType: type }); store.setState({ connectionStartHandle: { nodeId, type, handleId } }); @@ -101,6 +106,7 @@ const Handle = forwardRef( } const doc = getHostForElement(event.target as HTMLElement); + const isValidConnectionHandler = isValidConnection || isValidConnectionStore || alwaysValid; const { connection, isValid } = isValidHandle( event, { @@ -112,7 +118,7 @@ const Handle = forwardRef( connectionStartHandle.nodeId, connectionStartHandle.handleId || null, connectionStartHandle.type, - isValidConnection, + isValidConnectionHandler, doc ); diff --git a/packages/core/src/components/Handle/utils.ts b/packages/core/src/components/Handle/utils.ts index 58263781..bfd181e1 100644 --- a/packages/core/src/components/Handle/utils.ts +++ b/packages/core/src/components/Handle/utils.ts @@ -104,9 +104,10 @@ export function isValidHandle( // in strict mode we don't allow target to target or source to source connections const isValid = - connectionMode === ConnectionMode.Strict + handleToCheck.classList.contains('connectable') && + (connectionMode === ConnectionMode.Strict ? (isTarget && handleType === 'source') || (!isTarget && handleType === 'target') - : handleNodeId !== fromNodeId || handleId !== fromHandleId; + : handleNodeId !== fromNodeId || handleId !== fromHandleId); if (isValid) { result.isValid = isValidConnection(connection); diff --git a/packages/core/src/components/Nodes/utils.ts b/packages/core/src/components/Nodes/utils.ts index dca9f297..174b1be7 100644 --- a/packages/core/src/components/Nodes/utils.ts +++ b/packages/core/src/components/Nodes/utils.ts @@ -1,4 +1,4 @@ -import { MouseEvent } from 'react'; +import { MouseEvent, RefObject } from 'react'; import { StoreApi } from 'zustand'; import { getDimensions } from '../../utils'; @@ -58,6 +58,7 @@ export function handleNodeClick({ id, store, unselect = false, + nodeRef, }: { id: string; store: { @@ -65,6 +66,7 @@ export function handleNodeClick({ setState: StoreApi['setState']; }; unselect?: boolean; + nodeRef?: RefObject; }) { const { addSelectedNodes, unselectNodesAndEdges, multiSelectionActive, nodeInternals } = store.getState(); const node = nodeInternals.get(id)!; @@ -75,5 +77,7 @@ export function handleNodeClick({ addSelectedNodes([id]); } else if (unselect || (node.selected && multiSelectionActive)) { unselectNodesAndEdges({ nodes: [node] }); + + requestAnimationFrame(() => nodeRef?.current?.blur()); } } diff --git a/packages/core/src/components/Nodes/wrapNode.tsx b/packages/core/src/components/Nodes/wrapNode.tsx index f56f117b..6f95dbdd 100644 --- a/packages/core/src/components/Nodes/wrapNode.tsx +++ b/packages/core/src/components/Nodes/wrapNode.tsx @@ -74,6 +74,7 @@ export default (NodeComponent: ComponentType) => { handleNodeClick({ id, store, + nodeRef, }); } @@ -90,13 +91,12 @@ export default (NodeComponent: ComponentType) => { if (elementSelectionKeys.includes(event.key) && isSelectable) { const unselect = event.key === 'Escape'; - if (unselect) { - nodeRef.current?.blur(); - } + handleNodeClick({ id, store, unselect, + nodeRef, }); } else if ( !disableKeyboardA11y && diff --git a/packages/core/src/components/StoreUpdater/index.tsx b/packages/core/src/components/StoreUpdater/index.tsx index febd3b0c..4491bb94 100644 --- a/packages/core/src/components/StoreUpdater/index.tsx +++ b/packages/core/src/components/StoreUpdater/index.tsx @@ -49,6 +49,7 @@ type StoreUpdaterProps = Pick< | 'autoPanOnNodeDrag' | 'onError' | 'connectionRadius' + | 'isValidConnection' > & { rfId: string }; const selector = (s: ReactFlowState) => ({ @@ -127,6 +128,7 @@ const StoreUpdater = ({ autoPanOnNodeDrag, onError, connectionRadius, + isValidConnection, }: StoreUpdaterProps) => { const { setNodes, @@ -184,6 +186,7 @@ const StoreUpdater = ({ useDirectStoreUpdater('autoPanOnNodeDrag', autoPanOnNodeDrag, store.setState); useDirectStoreUpdater('onError', onError, store.setState); useDirectStoreUpdater('connectionRadius', connectionRadius, store.setState); + useDirectStoreUpdater('isValidConnection', isValidConnection, store.setState); useStoreUpdater(nodes, setNodes); useStoreUpdater(edges, setEdges); diff --git a/packages/core/src/container/EdgeRenderer/utils.ts b/packages/core/src/container/EdgeRenderer/utils.ts index edd74543..0bf08e2d 100644 --- a/packages/core/src/container/EdgeRenderer/utils.ts +++ b/packages/core/src/container/EdgeRenderer/utils.ts @@ -77,10 +77,10 @@ export function getHandle(bounds: HandleElement[], handleId?: string | null): Ha return null; } - if (handleId) { - return bounds.find((d) => d.id === handleId)!; - } else if (bounds.length === 1) { + if (bounds.length === 1 || !handleId) { return bounds[0]; + } else if (handleId) { + return bounds.find((d) => d.id === handleId) || null; } return null; diff --git a/packages/core/src/container/ReactFlow/index.tsx b/packages/core/src/container/ReactFlow/index.tsx index 9e7b7a49..9fa23f9c 100644 --- a/packages/core/src/container/ReactFlow/index.tsx +++ b/packages/core/src/container/ReactFlow/index.tsx @@ -163,6 +163,7 @@ const ReactFlow = forwardRef( autoPanOnConnect = true, autoPanOnNodeDrag = true, connectionRadius = 20, + isValidConnection, onError, style, id, @@ -295,6 +296,7 @@ const ReactFlow = forwardRef( autoPanOnNodeDrag={autoPanOnNodeDrag} onError={onError} connectionRadius={connectionRadius} + isValidConnection={isValidConnection} /> {children} diff --git a/packages/core/src/hooks/useDrag/index.ts b/packages/core/src/hooks/useDrag/index.ts index 22d06daf..8821ab4a 100644 --- a/packages/core/src/hooks/useDrag/index.ts +++ b/packages/core/src/hooks/useDrag/index.ts @@ -153,6 +153,7 @@ function useDrag({ handleNodeClick({ id: nodeId, store, + nodeRef: nodeRef as RefObject, }); } diff --git a/packages/core/src/hooks/useKeyPress.ts b/packages/core/src/hooks/useKeyPress.ts index 20dcf37e..3301fc8f 100644 --- a/packages/core/src/hooks/useKeyPress.ts +++ b/packages/core/src/hooks/useKeyPress.ts @@ -19,6 +19,9 @@ const doc = typeof document !== 'undefined' ? document : null; export default (keyCode: KeyCode | null = null, options: UseKeyPressOptions = { target: doc }): boolean => { const [keyPressed, setKeyPressed] = useState(false); + // we need to remember if a modifier key is pressed in order to track it + const modifierPressed = useRef(false); + // we need to remember the pressed keys in order to support combinations const pressedKeys = useRef(new Set([])); @@ -43,7 +46,10 @@ export default (keyCode: KeyCode | null = null, options: UseKeyPressOptions = { useEffect(() => { if (keyCode !== null) { const downHandler = (event: KeyboardEvent) => { - if (isInputDOMNode(event)) { + + modifierPressed.current = event.ctrlKey || event.metaKey || event.shiftKey; + + if (!modifierPressed.current && isInputDOMNode(event)) { return false; } const keyOrCode = useKeyOrCode(event.code, keysToWatch); @@ -56,7 +62,7 @@ export default (keyCode: KeyCode | null = null, options: UseKeyPressOptions = { }; const upHandler = (event: KeyboardEvent) => { - if (isInputDOMNode(event)) { + if (!modifierPressed.current && isInputDOMNode(event)) { return false; } const keyOrCode = useKeyOrCode(event.code, keysToWatch); @@ -67,6 +73,7 @@ export default (keyCode: KeyCode | null = null, options: UseKeyPressOptions = { } else { pressedKeys.current.delete(event[keyOrCode]); } + modifierPressed.current = false; }; const resetHandler = () => { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c4a5b19e..158c9094 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -8,7 +8,7 @@ export { default as SimpleBezierEdge, getSimpleBezierPath } from './components/E export { default as SmoothStepEdge, getSmoothStepPath } from './components/Edges/SmoothStepEdge'; export { default as BaseEdge } from './components/Edges/BaseEdge'; -export { internalsSymbol, rectToBox, boxToRect, getBoundsOfRects } from './utils'; +export { internalsSymbol, rectToBox, boxToRect, getBoundsOfRects, clamp } from './utils'; export { isNode, isEdge, diff --git a/packages/core/src/store/index.ts b/packages/core/src/store/index.ts index c85225db..056957c3 100644 --- a/packages/core/src/store/index.ts +++ b/packages/core/src/store/index.ts @@ -33,8 +33,8 @@ const createRFStore = () => return Array.from(get().nodeInternals.values()); }, setEdges: (edges: Edge[]) => { - const { defaultEdgeOptions = {} } = get(); - set({ edges: edges.map((e) => ({ ...defaultEdgeOptions, ...e })) }); + const { defaultEdgeOptions = null } = get(); + set({ edges: defaultEdgeOptions ? edges.map((e) => ({ ...defaultEdgeOptions, ...e })) : edges }); }, setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => { const hasDefaultNodes = typeof nodes !== 'undefined'; diff --git a/packages/core/src/store/initialState.ts b/packages/core/src/store/initialState.ts index 99e9d298..55aca27f 100644 --- a/packages/core/src/store/initialState.ts +++ b/packages/core/src/store/initialState.ts @@ -62,6 +62,7 @@ const initialState: ReactFlowStore = { autoPanOnNodeDrag: true, connectionRadius: 20, onError: devWarn, + isValidConnection: undefined, }; export default initialState; diff --git a/packages/core/src/types/component-props.ts b/packages/core/src/types/component-props.ts index 1614805a..cae9da3b 100644 --- a/packages/core/src/types/component-props.ts +++ b/packages/core/src/types/component-props.ts @@ -38,6 +38,7 @@ import type { SelectionMode, OnError, } from '.'; +import { ValidConnectionFunc } from '../components/Handle/utils'; export type ReactFlowProps = HTMLAttributes & { nodes?: Node[]; @@ -144,6 +145,7 @@ export type ReactFlowProps = HTMLAttributes & { autoPanOnConnect?: boolean; connectionRadius?: number; onError?: OnError; + isValidConnection?: ValidConnectionFunc; }; export type ReactFlowRefType = HTMLDivElement; diff --git a/packages/core/src/types/general.ts b/packages/core/src/types/general.ts index f628727e..549228a8 100644 --- a/packages/core/src/types/general.ts +++ b/packages/core/src/types/general.ts @@ -61,6 +61,8 @@ export interface Connection { targetHandle: string | null; } +export type IsValidConnection = (edge: Edge | Connection) => boolean; + export type ConnectionStatus = 'valid' | 'invalid'; export enum ConnectionMode { @@ -223,6 +225,8 @@ export type ReactFlowStore = { autoPanOnConnect: boolean; autoPanOnNodeDrag: boolean; connectionRadius: number; + + isValidConnection?: IsValidConnection; }; export type ReactFlowActions = { @@ -277,3 +281,7 @@ export type SelectionRect = Rect & { }; export type OnError = (id: string, message: string) => void; + +export interface UpdateEdgeOptions { + shouldReplaceId?: boolean; +} \ No newline at end of file diff --git a/packages/core/src/utils/graph.ts b/packages/core/src/utils/graph.ts index c7772a1c..0d28d659 100644 --- a/packages/core/src/utils/graph.ts +++ b/packages/core/src/utils/graph.ts @@ -12,6 +12,7 @@ import { Rect, NodeInternals, NodeOrigin, + UpdateEdgeOptions, } from '../types'; import { errorMessages } from '../contants'; @@ -93,32 +94,34 @@ export const addEdge = (edgeParams: Edge | Connection, edges: Edge[]): Edge[] => return edges.concat(edge); }; -export const updateEdge = (oldEdge: Edge, newConnection: Connection, edges: Edge[]): Edge[] => { +export const updateEdge = (oldEdge: Edge, newConnection: Connection, edges: Edge[], options: UpdateEdgeOptions = { shouldReplaceId: true }): Edge[] => { + const { id: oldEdgeId, ...rest } = oldEdge; + if (!newConnection.source || !newConnection.target) { devWarn('006', errorMessages['006']()); return edges; } - const foundEdge = edges.find((e) => e.id === oldEdge.id) as Edge; + const foundEdge = edges.find((e) => e.id === oldEdgeId) as Edge; if (!foundEdge) { - devWarn('007', errorMessages['007'](oldEdge.id)); + devWarn('007', errorMessages['007'](oldEdgeId)); return edges; } // Remove old edge and create the new edge with parameters of old edge. const edge = { - ...oldEdge, - id: getEdgeId(newConnection), + ...rest, + id: options.shouldReplaceId ? getEdgeId(newConnection) : oldEdgeId, source: newConnection.source, target: newConnection.target, sourceHandle: newConnection.sourceHandle, targetHandle: newConnection.targetHandle, } as Edge; - return edges.filter((e) => e.id !== oldEdge.id).concat(edge); + return edges.filter((e) => e.id !== oldEdgeId).concat(edge); }; export const pointToRendererPoint = ( diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 2d801d05..b4f0ca3f 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -104,11 +104,10 @@ export function isInputDOMNode(event: KeyboardEvent | ReactKeyboardEvent): boole const target = (kbEvent.composedPath?.()?.[0] || event.target) as HTMLElement; const isInput = ['INPUT', 'SELECT', 'TEXTAREA'].includes(target?.nodeName) || target?.hasAttribute('contenteditable'); - // we want to be able to do a multi selection event if we are in an input field - const isModifierKey = event.ctrlKey || event.metaKey || event.shiftKey; + // when an input field is focused we don't want to trigger deletion or movement of nodes - return (isInput && !isModifierKey) || !!target?.closest('.nokey'); + return isInput || !!target?.closest('.nokey'); } export const isMouseEvent = ( diff --git a/packages/minimap/CHANGELOG.md b/packages/minimap/CHANGELOG.md index e0ab8a44..a1a322a9 100644 --- a/packages/minimap/CHANGELOG.md +++ b/packages/minimap/CHANGELOG.md @@ -1,5 +1,18 @@ # @reactflow/minimap +## 11.4.0 + +### Minor Changes + +- [#2906](https://github.com/wbkd/react-flow/pull/2906) [`4a30185a`](https://github.com/wbkd/react-flow/commit/4a30185a12899691ff61259f0db84bc5494cb573) Thanks [@hayleigh-dot-dev](https://github.com/hayleigh-dot-dev)! - Add `nodeComponent` prop for passing custom component for the nodes + +### Patch Changes + +- [#2895](https://github.com/wbkd/react-flow/pull/2895) [`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425) - add data-testid for controls, minimap and background + +- Updated dependencies [[`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425), [`83fc4675`](https://github.com/wbkd/react-flow/commit/83fc467545527729633e817dbccfe59d0040da4b), [`b1190837`](https://github.com/wbkd/react-flow/commit/b11908370bc438ca8d4179497cd4eb1f8c656798), [`5fabd272`](https://github.com/wbkd/react-flow/commit/5fabd2720f6367f75f79a45822d8f675a3b8e1cf), [`8f080bd5`](https://github.com/wbkd/react-flow/commit/8f080bd5e0e7e6c71f51eee9c9f2bc4b25182861), [`b8886514`](https://github.com/wbkd/react-flow/commit/b88865140c72fa7e92a883498768000cb2cc96a7), [`16bf89f2`](https://github.com/wbkd/react-flow/commit/16bf89f2b7bbf8449c00d0e2c07c19c3ff6d2533)]: + - @reactflow/core@11.6.0 + ## 11.3.8 ### Patch Changes diff --git a/packages/minimap/package.json b/packages/minimap/package.json index 66e22ced..81f0ff1c 100644 --- a/packages/minimap/package.json +++ b/packages/minimap/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/minimap", - "version": "11.3.8", + "version": "11.4.0", "description": "Minimap component for React Flow.", "keywords": [ "react", diff --git a/packages/minimap/src/MiniMap.tsx b/packages/minimap/src/MiniMap.tsx index bb4a2f89..59b3f34d 100644 --- a/packages/minimap/src/MiniMap.tsx +++ b/packages/minimap/src/MiniMap.tsx @@ -55,6 +55,9 @@ function MiniMap({ nodeClassName = '', nodeBorderRadius = 5, nodeStrokeWidth = 2, + // We need to rename the prop to be `CapitalCase` so that JSX will render it as + // a component properly. + nodeComponent: NodeComponent = MiniMapNode, maskColor = 'rgb(240, 240, 240, 0.6)', maskStrokeColor = 'none', maskStrokeWidth = 1, @@ -161,7 +164,12 @@ function MiniMap({ : undefined; return ( - + void; -} - const MiniMapNode = ({ id, x, diff --git a/packages/minimap/src/types.ts b/packages/minimap/src/types.ts index 7d006329..394e466e 100644 --- a/packages/minimap/src/types.ts +++ b/packages/minimap/src/types.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { HTMLAttributes, MouseEvent } from 'react'; +import type { ComponentType, CSSProperties, HTMLAttributes, MouseEvent } from 'react'; import type { Node, PanelPosition, XYPosition } from '@reactflow/core'; export type GetMiniMapNodeAttribute = (node: Node) => string; @@ -10,6 +10,7 @@ export type MiniMapProps = Omit, ' nodeClassName?: string | GetMiniMapNodeAttribute; nodeBorderRadius?: number; nodeStrokeWidth?: number; + nodeComponent?: ComponentType; maskColor?: string; maskStrokeColor?: string; maskStrokeWidth?: number; @@ -20,3 +21,19 @@ export type MiniMapProps = Omit, ' zoomable?: boolean; ariaLabel?: string | null; }; + +export interface MiniMapNodeProps { + id: string; + x: number; + y: number; + width: number; + height: number; + borderRadius: number; + className: string; + color: string; + shapeRendering: string; + strokeColor: string; + strokeWidth: number; + style?: CSSProperties; + onClick?: (event: MouseEvent, id: string) => void; +} diff --git a/packages/node-resizer/CHANGELOG.md b/packages/node-resizer/CHANGELOG.md index 9d7870b5..b8cfb5cb 100644 --- a/packages/node-resizer/CHANGELOG.md +++ b/packages/node-resizer/CHANGELOG.md @@ -1,5 +1,17 @@ # @reactflow/node-resizer +## 2.1.0 + +### Minor Changes + +- [#2900](https://github.com/wbkd/react-flow/pull/2900) [`b1190837`](https://github.com/wbkd/react-flow/commit/b11908370bc438ca8d4179497cd4eb1f8c656798) Thanks [@stffabi](https://github.com/stffabi)! - add `maxWidth` and `maxHeight` props +- [#2900](https://github.com/wbkd/react-flow/pull/2900) [`b1190837`](https://github.com/wbkd/react-flow/commit/b11908370bc438ca8d4179497cd4eb1f8c656798) - add `keepAspectRatio` prop + +### Patch Changes + +- Updated dependencies [[`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425), [`83fc4675`](https://github.com/wbkd/react-flow/commit/83fc467545527729633e817dbccfe59d0040da4b), [`b1190837`](https://github.com/wbkd/react-flow/commit/b11908370bc438ca8d4179497cd4eb1f8c656798), [`5fabd272`](https://github.com/wbkd/react-flow/commit/5fabd2720f6367f75f79a45822d8f675a3b8e1cf), [`8f080bd5`](https://github.com/wbkd/react-flow/commit/8f080bd5e0e7e6c71f51eee9c9f2bc4b25182861), [`b8886514`](https://github.com/wbkd/react-flow/commit/b88865140c72fa7e92a883498768000cb2cc96a7), [`16bf89f2`](https://github.com/wbkd/react-flow/commit/16bf89f2b7bbf8449c00d0e2c07c19c3ff6d2533)]: + - @reactflow/core@11.6.0 + ## 2.0.1 ### Patch Changes diff --git a/packages/node-resizer/package.json b/packages/node-resizer/package.json index 6bbc73e4..b40e0ead 100644 --- a/packages/node-resizer/package.json +++ b/packages/node-resizer/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/node-resizer", - "version": "2.0.1", + "version": "2.1.0", "description": "A helper component for resizing nodes.", "keywords": [ "react", @@ -38,7 +38,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@reactflow/core": "workspace:^11.3.3", + "@reactflow/core": "workspace:^11.6.0", "classcat": "^5.0.4", "d3-drag": "^3.0.0", "d3-selection": "^3.0.0", diff --git a/packages/node-resizer/src/NodeResizer.tsx b/packages/node-resizer/src/NodeResizer.tsx index c7a3b8a6..485b8efc 100644 --- a/packages/node-resizer/src/NodeResizer.tsx +++ b/packages/node-resizer/src/NodeResizer.tsx @@ -14,6 +14,9 @@ export default function NodeResizer({ color, minWidth = 10, minHeight = 10, + maxWidth = Number.MAX_VALUE, + maxHeight = Number.MAX_VALUE, + keepAspectRatio = false, shouldResize, onResizeStart, onResize, @@ -36,7 +39,10 @@ export default function NodeResizer({ color={color} minWidth={minWidth} minHeight={minHeight} + maxWidth={maxWidth} + maxHeight={maxHeight} onResizeStart={onResizeStart} + keepAspectRatio={keepAspectRatio} shouldResize={shouldResize} onResize={onResize} onResizeEnd={onResizeEnd} @@ -52,7 +58,10 @@ export default function NodeResizer({ color={color} minWidth={minWidth} minHeight={minHeight} + maxWidth={maxWidth} + maxHeight={maxHeight} onResizeStart={onResizeStart} + keepAspectRatio={keepAspectRatio} shouldResize={shouldResize} onResize={onResize} onResizeEnd={onResizeEnd} diff --git a/packages/node-resizer/src/ResizeControl.tsx b/packages/node-resizer/src/ResizeControl.tsx index 3397537d..6757b2a3 100644 --- a/packages/node-resizer/src/ResizeControl.tsx +++ b/packages/node-resizer/src/ResizeControl.tsx @@ -9,6 +9,7 @@ import { NodeDimensionChange, useNodeId, NodePositionChange, + clamp, } from '@reactflow/core'; import { ResizeDragEvent, ResizeControlProps, ResizeControlLineProps, ResizeControlVariant } from './types'; @@ -20,6 +21,7 @@ const initStartValues = { ...initPrevValues, pointerX: 0, pointerY: 0, + aspectRatio: 1, }; function ResizeControl({ @@ -32,6 +34,9 @@ function ResizeControl({ color, minWidth = 10, minHeight = 10, + maxWidth = Number.MAX_VALUE, + maxHeight = Number.MAX_VALUE, + keepAspectRatio = false, shouldResize, onResizeStart, onResize, @@ -53,6 +58,12 @@ function ResizeControl({ } const selection = select(resizeControlRef.current); + + const enableX = controlPosition.includes('right') || controlPosition.includes('left'); + const enableY = controlPosition.includes('bottom') || controlPosition.includes('top'); + const invertX = controlPosition.includes('left'); + const invertY = controlPosition.includes('top'); + const dragHandler = drag() .on('start', (event: ResizeDragEvent) => { const node = store.getState().nodeInternals.get(id); @@ -69,6 +80,7 @@ function ResizeControl({ ...prevValues.current, pointerX: xSnapped, pointerY: ySnapped, + aspectRatio: prevValues.current.width / prevValues.current.height, }; onResizeStart?.(event, { ...prevValues.current }); @@ -77,10 +89,6 @@ function ResizeControl({ const { nodeInternals, triggerNodeChanges } = store.getState(); const { xSnapped, ySnapped } = getPointerPosition(event); const node = nodeInternals.get(id); - const enableX = controlPosition.includes('right') || controlPosition.includes('left'); - const enableY = controlPosition.includes('bottom') || controlPosition.includes('top'); - const invertX = controlPosition.includes('left'); - const invertY = controlPosition.includes('top'); if (node) { const changes: NodeChange[] = []; @@ -91,13 +99,43 @@ function ResizeControl({ height: startHeight, x: startNodeX, y: startNodeY, + aspectRatio, } = startValues.current; const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues.current; + const distX = Math.floor(enableX ? xSnapped - startX : 0); const distY = Math.floor(enableY ? ySnapped - startY : 0); - const width = Math.max(startWidth + (invertX ? -distX : distX), minWidth); - const height = Math.max(startHeight + (invertY ? -distY : distY), minHeight); + + let width = clamp(startWidth + (invertX ? -distX : distX), minWidth, maxWidth); + let height = clamp(startHeight + (invertY ? -distY : distY), minHeight, maxHeight); + + if (keepAspectRatio) { + const nextAspectRatio = width / height; + const isDiagonal = enableX && enableY; + const isHorizontal = enableX && !enableY; + const isVertical = enableY && !enableX; + + width = (nextAspectRatio <= aspectRatio && isDiagonal) || isVertical ? height * aspectRatio : width; + height = (nextAspectRatio > aspectRatio && isDiagonal) || isHorizontal ? width / aspectRatio : height; + + if (width >= maxWidth) { + width = maxWidth; + height = maxWidth / aspectRatio; + } else if (width <= minWidth) { + width = minWidth; + height = minWidth / aspectRatio; + } + + if (height >= maxHeight) { + height = maxHeight; + width = maxHeight * aspectRatio; + } else if (height <= minHeight) { + height = minHeight; + width = minHeight * aspectRatio; + } + } + const isWidthChange = width !== prevWidth; const isHeightChange = height !== prevHeight; @@ -183,7 +221,7 @@ function ResizeControl({ return () => { selection.on('.drag', null); }; - }, [id, controlPosition, minWidth, minHeight, getPointerPosition]); + }, [id, controlPosition, minWidth, minHeight, maxWidth, maxHeight, keepAspectRatio, getPointerPosition]); const positionClassNames = controlPosition.split('-'); const colorStyleProp = variant === ResizeControlVariant.Line ? 'borderColor' : 'backgroundColor'; diff --git a/packages/node-resizer/src/types.ts b/packages/node-resizer/src/types.ts index 75d40ace..ad9114b2 100644 --- a/packages/node-resizer/src/types.ts +++ b/packages/node-resizer/src/types.ts @@ -29,6 +29,9 @@ export type NodeResizerProps = { isVisible?: boolean; minWidth?: number; minHeight?: number; + maxWidth?: number; + maxHeight?: number; + keepAspectRatio?: boolean; shouldResize?: ShouldResize; onResizeStart?: OnResizeStart; onResize?: OnResize; @@ -46,7 +49,17 @@ export enum ResizeControlVariant { export type ResizeControlProps = Pick< NodeResizerProps, - 'nodeId' | 'color' | 'minWidth' | 'minHeight' | 'shouldResize' | 'onResizeStart' | 'onResize' | 'onResizeEnd' + | 'nodeId' + | 'color' + | 'minWidth' + | 'minHeight' + | 'maxWidth' + | 'maxHeight' + | 'keepAspectRatio' + | 'shouldResize' + | 'onResizeStart' + | 'onResize' + | 'onResizeEnd' > & { position?: ControlPosition; variant?: ResizeControlVariant; diff --git a/packages/node-toolbar/CHANGELOG.md b/packages/node-toolbar/CHANGELOG.md index ddd7225e..7fc031e2 100644 --- a/packages/node-toolbar/CHANGELOG.md +++ b/packages/node-toolbar/CHANGELOG.md @@ -1,5 +1,12 @@ # @reactflow/node-toolbar +## 1.1.9 + +### Patch Changes + +- Updated dependencies [[`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425), [`83fc4675`](https://github.com/wbkd/react-flow/commit/83fc467545527729633e817dbccfe59d0040da4b), [`b1190837`](https://github.com/wbkd/react-flow/commit/b11908370bc438ca8d4179497cd4eb1f8c656798), [`5fabd272`](https://github.com/wbkd/react-flow/commit/5fabd2720f6367f75f79a45822d8f675a3b8e1cf), [`8f080bd5`](https://github.com/wbkd/react-flow/commit/8f080bd5e0e7e6c71f51eee9c9f2bc4b25182861), [`b8886514`](https://github.com/wbkd/react-flow/commit/b88865140c72fa7e92a883498768000cb2cc96a7), [`16bf89f2`](https://github.com/wbkd/react-flow/commit/16bf89f2b7bbf8449c00d0e2c07c19c3ff6d2533)]: + - @reactflow/core@11.6.0 + ## 1.1.8 ### Patch Changes diff --git a/packages/node-toolbar/package.json b/packages/node-toolbar/package.json index 8500fda7..35378ec0 100644 --- a/packages/node-toolbar/package.json +++ b/packages/node-toolbar/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/node-toolbar", - "version": "1.1.8", + "version": "1.1.9", "description": "A toolbar component for React Flow that can be attached to a node.", "keywords": [ "react", diff --git a/packages/reactflow/CHANGELOG.md b/packages/reactflow/CHANGELOG.md index 1a6a38dd..b8d6693f 100644 --- a/packages/reactflow/CHANGELOG.md +++ b/packages/reactflow/CHANGELOG.md @@ -1,5 +1,29 @@ # reactflow +## 11.6.0 + +This release introduces a new `isValidConnection` prop for the ReactFlow component. You no longer need to pass it to all your Handle components but can pass it once. We also added a new option for the `updateEdge` function that allows you to specify if you want to replace an id when updating it. More over the `MiniMap` got a new `nodeComponent` prop to pass a custom component for the mini map nodes. + +### Minor Changes + +- [#2877](https://github.com/wbkd/react-flow/pull/2877) [`b8886514`](https://github.com/wbkd/react-flow/commit/b88865140c72fa7e92a883498768000cb2cc96a7) - add `isValidConnection` prop for ReactFlow component +- [#2847](https://github.com/wbkd/react-flow/pull/2847) [`16bf89f2`](https://github.com/wbkd/react-flow/commit/16bf89f2b7bbf8449c00d0e2c07c19c3ff6d2533) Thanks [@bcakmakoglu](https://github.com/bcakmakoglu)! - Add option to enable/disable replacing edge id when using `updateEdge` +- [#2906](https://github.com/wbkd/react-flow/pull/2906) [`4a30185a`](https://github.com/wbkd/react-flow/commit/4a30185a12899691ff61259f0db84bc5494cb573) Thanks [@hayleigh-dot-dev](https://github.com/hayleigh-dot-dev)! - Minimap: add nodeComponent prop for passing custom component + +### Patch Changes + +- [#2895](https://github.com/wbkd/react-flow/pull/2895) [`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425) - add data-testid for controls, minimap and background +- [#2894](https://github.com/wbkd/react-flow/pull/2894) [`83fc4675`](https://github.com/wbkd/react-flow/commit/83fc467545527729633e817dbccfe59d0040da4b) - fix(nodes): blur when node gets unselected +- [#2892](https://github.com/wbkd/react-flow/pull/2892) [`5fabd272`](https://github.com/wbkd/react-flow/commit/5fabd2720f6367f75f79a45822d8f675a3b8e1cf) Thanks [@danielgek](https://github.com/danielgek) - track modifier keys on useKeypress +- [#2893](https://github.com/wbkd/react-flow/pull/2893) [`8f080bd5`](https://github.com/wbkd/react-flow/commit/8f080bd5e0e7e6c71f51eee9c9f2bc4b25182861) - fix: check if handle is connectable + +- Updated dependencies [[`3d5764ca`](https://github.com/wbkd/react-flow/commit/3d5764cac6548984a30cbf85899024e62fd69425), [`4a30185a`](https://github.com/wbkd/react-flow/commit/4a30185a12899691ff61259f0db84bc5494cb573), [`83fc4675`](https://github.com/wbkd/react-flow/commit/83fc467545527729633e817dbccfe59d0040da4b), [`b1190837`](https://github.com/wbkd/react-flow/commit/b11908370bc438ca8d4179497cd4eb1f8c656798), [`5fabd272`](https://github.com/wbkd/react-flow/commit/5fabd2720f6367f75f79a45822d8f675a3b8e1cf), [`8f080bd5`](https://github.com/wbkd/react-flow/commit/8f080bd5e0e7e6c71f51eee9c9f2bc4b25182861), [`b8886514`](https://github.com/wbkd/react-flow/commit/b88865140c72fa7e92a883498768000cb2cc96a7), [`16bf89f2`](https://github.com/wbkd/react-flow/commit/16bf89f2b7bbf8449c00d0e2c07c19c3ff6d2533)]: + - @reactflow/background@11.1.9 + - @reactflow/controls@11.1.9 + - @reactflow/core@11.6.0 + - @reactflow/minimap@11.4.0 + - @reactflow/node-toolbar@1.1.9 + ## 11.5.6 ### Patch Changes diff --git a/packages/reactflow/package.json b/packages/reactflow/package.json index c2b29c19..2663a4cd 100644 --- a/packages/reactflow/package.json +++ b/packages/reactflow/package.json @@ -1,6 +1,6 @@ { "name": "reactflow", - "version": "11.5.6", + "version": "11.6.0", "description": "A highly customizable React library for building node-based editors and interactive flow charts", "keywords": [ "react",