feat(node-resizer): add flag to keep aspect ratio, cleanup example
This commit is contained in:
@@ -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,97 @@
|
||||
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 },
|
||||
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 },
|
||||
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 },
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -16,6 +16,7 @@ export default function NodeResizer({
|
||||
minHeight = 10,
|
||||
maxWidth = Number.MAX_VALUE,
|
||||
maxHeight = Number.MAX_VALUE,
|
||||
keepAspectRatio = false,
|
||||
shouldResize,
|
||||
onResizeStart,
|
||||
onResize,
|
||||
@@ -41,6 +42,7 @@ export default function NodeResizer({
|
||||
maxWidth={maxWidth}
|
||||
maxHeight={maxHeight}
|
||||
onResizeStart={onResizeStart}
|
||||
keepAspectRatio={keepAspectRatio}
|
||||
shouldResize={shouldResize}
|
||||
onResize={onResize}
|
||||
onResizeEnd={onResizeEnd}
|
||||
@@ -59,6 +61,7 @@ export default function NodeResizer({
|
||||
maxWidth={maxWidth}
|
||||
maxHeight={maxHeight}
|
||||
onResizeStart={onResizeStart}
|
||||
keepAspectRatio={keepAspectRatio}
|
||||
shouldResize={shouldResize}
|
||||
onResize={onResize}
|
||||
onResizeEnd={onResizeEnd}
|
||||
|
||||
@@ -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({
|
||||
@@ -34,6 +36,7 @@ function ResizeControl({
|
||||
minHeight = 10,
|
||||
maxWidth = Number.MAX_VALUE,
|
||||
maxHeight = Number.MAX_VALUE,
|
||||
keepAspectRatio = false,
|
||||
shouldResize,
|
||||
onResizeStart,
|
||||
onResize,
|
||||
@@ -55,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);
|
||||
@@ -71,6 +80,7 @@ function ResizeControl({
|
||||
...prevValues.current,
|
||||
pointerX: xSnapped,
|
||||
pointerY: ySnapped,
|
||||
aspectRatio: prevValues.current.width / prevValues.current.height,
|
||||
};
|
||||
|
||||
onResizeStart?.(event, { ...prevValues.current });
|
||||
@@ -79,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[] = [];
|
||||
@@ -93,13 +99,29 @@ 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.min(Math.max(startWidth + (invertX ? -distX : distX), minWidth), maxWidth);
|
||||
const height = Math.min(Math.max(startHeight + (invertY ? -distY : distY), minHeight), maxHeight);
|
||||
|
||||
let width = startWidth + (invertX ? -distX : distX);
|
||||
let height = startHeight + (invertY ? -distY : distY);
|
||||
|
||||
width = clamp(width, minWidth, maxWidth);
|
||||
height = clamp(height, 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;
|
||||
}
|
||||
|
||||
const isWidthChange = width !== prevWidth;
|
||||
const isHeightChange = height !== prevHeight;
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ export type NodeResizerProps = {
|
||||
minHeight?: number;
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
keepAspectRatio?: boolean;
|
||||
shouldResize?: ShouldResize;
|
||||
onResizeStart?: OnResizeStart;
|
||||
onResize?: OnResize;
|
||||
@@ -48,7 +49,17 @@ export enum ResizeControlVariant {
|
||||
|
||||
export type ResizeControlProps = Pick<
|
||||
NodeResizerProps,
|
||||
'nodeId' | 'color' | 'minWidth' | 'minHeight' | 'maxWidth' | 'maxHeight' | 'shouldResize' | 'onResizeStart' | 'onResize' | 'onResizeEnd'
|
||||
| 'nodeId'
|
||||
| 'color'
|
||||
| 'minWidth'
|
||||
| 'minHeight'
|
||||
| 'maxWidth'
|
||||
| 'maxHeight'
|
||||
| 'keepAspectRatio'
|
||||
| 'shouldResize'
|
||||
| 'onResizeStart'
|
||||
| 'onResize'
|
||||
| 'onResizeEnd'
|
||||
> & {
|
||||
position?: ControlPosition;
|
||||
variant?: ResizeControlVariant;
|
||||
|
||||
Reference in New Issue
Block a user