Merge pull request #5326 from xyflow/fix/node-resizer-controls
correct node resizer control size for viewport zoom
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
'@xyflow/react': minor
|
||||||
|
'@xyflow/svelte': minor
|
||||||
|
'@xyflow/system': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Prevent NodeResizer controls to become too small when zooming out
|
||||||
@@ -10,6 +10,8 @@ const CustomResizerNode: FC<NodeProps> = ({ data }) => {
|
|||||||
resizeDirection="horizontal"
|
resizeDirection="horizontal"
|
||||||
minWidth={100}
|
minWidth={100}
|
||||||
maxWidth={500}
|
maxWidth={500}
|
||||||
|
color="orange"
|
||||||
|
autoScale={false}
|
||||||
/>
|
/>
|
||||||
<Handle type="target" position={Position.Left} />
|
<Handle type="target" position={Position.Left} />
|
||||||
<div>{data.label}</div>
|
<div>{data.label}</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useRef, useEffect, memo } from 'react';
|
import { useRef, useEffect, memo, useCallback } from 'react';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
|
import { shallow } from 'zustand/shallow';
|
||||||
import {
|
import {
|
||||||
XYResizer,
|
XYResizer,
|
||||||
ResizeControlVariant,
|
ResizeControlVariant,
|
||||||
@@ -13,18 +14,28 @@ import {
|
|||||||
evaluateAbsolutePosition,
|
evaluateAbsolutePosition,
|
||||||
ParentExpandChild,
|
ParentExpandChild,
|
||||||
XYPosition,
|
XYPosition,
|
||||||
|
ControlPosition,
|
||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import { useStoreApi } from '../../hooks/useStore';
|
import { useStoreApi, useStore } from '../../hooks/useStore';
|
||||||
import { useNodeId } from '../../contexts/NodeIdContext';
|
import { useNodeId } from '../../contexts/NodeIdContext';
|
||||||
import type { ResizeControlProps, ResizeControlLineProps } from './types';
|
import type { ResizeControlProps, ResizeControlLineProps } from './types';
|
||||||
|
import { ReactFlowState } from '../../types';
|
||||||
|
|
||||||
|
const scaleSelector = (calculateScale: boolean) => (store: ReactFlowState) =>
|
||||||
|
calculateScale ? `${Math.max(1 / store.transform[2], 1)}` : undefined;
|
||||||
|
|
||||||
|
const defaultPositions: Record<ResizeControlVariant, ControlPosition> = {
|
||||||
|
[ResizeControlVariant.Line]: 'right',
|
||||||
|
[ResizeControlVariant.Handle]: 'bottom-right',
|
||||||
|
};
|
||||||
|
|
||||||
function ResizeControl({
|
function ResizeControl({
|
||||||
nodeId,
|
nodeId,
|
||||||
position,
|
position,
|
||||||
variant = ResizeControlVariant.Handle,
|
variant = ResizeControlVariant.Handle,
|
||||||
className,
|
className,
|
||||||
style = {},
|
style = undefined,
|
||||||
children,
|
children,
|
||||||
color,
|
color,
|
||||||
minWidth = 10,
|
minWidth = 10,
|
||||||
@@ -33,6 +44,7 @@ function ResizeControl({
|
|||||||
maxHeight = Number.MAX_VALUE,
|
maxHeight = Number.MAX_VALUE,
|
||||||
keepAspectRatio = false,
|
keepAspectRatio = false,
|
||||||
resizeDirection,
|
resizeDirection,
|
||||||
|
autoScale = true,
|
||||||
shouldResize,
|
shouldResize,
|
||||||
onResizeStart,
|
onResizeStart,
|
||||||
onResize,
|
onResize,
|
||||||
@@ -42,10 +54,13 @@ function ResizeControl({
|
|||||||
const id = typeof nodeId === 'string' ? nodeId : contextNodeId;
|
const id = typeof nodeId === 'string' ? nodeId : contextNodeId;
|
||||||
const store = useStoreApi();
|
const store = useStoreApi();
|
||||||
const resizeControlRef = useRef<HTMLDivElement>(null);
|
const resizeControlRef = useRef<HTMLDivElement>(null);
|
||||||
const defaultPosition = variant === ResizeControlVariant.Line ? 'right' : 'bottom-right';
|
const isHandleControl = variant === ResizeControlVariant.Handle;
|
||||||
const controlPosition = position ?? defaultPosition;
|
const scale = useStore(
|
||||||
|
useCallback(scaleSelector(isHandleControl && autoScale), [isHandleControl, autoScale]),
|
||||||
|
shallow
|
||||||
|
);
|
||||||
const resizer = useRef<XYResizerInstance | null>(null);
|
const resizer = useRef<XYResizerInstance | null>(null);
|
||||||
|
const controlPosition = position ?? defaultPositions[variant];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!resizeControlRef.current || !id) {
|
if (!resizeControlRef.current || !id) {
|
||||||
@@ -192,14 +207,16 @@ function ResizeControl({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const positionClassNames = controlPosition.split('-');
|
const positionClassNames = controlPosition.split('-');
|
||||||
const colorStyleProp = variant === ResizeControlVariant.Line ? 'borderColor' : 'backgroundColor';
|
|
||||||
const controlStyle = color ? { ...style, [colorStyleProp]: color } : style;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cc(['react-flow__resize-control', 'nodrag', ...positionClassNames, variant, className])}
|
className={cc(['react-flow__resize-control', 'nodrag', ...positionClassNames, variant, className])}
|
||||||
ref={resizeControlRef}
|
ref={resizeControlRef}
|
||||||
style={controlStyle}
|
style={{
|
||||||
|
...style,
|
||||||
|
scale,
|
||||||
|
...(color && { [isHandleControl ? 'backgroundColor' : 'borderColor']: color }),
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export function NodeResizer({
|
|||||||
maxWidth = Number.MAX_VALUE,
|
maxWidth = Number.MAX_VALUE,
|
||||||
maxHeight = Number.MAX_VALUE,
|
maxHeight = Number.MAX_VALUE,
|
||||||
keepAspectRatio = false,
|
keepAspectRatio = false,
|
||||||
|
autoScale = true,
|
||||||
shouldResize,
|
shouldResize,
|
||||||
onResizeStart,
|
onResizeStart,
|
||||||
onResize,
|
onResize,
|
||||||
@@ -66,6 +67,7 @@ export function NodeResizer({
|
|||||||
maxHeight={maxHeight}
|
maxHeight={maxHeight}
|
||||||
onResizeStart={onResizeStart}
|
onResizeStart={onResizeStart}
|
||||||
keepAspectRatio={keepAspectRatio}
|
keepAspectRatio={keepAspectRatio}
|
||||||
|
autoScale={autoScale}
|
||||||
shouldResize={shouldResize}
|
shouldResize={shouldResize}
|
||||||
onResize={onResize}
|
onResize={onResize}
|
||||||
onResizeEnd={onResizeEnd}
|
onResizeEnd={onResizeEnd}
|
||||||
@@ -85,6 +87,7 @@ export function NodeResizer({
|
|||||||
maxHeight={maxHeight}
|
maxHeight={maxHeight}
|
||||||
onResizeStart={onResizeStart}
|
onResizeStart={onResizeStart}
|
||||||
keepAspectRatio={keepAspectRatio}
|
keepAspectRatio={keepAspectRatio}
|
||||||
|
autoScale={autoScale}
|
||||||
shouldResize={shouldResize}
|
shouldResize={shouldResize}
|
||||||
onResize={onResize}
|
onResize={onResize}
|
||||||
onResizeEnd={onResizeEnd}
|
onResizeEnd={onResizeEnd}
|
||||||
|
|||||||
@@ -59,6 +59,11 @@ export type NodeResizerProps = {
|
|||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
keepAspectRatio?: boolean;
|
keepAspectRatio?: boolean;
|
||||||
|
/**
|
||||||
|
* Scale the controls with the zoom level.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
autoScale?: boolean;
|
||||||
/** Callback to determine if node should resize. */
|
/** Callback to determine if node should resize. */
|
||||||
shouldResize?: ShouldResize;
|
shouldResize?: ShouldResize;
|
||||||
/** Callback called when resizing starts. */
|
/** Callback called when resizing starts. */
|
||||||
@@ -82,6 +87,7 @@ export type ResizeControlProps = Pick<
|
|||||||
| 'maxHeight'
|
| 'maxHeight'
|
||||||
| 'keepAspectRatio'
|
| 'keepAspectRatio'
|
||||||
| 'shouldResize'
|
| 'shouldResize'
|
||||||
|
| 'autoScale'
|
||||||
| 'onResizeStart'
|
| 'onResizeStart'
|
||||||
| 'onResize'
|
| 'onResize'
|
||||||
| 'onResizeEnd'
|
| 'onResizeEnd'
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
handleStyle,
|
handleStyle,
|
||||||
lineClass,
|
lineClass,
|
||||||
lineStyle,
|
lineStyle,
|
||||||
|
autoScale = true,
|
||||||
...rest
|
...rest
|
||||||
}: NodeResizerProps = $props();
|
}: NodeResizerProps = $props();
|
||||||
</script>
|
</script>
|
||||||
@@ -25,11 +26,19 @@
|
|||||||
style={lineStyle}
|
style={lineStyle}
|
||||||
{nodeId}
|
{nodeId}
|
||||||
{position}
|
{position}
|
||||||
|
{autoScale}
|
||||||
variant={ResizeControlVariant.Line}
|
variant={ResizeControlVariant.Line}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
{#each XY_RESIZER_HANDLE_POSITIONS as position (position)}
|
{#each XY_RESIZER_HANDLE_POSITIONS as position (position)}
|
||||||
<ResizeControl class={handleClass} style={handleStyle} {nodeId} {position} {...rest} />
|
<ResizeControl
|
||||||
|
class={handleClass}
|
||||||
|
style={handleStyle}
|
||||||
|
{nodeId}
|
||||||
|
{position}
|
||||||
|
{autoScale}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
maxWidth = Number.MAX_VALUE,
|
maxWidth = Number.MAX_VALUE,
|
||||||
maxHeight = Number.MAX_VALUE,
|
maxHeight = Number.MAX_VALUE,
|
||||||
keepAspectRatio = false,
|
keepAspectRatio = false,
|
||||||
|
autoScale = true,
|
||||||
shouldResize,
|
shouldResize,
|
||||||
onResizeStart,
|
onResizeStart,
|
||||||
onResize,
|
onResize,
|
||||||
@@ -40,10 +41,10 @@
|
|||||||
let resizeControlRef: HTMLDivElement;
|
let resizeControlRef: HTMLDivElement;
|
||||||
let resizer: XYResizerInstance | null = $state(null);
|
let resizer: XYResizerInstance | null = $state(null);
|
||||||
|
|
||||||
|
let isLineVariant = $derived(variant === ResizeControlVariant.Line);
|
||||||
|
|
||||||
let controlPosition = $derived.by(() => {
|
let controlPosition = $derived.by(() => {
|
||||||
let defaultPosition = (
|
let defaultPosition = (isLineVariant ? 'right' : 'bottom-right') as ControlPosition;
|
||||||
variant === ResizeControlVariant.Line ? 'right' : 'bottom-right'
|
|
||||||
) as ControlPosition;
|
|
||||||
return position ?? defaultPosition;
|
return position ?? defaultPosition;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -119,8 +120,9 @@
|
|||||||
<div
|
<div
|
||||||
class={['svelte-flow__resize-control', store.noDragClass, ...positionClasses, variant, className]}
|
class={['svelte-flow__resize-control', store.noDragClass, ...positionClasses, variant, className]}
|
||||||
bind:this={resizeControlRef}
|
bind:this={resizeControlRef}
|
||||||
style:border-color={variant === ResizeControlVariant.Line ? color : undefined}
|
style:border-color={isLineVariant ? color : undefined}
|
||||||
style:background-color={variant === ResizeControlVariant.Line ? undefined : color}
|
style:background-color={isLineVariant ? undefined : color}
|
||||||
|
style:scale={isLineVariant || !autoScale ? undefined : Math.max(1 / store.viewport.zoom, 1)}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ export type NodeResizerProps = {
|
|||||||
maxHeight?: number;
|
maxHeight?: number;
|
||||||
/** Keep aspect ratio when resizing */
|
/** Keep aspect ratio when resizing */
|
||||||
keepAspectRatio?: boolean;
|
keepAspectRatio?: boolean;
|
||||||
|
/** Automatically scale the node when resizing */
|
||||||
|
autoScale?: boolean;
|
||||||
/** Callback to determine if node should resize */
|
/** Callback to determine if node should resize */
|
||||||
shouldResize?: ShouldResize;
|
shouldResize?: ShouldResize;
|
||||||
/** Callback called when resizing starts */
|
/** Callback called when resizing starts */
|
||||||
@@ -55,6 +57,7 @@ export type ResizeControlProps = Pick<
|
|||||||
| 'maxWidth'
|
| 'maxWidth'
|
||||||
| 'maxHeight'
|
| 'maxHeight'
|
||||||
| 'keepAspectRatio'
|
| 'keepAspectRatio'
|
||||||
|
| 'autoScale'
|
||||||
| 'shouldResize'
|
| 'shouldResize'
|
||||||
| 'onResizeStart'
|
| 'onResizeStart'
|
||||||
| 'onResize'
|
| 'onResize'
|
||||||
|
|||||||
@@ -28,12 +28,12 @@
|
|||||||
|
|
||||||
/* handle styles */
|
/* handle styles */
|
||||||
.xy-flow__resize-control.handle {
|
.xy-flow__resize-control.handle {
|
||||||
width: 4px;
|
width: 5px;
|
||||||
height: 4px;
|
height: 5px;
|
||||||
border: 1px solid #fff;
|
border: 1px solid #fff;
|
||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
background-color: var(--xy-resize-background-color, var(--xy-resize-background-color-default));
|
background-color: var(--xy-resize-background-color, var(--xy-resize-background-color-default));
|
||||||
transform: translate(-50%, -50%);
|
translate: -50% -50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.xy-flow__resize-control.handle.left {
|
.xy-flow__resize-control.handle.left {
|
||||||
|
|||||||
Reference in New Issue
Block a user