Merge pull request #2891 from wbkd/next-release

v11.6 release
This commit is contained in:
Moritz Klack
2023-03-13 17:03:19 +01:00
committed by GitHub
52 changed files with 595 additions and 215 deletions
+6
View File
@@ -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',
@@ -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) => (
<circle cx={x} cy={y} r={Math.max(width, height) / 2} fill={color} />
);
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 (
<ReactFlow
nodes={nodes}
edges={edges}
onInit={onInit}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onNodeClick={onNodeClick}
onConnect={(p) => onConnect(p)}
onNodeDragStop={onNodeDragStop}
onlyRenderVisibleElements={false}
>
<Controls />
<Background variant={BackgroundVariant.Lines} />
<MiniMap nodeComponent={CustomMiniMapNode} />
<button type="button" onClick={addRandomNode} style={buttonStyle}>
add node
</button>
</ReactFlow>
);
};
export default CustomMiniMapNodeFlow;
@@ -38,6 +38,9 @@ const BasicFlow = () => {
onSelectionContextMenu={onPaneContextMenu}
>
<Background variant={BackgroundVariant.Cross} />
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
<input type={'text'} placeholder={'name'} />
</div>
</ReactFlow>
);
};
@@ -10,10 +10,21 @@ const controlStyle = {
border: 'none',
};
const CustomNode: FC<NodeProps> = ({ id, data }) => {
const CustomResizerNode: FC<NodeProps> = ({ data }) => {
return (
<>
<NodeResizeControl style={controlStyle}>
<NodeResizeControl
minWidth={data.minWidth ?? undefined}
maxWidth={data.maxWidth ?? undefined}
minHeight={data.minHeight ?? undefined}
maxHeight={data.maxHeight ?? undefined}
shouldResize={data.shouldResize ?? undefined}
onResizeStart={data.onResizeStart ?? undefined}
onResize={data.onResize ?? undefined}
onResizeEnd={data.onResizeEnd ?? undefined}
keepAspectRatio={data.keepAspectRatio ?? undefined}
style={controlStyle}
>
<ResizeIcon />
</NodeResizeControl>
@@ -24,4 +35,4 @@ const CustomNode: FC<NodeProps> = ({ id, data }) => {
);
};
export default memo(CustomNode);
export default memo(CustomResizerNode);
@@ -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<NodeProps> = ({ id, data }) => {
return (
<>
<NodeResizeControl color="red" position={Position.Top} />
<NodeResizeControl color="red" position={Position.Bottom} />
<Handle type="target" position={Position.Left} />
<div style={{ padding: 10 }}>{data.label}</div>
<Handle type="source" position={Position.Right} />
</>
);
};
export default memo(CustomNode);
@@ -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<NodeProps> = ({ id, data }) => {
return (
<>
<NodeResizeControl color="red" position={Position.Left} />
<NodeResizeControl color="red" position={Position.Right} shouldResize={shouldResize} onResize={onResize} />
<Handle type="target" position={Position.Top} />
<div style={{ padding: 10 }}>{data.label}</div>
<Handle type="source" position={Position.Bottom} />
</>
);
};
export default memo(CustomNode);
@@ -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<NodeProps> = ({ data, selected }) => {
return (
<>
<NodeResizer
minWidth={data.minWidth ?? undefined}
maxWidth={data.maxWidth ?? undefined}
minHeight={data.minHeight ?? undefined}
maxHeight={data.maxHeight ?? undefined}
isVisible={data.isVisible ?? selected}
shouldResize={data.shouldResize ?? undefined}
onResizeStart={data.onResizeStart ?? undefined}
onResize={data.onResize ?? undefined}
onResizeEnd={data.onResizeEnd ?? undefined}
keepAspectRatio={data.keepAspectRatio ?? undefined}
/>
<Handle type="target" position={Position.Left} />
<div>{data.label}</div>
<Handle type="source" position={Position.Right} />
</>
);
};
export default memo(DefaultResizerNode);
@@ -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<NodeProps> = ({ data }) => {
return (
<>
<NodeResizeControl
minWidth={data.minWidth ?? undefined}
maxWidth={data.maxWidth ?? undefined}
minHeight={data.minHeight ?? undefined}
maxHeight={data.maxHeight ?? undefined}
shouldResize={data.shouldResize ?? undefined}
onResizeStart={data.onResizeStart ?? undefined}
onResize={data.onResize ?? undefined}
onResizeEnd={data.onResizeEnd ?? undefined}
keepAspectRatio={data.keepAspectRatio ?? undefined}
color="red"
position={Position.Left}
/>
<NodeResizeControl
minWidth={data.minWidth ?? undefined}
maxWidth={data.maxWidth ?? undefined}
minHeight={data.minHeight ?? undefined}
maxHeight={data.maxHeight ?? undefined}
shouldResize={data.shouldResize ?? undefined}
onResizeStart={data.onResizeStart ?? undefined}
onResize={data.onResize ?? undefined}
onResizeEnd={data.onResizeEnd ?? undefined}
keepAspectRatio={data.keepAspectRatio ?? undefined}
color="red"
position={Position.Right}
/>
<Handle type="target" position={Position.Top} />
<div style={{ padding: 10 }}>{data.label}</div>
<Handle type="source" position={Position.Bottom} />
</>
);
};
export default memo(HorizontalResizerNode);
@@ -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<NodeProps> = ({ data, selected }) => {
return (
<>
<NodeResizer
minWidth={100}
minHeight={100}
isVisible={selected}
shouldResize={shouldResize}
onResizeStart={onResizeStart}
onResize={onResize}
onResizeEnd={onResizeEnd}
/>
<Handle type="target" position={Position.Left} />
<div>{data.label}</div>
<Handle type="source" position={Position.Right} />
</>
);
};
export default memo(CustomNode);
@@ -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<NodeProps> = ({ id, data }) => {
return (
<>
<NodeResizeControl
minWidth={data.minWidth ?? undefined}
maxWidth={data.maxWidth ?? undefined}
minHeight={data.minHeight ?? undefined}
maxHeight={data.maxHeight ?? undefined}
shouldResize={data.shouldResize ?? undefined}
onResizeStart={data.onResizeStart ?? undefined}
onResize={data.onResize ?? undefined}
onResizeEnd={data.onResizeEnd ?? undefined}
keepAspectRatio={data.keepAspectRatio ?? undefined}
color="red"
position={Position.Top}
/>
<NodeResizeControl
minWidth={data.minWidth ?? undefined}
maxWidth={data.maxWidth ?? undefined}
minHeight={data.minHeight ?? undefined}
maxHeight={data.maxHeight ?? undefined}
shouldResize={data.shouldResize ?? undefined}
onResizeStart={data.onResizeStart ?? undefined}
onResize={data.onResize ?? undefined}
onResizeEnd={data.onResizeEnd ?? undefined}
keepAspectRatio={data.keepAspectRatio ?? undefined}
color="red"
position={Position.Bottom}
/>
<Handle type="target" position={Position.Left} />
<div style={{ padding: 10 }}>{data.label}</div>
<Handle type="source" position={Position.Right} />
</>
);
};
export default memo(CustomNode);
@@ -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 },
},
];
@@ -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 = () => (
<ReactFlowProvider>
<Flow />
<SelectionLogger />
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
<input type={'text'} placeholder={'name'} />
</div>
</ReactFlowProvider>
);
@@ -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<NodeProps> = () => (
<>
<div>Only connectable with B</div>
<Handle type="source" position={Position.Right} isValidConnection={isValidConnection} />
<Handle type="source" position={Position.Right} />
</>
);
const CustomNode: FC<NodeProps> = ({ id }) => (
<>
<Handle type="target" position={Position.Left} isValidConnection={isValidConnection} />
<Handle type="target" position={Position.Left} />
<div>{id}</div>
<Handle type="source" position={Position.Right} isValidConnection={isValidConnection} />
<Handle type="source" position={Position.Right} />
</>
);
@@ -74,6 +76,11 @@ const ValidationFlow = () => {
[value]
);
const onEdgeUpdate = useCallback(
(oldEdge: Edge, newConnection: Connection) => setEdges((els) => updateEdge(oldEdge, newConnection, els)),
[setEdges]
);
return (
<ReactFlow
nodes={nodes}
@@ -86,6 +93,8 @@ const ValidationFlow = () => {
nodeTypes={nodeTypes}
onConnectStart={onConnectStart}
onConnectEnd={onConnectEnd}
onEdgeUpdate={onEdgeUpdate}
isValidConnection={isValidConnection}
fitView
/>
);
+9
View File
@@ -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
+1 -1
View File
@@ -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",
+1
View File
@@ -59,6 +59,7 @@ function Background({
left: 0,
}}
ref={ref}
data-testid="rf__background"
>
<pattern
id={patternId}
+9
View File
@@ -1,5 +1,14 @@
# @reactflow/controls
## 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
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@reactflow/controls",
"version": "11.1.8",
"version": "11.1.9",
"description": "Component to control the viewport of a React Flow instance",
"keywords": [
"react",
+6 -1
View File
@@ -68,7 +68,12 @@ const Controls: FC<PropsWithChildren<ControlProps>> = ({
};
return (
<Panel className={cc(['react-flow__controls', className])} position={position} style={style}>
<Panel
className={cc(['react-flow__controls', className])}
position={position}
style={style}
data-testid="rf__controls"
>
{showZoom && (
<>
<ControlButton
+14
View File
@@ -1,5 +1,19 @@
# @reactflow/core
## 11.6.0
### 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`
### 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
## 11.5.5
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@reactflow/core",
"version": "11.5.5",
"version": "11.6.0",
"description": "Core components and util functions of React Flow.",
"keywords": [
"react",
@@ -11,6 +11,8 @@ import { getMouseHandler } from './utils';
import { elementSelectionKeys } from '../../utils';
import type { EdgeProps, WrapEdgeProps, Connection } from '../../types';
const alwaysValidConnection = () => true;
export default (EdgeComponent: ComponentType<EdgeProps>) => {
const EdgeWrapper = ({
id,
@@ -94,12 +96,14 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
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);
+12 -6
View File
@@ -27,7 +27,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
{
type = 'source',
position = Position.Top,
isValidConnection = alwaysValid,
isValidConnection,
isConnectable = true,
id,
onConnect,
@@ -61,8 +61,8 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
...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<HTMLDivElement, HandleComponentProps>(
isTarget,
getState: store.getState,
setState: store.setState,
isValidConnection,
isValidConnection: isValidConnection || store.getState().isValidConnection || alwaysValid,
});
}
@@ -93,7 +93,12 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
};
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<HTMLDivElement, HandleComponentProps>(
}
const doc = getHostForElement(event.target as HTMLElement);
const isValidConnectionHandler = isValidConnection || isValidConnectionStore || alwaysValid;
const { connection, isValid } = isValidHandle(
event,
{
@@ -112,7 +118,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
connectionStartHandle.nodeId,
connectionStartHandle.handleId || null,
connectionStartHandle.type,
isValidConnection,
isValidConnectionHandler,
doc
);
+3 -2
View File
@@ -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);
+5 -1
View File
@@ -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<ReactFlowState>['setState'];
};
unselect?: boolean;
nodeRef?: RefObject<HTMLDivElement>;
}) {
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());
}
}
@@ -74,6 +74,7 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
handleNodeClick({
id,
store,
nodeRef,
});
}
@@ -90,13 +91,12 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
if (elementSelectionKeys.includes(event.key) && isSelectable) {
const unselect = event.key === 'Escape';
if (unselect) {
nodeRef.current?.blur();
}
handleNodeClick({
id,
store,
unselect,
nodeRef,
});
} else if (
!disableKeyboardA11y &&
@@ -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<Node[]>(nodes, setNodes);
useStoreUpdater<Edge[]>(edges, setEdges);
@@ -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;
@@ -163,6 +163,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
autoPanOnConnect = true,
autoPanOnNodeDrag = true,
connectionRadius = 20,
isValidConnection,
onError,
style,
id,
@@ -295,6 +296,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
autoPanOnNodeDrag={autoPanOnNodeDrag}
onError={onError}
connectionRadius={connectionRadius}
isValidConnection={isValidConnection}
/>
<SelectionListener onSelectionChange={onSelectionChange} />
{children}
+1
View File
@@ -153,6 +153,7 @@ function useDrag({
handleNodeClick({
id: nodeId,
store,
nodeRef: nodeRef as RefObject<HTMLDivElement>,
});
}
+9 -2
View File
@@ -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<PressedKeys>(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 = () => {
+1 -1
View File
@@ -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,
+2 -2
View File
@@ -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';
+1
View File
@@ -62,6 +62,7 @@ const initialState: ReactFlowStore = {
autoPanOnNodeDrag: true,
connectionRadius: 20,
onError: devWarn,
isValidConnection: undefined,
};
export default initialState;
@@ -38,6 +38,7 @@ import type {
SelectionMode,
OnError,
} from '.';
import { ValidConnectionFunc } from '../components/Handle/utils';
export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
nodes?: Node[];
@@ -144,6 +145,7 @@ export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
autoPanOnConnect?: boolean;
connectionRadius?: number;
onError?: OnError;
isValidConnection?: ValidConnectionFunc;
};
export type ReactFlowRefType = HTMLDivElement;
+8
View File
@@ -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;
}
+9 -6
View File
@@ -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 = (
+2 -3
View File
@@ -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 = (
+13
View File
@@ -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
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@reactflow/minimap",
"version": "11.3.8",
"version": "11.4.0",
"description": "Minimap component for React Flow.",
"keywords": [
"react",
+10 -2
View File
@@ -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 (
<Panel position={position} style={style} className={cc(['react-flow__minimap', className])}>
<Panel
position={position}
style={style}
className={cc(['react-flow__minimap', className])}
data-testid="rf__minimap"
>
<svg
width={elementWidth}
height={elementHeight}
@@ -176,7 +184,7 @@ function MiniMap({
const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute;
return (
<MiniMapNode
<NodeComponent
key={node.id}
x={x}
y={y}
+1 -17
View File
@@ -1,23 +1,7 @@
import { memo } from 'react';
import type { CSSProperties, MouseEvent } from 'react';
import type { MiniMapNodeProps } from './types';
import cc from 'classcat';
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;
}
const MiniMapNode = ({
id,
x,
+18 -1
View File
@@ -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<NodeData = any> = (node: Node<NodeData>) => string;
@@ -10,6 +10,7 @@ export type MiniMapProps<NodeData = any> = Omit<HTMLAttributes<SVGSVGElement>, '
nodeClassName?: string | GetMiniMapNodeAttribute<NodeData>;
nodeBorderRadius?: number;
nodeStrokeWidth?: number;
nodeComponent?: ComponentType<MiniMapNodeProps>;
maskColor?: string;
maskStrokeColor?: string;
maskStrokeWidth?: number;
@@ -20,3 +21,19 @@ export type MiniMapProps<NodeData = any> = Omit<HTMLAttributes<SVGSVGElement>, '
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;
}
+12
View File
@@ -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
+2 -2
View File
@@ -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",
@@ -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}
+45 -7
View File
@@ -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<HTMLDivElement, unknown>()
.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';
+14 -1
View File
@@ -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;
+7
View File
@@ -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
+1 -1
View File
@@ -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",
+24
View File
@@ -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
+1 -1
View File
@@ -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",