Merge pull request #2749 from wbkd/node-resizer/before-resize

Node-resizer: add shouldResize handler
This commit is contained in:
Moritz Klack
2023-01-23 18:07:19 +01:00
committed by GitHub
12 changed files with 136 additions and 18 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@reactflow/node-resizer': major
---
Add shouldresize, cleanup types
@@ -1,7 +1,7 @@
import { memo, FC, CSSProperties } from 'react'; import { memo, FC } from 'react';
import { Handle, Position, NodeProps } from 'reactflow'; import { Handle, Position, NodeProps } from 'reactflow';
import { NodeResizer, NodeResizeControl } from '@reactflow/node-resizer'; import { NodeResizeControl } from '@reactflow/node-resizer';
import '@reactflow/node-resizer/dist/style.css'; import '@reactflow/node-resizer/dist/style.css';
import ResizeIcon from './ResizeIcon'; import ResizeIcon from './ResizeIcon';
@@ -1,14 +1,14 @@
import { memo, FC } from 'react'; import { memo, FC } from 'react';
import { Handle, Position, NodeProps } from 'reactflow'; import { Handle, Position, NodeProps } from 'reactflow';
import { NodeResizer, NodeResizeControl } from '@reactflow/node-resizer'; import { NodeResizeControl } from '@reactflow/node-resizer';
import '@reactflow/node-resizer/dist/style.css'; import '@reactflow/node-resizer/dist/style.css';
const CustomNode: FC<NodeProps> = ({ id, data }) => { const CustomNode: FC<NodeProps> = ({ id, data }) => {
return ( return (
<> <>
<NodeResizeControl color="red" position="top" /> <NodeResizeControl color="red" position={Position.Top} />
<NodeResizeControl color="red" position="bottom" /> <NodeResizeControl color="red" position={Position.Bottom} />
<Handle type="target" position={Position.Left} /> <Handle type="target" position={Position.Left} />
<div style={{ padding: 10 }}>{data.label}</div> <div style={{ padding: 10 }}>{data.label}</div>
<Handle type="source" position={Position.Right} /> <Handle type="source" position={Position.Right} />
@@ -0,0 +1,33 @@
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);
@@ -1,21 +1,27 @@
import { memo, FC } from 'react'; import { memo, FC } from 'react';
import { Handle, Position, NodeProps } from 'reactflow'; import { Handle, Position, NodeProps } from 'reactflow';
import { NodeResizer, ResizeDragEvent, ResizeEventParams } from '@reactflow/node-resizer'; import { NodeResizer, ShouldResize, OnResize, OnResizeEnd, OnResizeStart } from '@reactflow/node-resizer';
import '@reactflow/node-resizer/dist/style.css'; import '@reactflow/node-resizer/dist/style.css';
const onResizeStart = (_: ResizeDragEvent, params: ResizeEventParams) => { const onResizeStart: OnResizeStart = (_, params) => {
console.log('resize start', params); console.log('resize start', params);
}; };
const onResize = (_: ResizeDragEvent, params: ResizeEventParams) => { const onResize: OnResize = (_, params) => {
console.log('resize', params); console.log('resize', params);
}; };
const onResizeEnd = (_: ResizeDragEvent, params: ResizeEventParams) => { const onResizeEnd: OnResizeEnd = (_, params) => {
console.log('resize end', params); console.log('resize end', params);
}; };
const shouldResize: ShouldResize = (_, params) => {
console.log('should resize', params);
return true;
};
const CustomNode: FC<NodeProps> = ({ data, selected }) => { const CustomNode: FC<NodeProps> = ({ data, selected }) => {
return ( return (
<> <>
@@ -23,6 +29,7 @@ const CustomNode: FC<NodeProps> = ({ data, selected }) => {
minWidth={100} minWidth={100}
minHeight={100} minHeight={100}
isVisible={selected} isVisible={selected}
shouldResize={shouldResize}
onResizeStart={onResizeStart} onResizeStart={onResizeStart}
onResize={onResize} onResize={onResize}
onResizeEnd={onResizeEnd} onResizeEnd={onResizeEnd}
@@ -4,11 +4,13 @@ import ReactFlow, { Controls, addEdge, Position, Connection, useNodesState, useE
import NodeResizerNode from './NodeResizerNode'; import NodeResizerNode from './NodeResizerNode';
import CustomResizer from './CustomResizer'; import CustomResizer from './CustomResizer';
import CustomResizer2 from './CustomResizer2'; import CustomResizer2 from './CustomResizer2';
import CustomResizer3 from './CustomResizer3';
const nodeTypes = { const nodeTypes = {
resizer: NodeResizerNode, resizer: NodeResizerNode,
customResizer: CustomResizer, customResizer: CustomResizer,
customResizer2: CustomResizer2, customResizer2: CustomResizer2,
customResizer3: CustomResizer3,
}; };
const initialEdges = [ const initialEdges = [
@@ -60,6 +62,13 @@ const initialNodes = [
position: { x: 250, y: 250 }, position: { x: 250, y: 250 },
style: { border: '1px solid #222', fontSize: 10 }, 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 },
},
]; ];
const CustomNodeFlow = () => { const CustomNodeFlow = () => {
@@ -87,7 +96,7 @@ const CustomNodeFlow = () => {
> >
<Controls /> <Controls />
<Panel position="bottom-right"> <Panel position="bottom-right">
<button onClick={() => setSnapToGrid(!snapToGrid)}>snapToGrid: {snapToGrid ? 'on' : 'off'}</button> <button onClick={() => setSnapToGrid((s) => !s)}>snapToGrid: {snapToGrid ? 'on' : 'off'}</button>
</Panel> </Panel>
</ReactFlow> </ReactFlow>
); );
+1 -1
View File
@@ -38,7 +38,7 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@reactflow/core": "workspace:*", "@reactflow/core": "workspace:^11.3.3",
"classcat": "^5.0.4", "classcat": "^5.0.4",
"d3-drag": "^3.0.0", "d3-drag": "^3.0.0",
"d3-selection": "^3.0.0", "d3-selection": "^3.0.0",
@@ -14,6 +14,7 @@ export default function NodeResizer({
color, color,
minWidth = 10, minWidth = 10,
minHeight = 10, minHeight = 10,
shouldResize,
onResizeStart, onResizeStart,
onResize, onResize,
onResizeEnd, onResizeEnd,
@@ -36,6 +37,7 @@ export default function NodeResizer({
minWidth={minWidth} minWidth={minWidth}
minHeight={minHeight} minHeight={minHeight}
onResizeStart={onResizeStart} onResizeStart={onResizeStart}
shouldResize={shouldResize}
onResize={onResize} onResize={onResize}
onResizeEnd={onResizeEnd} onResizeEnd={onResizeEnd}
/> />
@@ -51,6 +53,7 @@ export default function NodeResizer({
minWidth={minWidth} minWidth={minWidth}
minHeight={minHeight} minHeight={minHeight}
onResizeStart={onResizeStart} onResizeStart={onResizeStart}
shouldResize={shouldResize}
onResize={onResize} onResize={onResize}
onResizeEnd={onResizeEnd} onResizeEnd={onResizeEnd}
/> />
+24 -1
View File
@@ -12,6 +12,7 @@ import {
} from '@reactflow/core'; } from '@reactflow/core';
import { ResizeDragEvent, ResizeControlProps, ResizeControlLineProps, ResizeControlVariant } from './types'; import { ResizeDragEvent, ResizeControlProps, ResizeControlLineProps, ResizeControlVariant } from './types';
import { getDirection } from './utils';
const initPrevValues = { width: 0, height: 0, x: 0, y: 0 }; const initPrevValues = { width: 0, height: 0, x: 0, y: 0 };
@@ -31,6 +32,7 @@ function ResizeControl({
color, color,
minWidth = 10, minWidth = 10,
minHeight = 10, minHeight = 10,
shouldResize,
onResizeStart, onResizeStart,
onResize, onResize,
onResizeEnd, onResizeEnd,
@@ -140,7 +142,28 @@ function ResizeControl({
prevValues.current.height = height; prevValues.current.height = height;
} }
onResize?.(event, { ...prevValues.current }); if (changes.length === 0) {
return;
}
const direction = getDirection({
width: prevValues.current.width,
prevWidth,
height: prevValues.current.height,
prevHeight,
invertX,
invertY,
});
const nextValues = { ...prevValues.current, direction };
const callResize = shouldResize?.(event, nextValues);
if (callResize === false) {
return;
}
onResize?.(event, nextValues);
triggerNodeChanges(changes); triggerNodeChanges(changes);
} }
}) })
+17 -5
View File
@@ -1,13 +1,24 @@
import type { CSSProperties, ReactNode } from 'react'; import type { CSSProperties, ReactNode } from 'react';
import type { D3DragEvent, SubjectPosition } from 'd3-drag'; import type { D3DragEvent, SubjectPosition } from 'd3-drag';
export type ResizeEventParams = { export type ResizeParams = {
x: number; x: number;
y: number; y: number;
width: number; width: number;
height: number; height: number;
}; };
export type ResizeParamsWithDirection = ResizeParams & {
direction: number[];
};
type OnResizeHandler<Params = ResizeParams, Result = void> = (event: ResizeDragEvent, params: Params) => Result;
export type ShouldResize = OnResizeHandler<ResizeParamsWithDirection, boolean>;
export type OnResizeStart = OnResizeHandler;
export type OnResize = OnResizeHandler<ResizeParamsWithDirection>;
export type OnResizeEnd = OnResizeHandler;
export type NodeResizerProps = { export type NodeResizerProps = {
nodeId?: string; nodeId?: string;
color?: string; color?: string;
@@ -18,9 +29,10 @@ export type NodeResizerProps = {
isVisible?: boolean; isVisible?: boolean;
minWidth?: number; minWidth?: number;
minHeight?: number; minHeight?: number;
onResizeStart?: (event: ResizeDragEvent, params: ResizeEventParams) => void; shouldResize?: ShouldResize;
onResize?: (event: ResizeDragEvent, params: ResizeEventParams) => void; onResizeStart?: OnResizeStart;
onResizeEnd?: (event: ResizeDragEvent, params: ResizeEventParams) => void; onResize?: OnResize;
onResizeEnd?: OnResizeEnd;
}; };
export type ControlLinePosition = 'top' | 'bottom' | 'left' | 'right'; export type ControlLinePosition = 'top' | 'bottom' | 'left' | 'right';
@@ -34,7 +46,7 @@ export enum ResizeControlVariant {
export type ResizeControlProps = Pick< export type ResizeControlProps = Pick<
NodeResizerProps, NodeResizerProps,
'nodeId' | 'color' | 'minWidth' | 'minHeight' | 'onResizeStart' | 'onResize' | 'onResizeEnd' 'nodeId' | 'color' | 'minWidth' | 'minHeight' | 'shouldResize' | 'onResizeStart' | 'onResize' | 'onResizeEnd'
> & { > & {
position?: ControlPosition; position?: ControlPosition;
variant?: ResizeControlVariant; variant?: ResizeControlVariant;
+26
View File
@@ -0,0 +1,26 @@
type GetDirectionParams = {
width: number;
prevWidth: number;
height: number;
prevHeight: number;
invertX: boolean;
invertY: boolean;
};
// returns an array of two numbers (0, 1 or -1) representing the direction of the resize
// 0 = no change, 1 = increase, -1 = decrease
export function getDirection({ width, prevWidth, height, prevHeight, invertX, invertY }: GetDirectionParams) {
const deltaWidth = width - prevWidth;
const deltaHeight = height - prevHeight;
const direction = [deltaWidth > 0 ? 1 : deltaWidth < 0 ? -1 : 0, deltaHeight > 0 ? 1 : deltaHeight < 0 ? -1 : 0];
if (deltaWidth && invertX) {
direction[0] = direction[0] * -1;
}
if (deltaHeight && invertY) {
direction[1] = direction[1] * -1;
}
return direction;
}
+1 -1
View File
@@ -36,7 +36,7 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@reactflow/core": "workspace:*", "@reactflow/core": "workspace:^11.3.3",
"classcat": "^5.0.3", "classcat": "^5.0.3",
"zustand": "^4.3.1" "zustand": "^4.3.1"
}, },