Merge pull request #5227 from xyflow/feat/resizer-handle-dir

feat(xy-resizer): add resize direction
This commit is contained in:
Moritz Klack
2025-04-17 17:10:59 +02:00
committed by GitHub
10 changed files with 79 additions and 8 deletions

View File

@@ -0,0 +1,5 @@
---
'@xyflow/system': patch
---
add resizeDirection for XYResizer

View File

@@ -0,0 +1,5 @@
---
'@xyflow/react': minor
---
Add `resizeDirection` prop for the `NodeResizeControl` component

View File

@@ -0,0 +1,21 @@
import { memo, FC } from 'react';
import { Handle, Position, NodeProps, NodeResizeControl, ResizeControlVariant } from '@xyflow/react';
const CustomResizerNode: FC<NodeProps> = ({ data }) => {
return (
<>
<NodeResizeControl
variant={ResizeControlVariant.Handle}
position="bottom-right"
resizeDirection="horizontal"
minWidth={100}
maxWidth={500}
/>
<Handle type="target" position={Position.Left} />
<div>{data.label}</div>
<Handle type="source" position={Position.Right} />
</>
);
};
export default memo(CustomResizerNode);

View File

@@ -15,12 +15,14 @@ import DefaultResizer from './DefaultResizer';
import CustomResizer from './CustomResizer';
import VerticalResizer from './VerticalResizer';
import HorizontalResizer from './HorizontalResizer';
import BottomRightResizer from './BottomRightResizer';
const nodeTypes = {
defaultResizer: DefaultResizer,
customResizer: CustomResizer,
verticalResizer: VerticalResizer,
horizontalResizer: HorizontalResizer,
bottomRightResizer: BottomRightResizer,
};
const nodeStyle = {
@@ -166,6 +168,13 @@ const initialNodes: Node[] = [
expandParent: true,
style: { ...nodeStyle },
},
{
id: '6',
type: 'bottomRightResizer',
data: { label: 'Bottom Right with horizontal direction' },
position: { x: 500, y: 500 },
style: { ...nodeStyle },
},
];
const CustomNodeFlow = () => {

View File

@@ -32,6 +32,7 @@ function ResizeControl({
maxWidth = Number.MAX_VALUE,
maxHeight = Number.MAX_VALUE,
keepAspectRatio = false,
resizeDirection,
shouldResize,
onResizeStart,
onResize,
@@ -117,11 +118,12 @@ function ResizeControl({
}
if (change.width !== undefined && change.height !== undefined) {
const setAttributes = !resizeDirection ? true : resizeDirection === 'horizontal' ? 'width' : 'height';
const dimensionChange: NodeDimensionChange = {
id,
type: 'dimensions',
resizing: true,
setAttributes: true,
setAttributes,
dimensions: {
width: change.width,
height: change.height,
@@ -166,6 +168,7 @@ function ResizeControl({
maxHeight,
},
keepAspectRatio,
resizeDirection,
onResizeStart,
onResize,
onResizeEnd,

View File

@@ -3,6 +3,7 @@ import type {
ControlPosition,
ControlLinePosition,
ResizeControlVariant,
ResizeControlDirection,
ShouldResize,
OnResizeStart,
OnResize,
@@ -97,6 +98,11 @@ export type ResizeControlProps = Pick<
* @example ResizeControlVariant.Handle, ResizeControlVariant.Line
*/
variant?: ResizeControlVariant;
/**
* The direction the user can resize the node.
* If not provided, the user can resize in any direction.
*/
resizeDirection?: ResizeControlDirection;
className?: string;
style?: CSSProperties;
children?: ReactNode;
@@ -105,6 +111,6 @@ export type ResizeControlProps = Pick<
/**
* @expand
*/
export type ResizeControlLineProps = ResizeControlProps & {
export type ResizeControlLineProps = Omit<ResizeControlProps, 'resizeDirection'> & {
position?: ControlLinePosition;
};

View File

@@ -130,8 +130,12 @@ function applyChange(change: any, element: any): any {
element.measured.height = change.dimensions.height;
if (change.setAttributes) {
element.width = change.dimensions.width;
element.height = change.dimensions.height;
if (change.setAttributes === true || change.setAttributes === 'width') {
element.width = change.dimensions.width;
}
if (change.setAttributes === true || change.setAttributes === 'height') {
element.height = change.dimensions.height;
}
}
}

View File

@@ -7,7 +7,7 @@ export type NodeDimensionChange = {
/* if this is true, the node is currently being resized via the NodeResizer */
resizing?: boolean;
/* if this is true, we will set width and height of the node and not just the measured dimensions */
setAttributes?: boolean;
setAttributes?: boolean | 'width' | 'height';
};
export type NodePositionChange = {

View File

@@ -12,7 +12,15 @@ import type {
Transform,
XYPosition,
} from '../types';
import type { OnResize, OnResizeEnd, OnResizeStart, ResizeDragEvent, ShouldResize, ControlPosition } from './types';
import type {
OnResize,
OnResizeEnd,
OnResizeStart,
ResizeDragEvent,
ShouldResize,
ControlPosition,
ResizeControlDirection,
} from './types';
const initPrevValues = { width: 0, height: 0, x: 0, y: 0 };
@@ -60,6 +68,7 @@ type XYResizerUpdateParams = {
maxHeight: number;
};
keepAspectRatio: boolean;
resizeDirection?: ResizeControlDirection;
onResizeStart: OnResizeStart | undefined;
onResize: OnResize | undefined;
onResizeEnd: OnResizeEnd | undefined;
@@ -99,6 +108,7 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X
controlPosition,
boundaries,
keepAspectRatio,
resizeDirection,
onResizeStart,
onResize,
onResizeEnd,
@@ -251,8 +261,10 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X
}
if (isWidthChange || isHeightChange) {
change.width = isWidthChange ? width : prevValues.width;
change.height = isHeightChange ? height : prevValues.height;
change.width =
isWidthChange && (!resizeDirection || resizeDirection === 'horizontal') ? width : prevValues.width;
change.height =
isHeightChange && (!resizeDirection || resizeDirection === 'vertical') ? height : prevValues.height;
prevValues.width = change.width;
prevValues.height = change.height;
}

View File

@@ -35,6 +35,12 @@ export enum ResizeControlVariant {
Handle = 'handle',
}
/**
* The direction the user can resize the node.
* @public
*/
export type ResizeControlDirection = 'horizontal' | 'vertical';
export const XY_RESIZER_HANDLE_POSITIONS: ControlPosition[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];
export const XY_RESIZER_LINE_POSITIONS: ControlLinePosition[] = ['top', 'right', 'bottom', 'left'];