diff --git a/.changeset/short-bugs-stare.md b/.changeset/short-bugs-stare.md new file mode 100644 index 00000000..1ac80cac --- /dev/null +++ b/.changeset/short-bugs-stare.md @@ -0,0 +1,7 @@ +--- +'@xyflow/react': minor +'@xyflow/svelte': minor +'@xyflow/system': patch +--- + +Prevent NodeResizer controls to become too small when zooming out diff --git a/examples/react/src/examples/NodeResizer/BottomRightResizer.tsx b/examples/react/src/examples/NodeResizer/BottomRightResizer.tsx index ca860fdf..38ad14cd 100644 --- a/examples/react/src/examples/NodeResizer/BottomRightResizer.tsx +++ b/examples/react/src/examples/NodeResizer/BottomRightResizer.tsx @@ -10,6 +10,8 @@ const CustomResizerNode: FC = ({ data }) => { resizeDirection="horizontal" minWidth={100} maxWidth={500} + color="orange" + autoScale={false} />
{data.label}
diff --git a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx index beaeb527..d75c2606 100644 --- a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx +++ b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx @@ -1,5 +1,6 @@ -import { useRef, useEffect, memo } from 'react'; +import { useRef, useEffect, memo, useCallback } from 'react'; import cc from 'classcat'; +import { shallow } from 'zustand/shallow'; import { XYResizer, ResizeControlVariant, @@ -13,18 +14,28 @@ import { evaluateAbsolutePosition, ParentExpandChild, XYPosition, + ControlPosition, } from '@xyflow/system'; -import { useStoreApi } from '../../hooks/useStore'; +import { useStoreApi, useStore } from '../../hooks/useStore'; import { useNodeId } from '../../contexts/NodeIdContext'; 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.Line]: 'right', + [ResizeControlVariant.Handle]: 'bottom-right', +}; function ResizeControl({ nodeId, position, variant = ResizeControlVariant.Handle, className, - style = {}, + style = undefined, children, color, minWidth = 10, @@ -33,6 +44,7 @@ function ResizeControl({ maxHeight = Number.MAX_VALUE, keepAspectRatio = false, resizeDirection, + autoScale = true, shouldResize, onResizeStart, onResize, @@ -42,10 +54,13 @@ function ResizeControl({ const id = typeof nodeId === 'string' ? nodeId : contextNodeId; const store = useStoreApi(); const resizeControlRef = useRef(null); - const defaultPosition = variant === ResizeControlVariant.Line ? 'right' : 'bottom-right'; - const controlPosition = position ?? defaultPosition; - + const isHandleControl = variant === ResizeControlVariant.Handle; + const scale = useStore( + useCallback(scaleSelector(isHandleControl && autoScale), [isHandleControl, autoScale]), + shallow + ); const resizer = useRef(null); + const controlPosition = position ?? defaultPositions[variant]; useEffect(() => { if (!resizeControlRef.current || !id) { @@ -192,14 +207,16 @@ function ResizeControl({ ]); const positionClassNames = controlPosition.split('-'); - const colorStyleProp = variant === ResizeControlVariant.Line ? 'borderColor' : 'backgroundColor'; - const controlStyle = color ? { ...style, [colorStyleProp]: color } : style; return (
{children}
diff --git a/packages/react/src/additional-components/NodeResizer/NodeResizer.tsx b/packages/react/src/additional-components/NodeResizer/NodeResizer.tsx index 434be4b7..b869d207 100644 --- a/packages/react/src/additional-components/NodeResizer/NodeResizer.tsx +++ b/packages/react/src/additional-components/NodeResizer/NodeResizer.tsx @@ -40,6 +40,7 @@ export function NodeResizer({ maxWidth = Number.MAX_VALUE, maxHeight = Number.MAX_VALUE, keepAspectRatio = false, + autoScale = true, shouldResize, onResizeStart, onResize, @@ -66,6 +67,7 @@ export function NodeResizer({ maxHeight={maxHeight} onResizeStart={onResizeStart} keepAspectRatio={keepAspectRatio} + autoScale={autoScale} shouldResize={shouldResize} onResize={onResize} onResizeEnd={onResizeEnd} @@ -85,6 +87,7 @@ export function NodeResizer({ maxHeight={maxHeight} onResizeStart={onResizeStart} keepAspectRatio={keepAspectRatio} + autoScale={autoScale} shouldResize={shouldResize} onResize={onResize} onResizeEnd={onResizeEnd} diff --git a/packages/react/src/additional-components/NodeResizer/types.ts b/packages/react/src/additional-components/NodeResizer/types.ts index e6dd9d36..eb70a115 100644 --- a/packages/react/src/additional-components/NodeResizer/types.ts +++ b/packages/react/src/additional-components/NodeResizer/types.ts @@ -59,6 +59,11 @@ export type NodeResizerProps = { * @default false */ keepAspectRatio?: boolean; + /** + * Scale the controls with the zoom level. + * @default true + */ + autoScale?: boolean; /** Callback to determine if node should resize. */ shouldResize?: ShouldResize; /** Callback called when resizing starts. */ @@ -82,6 +87,7 @@ export type ResizeControlProps = Pick< | 'maxHeight' | 'keepAspectRatio' | 'shouldResize' + | 'autoScale' | 'onResizeStart' | 'onResize' | 'onResizeEnd' diff --git a/packages/svelte/src/lib/plugins/NodeResizer/NodeResizer.svelte b/packages/svelte/src/lib/plugins/NodeResizer/NodeResizer.svelte index 8ced5ba3..0fa4bd3d 100644 --- a/packages/svelte/src/lib/plugins/NodeResizer/NodeResizer.svelte +++ b/packages/svelte/src/lib/plugins/NodeResizer/NodeResizer.svelte @@ -14,6 +14,7 @@ handleStyle, lineClass, lineStyle, + autoScale = true, ...rest }: NodeResizerProps = $props(); @@ -25,11 +26,19 @@ style={lineStyle} {nodeId} {position} + {autoScale} variant={ResizeControlVariant.Line} {...rest} /> {/each} {#each XY_RESIZER_HANDLE_POSITIONS as position (position)} - + {/each} {/if} diff --git a/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte b/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte index b30ecc47..b9f38214 100644 --- a/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte +++ b/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte @@ -22,6 +22,7 @@ maxWidth = Number.MAX_VALUE, maxHeight = Number.MAX_VALUE, keepAspectRatio = false, + autoScale = true, shouldResize, onResizeStart, onResize, @@ -40,10 +41,10 @@ let resizeControlRef: HTMLDivElement; let resizer: XYResizerInstance | null = $state(null); + let isLineVariant = $derived(variant === ResizeControlVariant.Line); + let controlPosition = $derived.by(() => { - let defaultPosition = ( - variant === ResizeControlVariant.Line ? 'right' : 'bottom-right' - ) as ControlPosition; + let defaultPosition = (isLineVariant ? 'right' : 'bottom-right') as ControlPosition; return position ?? defaultPosition; }); @@ -119,8 +120,9 @@
{@render children?.()} diff --git a/packages/svelte/src/lib/plugins/NodeResizer/types.ts b/packages/svelte/src/lib/plugins/NodeResizer/types.ts index 23140c52..5f9bb95e 100644 --- a/packages/svelte/src/lib/plugins/NodeResizer/types.ts +++ b/packages/svelte/src/lib/plugins/NodeResizer/types.ts @@ -36,6 +36,8 @@ export type NodeResizerProps = { maxHeight?: number; /** Keep aspect ratio when resizing */ keepAspectRatio?: boolean; + /** Automatically scale the node when resizing */ + autoScale?: boolean; /** Callback to determine if node should resize */ shouldResize?: ShouldResize; /** Callback called when resizing starts */ @@ -55,6 +57,7 @@ export type ResizeControlProps = Pick< | 'maxWidth' | 'maxHeight' | 'keepAspectRatio' + | 'autoScale' | 'shouldResize' | 'onResizeStart' | 'onResize' diff --git a/packages/system/src/styles/node-resizer.css b/packages/system/src/styles/node-resizer.css index 1c031d45..4c69084b 100644 --- a/packages/system/src/styles/node-resizer.css +++ b/packages/system/src/styles/node-resizer.css @@ -28,12 +28,12 @@ /* handle styles */ .xy-flow__resize-control.handle { - width: 4px; - height: 4px; + width: 5px; + height: 5px; border: 1px solid #fff; border-radius: 1px; 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 {