Merge pull request #5528 from xyflow/resizer-update-keep-aspect

NodeResizer: Make parameters dynamic
This commit is contained in:
Moritz Klack
2025-10-01 11:22:35 +02:00
committed by GitHub
9 changed files with 74 additions and 14 deletions
+7
View File
@@ -0,0 +1,7 @@
---
'@xyflow/system': patch
'@xyflow/react': patch
'@xyflow/svelte': patch
---
Let NodeResizer props change during ongoing resize
@@ -1,7 +1,9 @@
import { memo, FC } from 'react'; import { memo, FC } from 'react';
import { Handle, Position, NodeProps, NodeResizer } from '@xyflow/react'; import { Handle, Position, NodeProps, NodeResizer, useKeyPress } from '@xyflow/react';
const DefaultResizerNode: FC<NodeProps> = ({ data, selected }) => { const DefaultResizerNode: FC<NodeProps> = ({ data, selected }) => {
const keepAspectRatio = useKeyPress('k');
return ( return (
<> <>
<NodeResizer <NodeResizer
@@ -14,7 +16,7 @@ const DefaultResizerNode: FC<NodeProps> = ({ data, selected }) => {
onResizeStart={data.onResizeStart ?? undefined} onResizeStart={data.onResizeStart ?? undefined}
onResize={data.onResize ?? undefined} onResize={data.onResize ?? undefined}
onResizeEnd={data.onResizeEnd ?? undefined} onResizeEnd={data.onResizeEnd ?? undefined}
keepAspectRatio={data.keepAspectRatio ?? undefined} keepAspectRatio={keepAspectRatio || (data.keepAspectRatio ?? undefined)}
/> />
<Handle type="target" position={Position.Left} /> <Handle type="target" position={Position.Left} />
<div>{data.label}</div> <div>{data.label}</div>
@@ -9,6 +9,7 @@
import '@xyflow/svelte/dist/style.css'; import '@xyflow/svelte/dist/style.css';
import type { ResizeNode } from './types'; import type { ResizeNode } from './types';
import { store } from './store.svelte';
const nodeTypes = { const nodeTypes = {
defaultResizer: DefaultResizer, defaultResizer: DefaultResizer,
@@ -139,6 +140,15 @@
let snapToGrid = $state(false); let snapToGrid = $state(false);
</script> </script>
<svelte:window
on:keydown={(e) => {
if (e.key === 'k') store.keepAspectRatio = true;
}}
on:keyup={(e) => {
if (e.key === 'k') store.keepAspectRatio = false;
}}
/>
<SvelteFlow <SvelteFlow
bind:nodes bind:nodes
bind:edges bind:edges
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Handle, Position, type NodeProps, NodeResizeControl, type Node } from '@xyflow/svelte'; import { Handle, Position, type NodeProps, NodeResizeControl, type Node } from '@xyflow/svelte';
import type { ResizeNode } from './types'; import type { ResizeNode } from './types';
import { store } from './store.svelte';
let { data }: NodeProps<ResizeNode> = $props(); let { data }: NodeProps<ResizeNode> = $props();
</script> </script>
@@ -14,7 +15,7 @@
onResizeStart={data.onResizeStart ?? undefined} onResizeStart={data.onResizeStart ?? undefined}
onResize={data.onResize ?? undefined} onResize={data.onResize ?? undefined}
onResizeEnd={data.onResizeEnd ?? undefined} onResizeEnd={data.onResizeEnd ?? undefined}
keepAspectRatio={data.keepAspectRatio ?? undefined} keepAspectRatio={store.keepAspectRatio || (data.keepAspectRatio ?? undefined)}
style="background: transparent; border: none;" style="background: transparent; border: none;"
> >
<svg <svg
@@ -2,6 +2,8 @@
import { Handle, NodeResizer, Position, type NodeProps } from '@xyflow/svelte'; import { Handle, NodeResizer, Position, type NodeProps } from '@xyflow/svelte';
import type { ResizeNode } from './types'; import type { ResizeNode } from './types';
import { store } from './store.svelte';
let { data, selected }: NodeProps<ResizeNode> = $props(); let { data, selected }: NodeProps<ResizeNode> = $props();
</script> </script>
@@ -15,7 +17,7 @@
onResizeStart={data.onResizeStart ?? undefined} onResizeStart={data.onResizeStart ?? undefined}
onResize={data.onResize ?? undefined} onResize={data.onResize ?? undefined}
onResizeEnd={data.onResizeEnd ?? undefined} onResizeEnd={data.onResizeEnd ?? undefined}
keepAspectRatio={data.keepAspectRatio ?? undefined} keepAspectRatio={store.keepAspectRatio || (data.keepAspectRatio ?? undefined)}
/> />
<Handle type="target" position={Position.Left} /> <Handle type="target" position={Position.Left} />
<div>{data.label}</div> <div>{data.label}</div>
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Handle, NodeResizeControl, Position, type NodeProps } from '@xyflow/svelte'; import { Handle, NodeResizeControl, Position, type NodeProps } from '@xyflow/svelte';
import type { ResizeNode } from './types'; import type { ResizeNode } from './types';
import { store } from './store.svelte';
let { data }: NodeProps<ResizeNode> = $props(); let { data }: NodeProps<ResizeNode> = $props();
</script> </script>
@@ -14,7 +15,7 @@
onResizeStart={data.onResizeStart ?? undefined} onResizeStart={data.onResizeStart ?? undefined}
onResize={data.onResize ?? undefined} onResize={data.onResize ?? undefined}
onResizeEnd={data.onResizeEnd ?? undefined} onResizeEnd={data.onResizeEnd ?? undefined}
keepAspectRatio={data.keepAspectRatio ?? undefined} keepAspectRatio={store.keepAspectRatio || (data.keepAspectRatio ?? undefined)}
color="red" color="red"
position={Position.Left} position={Position.Left}
/> />
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Handle, NodeResizeControl, Position, type NodeProps } from '@xyflow/svelte'; import { Handle, NodeResizeControl, Position, type NodeProps } from '@xyflow/svelte';
import type { ResizeNode } from './types'; import type { ResizeNode } from './types';
import { store } from './store.svelte';
let { data }: NodeProps<ResizeNode> = $props(); let { data }: NodeProps<ResizeNode> = $props();
</script> </script>
@@ -14,7 +15,7 @@
onResizeStart={data.onResizeStart ?? undefined} onResizeStart={data.onResizeStart ?? undefined}
onResize={data.onResize ?? undefined} onResize={data.onResize ?? undefined}
onResizeEnd={data.onResizeEnd ?? undefined} onResizeEnd={data.onResizeEnd ?? undefined}
keepAspectRatio={data.keepAspectRatio ?? undefined} keepAspectRatio={store.keepAspectRatio || (data.keepAspectRatio ?? undefined)}
color="red" color="red"
position={Position.Top} position={Position.Top}
/> />
@@ -0,0 +1,14 @@
function createStore() {
let keepAspectRatio = $state(false);
return {
get keepAspectRatio() {
return keepAspectRatio;
},
set keepAspectRatio(value: boolean) {
keepAspectRatio = value;
}
};
}
export const store = createStore();
+30 -8
View File
@@ -104,6 +104,18 @@ function nodeToChildExtent(child: NodeBase, parent: NodeBase, nodeOrigin: NodeOr
export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: XYResizerParams): XYResizerInstance { export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: XYResizerParams): XYResizerInstance {
const selection = select(domNode); const selection = select(domNode);
let params = {
controlDirection: getControlDirection('bottom-right'),
boundaries: {
minWidth: 0,
minHeight: 0,
maxWidth: Number.MAX_VALUE,
maxHeight: Number.MAX_VALUE,
},
resizeDirection: undefined as ResizeControlDirection | undefined,
keepAspectRatio: false,
};
function update({ function update({
controlPosition, controlPosition,
boundaries, boundaries,
@@ -117,7 +129,12 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X
let prevValues = { ...initPrevValues }; let prevValues = { ...initPrevValues };
let startValues = { ...initStartValues }; let startValues = { ...initStartValues };
const controlDirection = getControlDirection(controlPosition); params = {
boundaries,
resizeDirection,
keepAspectRatio,
controlDirection: getControlDirection(controlPosition),
};
let node: InternalNodeBase | undefined = undefined; let node: InternalNodeBase | undefined = undefined;
let containerBounds: DOMRect | null = null; let containerBounds: DOMRect | null = null;
@@ -212,16 +229,17 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X
if (!node) { if (!node) {
return; return;
} }
const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues; const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues;
const change: XYResizerChange = {}; const change: XYResizerChange = {};
const nodeOrigin = node.origin ?? storeNodeOrigin; const nodeOrigin = node.origin ?? storeNodeOrigin;
const { width, height, x, y } = getDimensionsAfterResize( const { width, height, x, y } = getDimensionsAfterResize(
startValues, startValues,
controlDirection, params.controlDirection,
pointerPosition, pointerPosition,
boundaries, params.boundaries,
keepAspectRatio, params.keepAspectRatio,
nodeOrigin, nodeOrigin,
parentExtent, parentExtent,
childExtent childExtent
@@ -264,9 +282,13 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X
if (isWidthChange || isHeightChange) { if (isWidthChange || isHeightChange) {
change.width = change.width =
isWidthChange && (!resizeDirection || resizeDirection === 'horizontal') ? width : prevValues.width; isWidthChange && (!params.resizeDirection || params.resizeDirection === 'horizontal')
? width
: prevValues.width;
change.height = change.height =
isHeightChange && (!resizeDirection || resizeDirection === 'vertical') ? height : prevValues.height; isHeightChange && (!params.resizeDirection || params.resizeDirection === 'vertical')
? height
: prevValues.height;
prevValues.width = change.width; prevValues.width = change.width;
prevValues.height = change.height; prevValues.height = change.height;
} }
@@ -291,8 +313,8 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }: X
prevWidth, prevWidth,
height: prevValues.height, height: prevValues.height,
prevHeight, prevHeight,
affectsX: controlDirection.affectsX, affectsX: params.controlDirection.affectsX,
affectsY: controlDirection.affectsY, affectsY: params.controlDirection.affectsY,
}); });
const nextValues = { ...prevValues, direction }; const nextValues = { ...prevValues, direction };