diff --git a/examples/react/src/examples/NodeResizer/index.tsx b/examples/react/src/examples/NodeResizer/index.tsx index 11ce29d2..f8e46940 100644 --- a/examples/react/src/examples/NodeResizer/index.tsx +++ b/examples/react/src/examples/NodeResizer/index.tsx @@ -116,6 +116,29 @@ const initialNodes: Node[] = [ position: { x: 250, y: 400 }, style: { ...nodeStyle }, }, + { + id: '5', + type: 'defaultResizer', + data: { label: 'Parent' }, + position: { x: 700, y: 0 }, + style: { ...nodeStyle, width: 300, height: 300 }, + }, + { + id: '5a', + type: 'defaultResizer', + data: { label: 'Child' }, + position: { x: 50, y: 50 }, + parentNode: '5', + style: { ...nodeStyle }, + }, + { + id: '5b', + type: 'defaultResizer', + data: { label: 'Child' }, + position: { x: 100, y: 100 }, + parentNode: '5', + style: { ...nodeStyle }, + }, ]; const CustomNodeFlow = () => { diff --git a/examples/svelte/src/routes/examples/node-resizer/+page.svelte b/examples/svelte/src/routes/examples/node-resizer/+page.svelte index 61257ee1..39778156 100644 --- a/examples/svelte/src/routes/examples/node-resizer/+page.svelte +++ b/examples/svelte/src/routes/examples/node-resizer/+page.svelte @@ -100,6 +100,29 @@ data: { label: 'horizontal resizer with maxWidth', maxWidth: 300 }, position: { x: 250, y: 400 }, style: nodeStyle + }, + { + id: '5', + type: 'defaultResizer', + data: { label: 'Parent' }, + position: { x: 700, y: 0 }, + style: nodeStyle + 'width: 300px; height: 300px' + }, + { + id: '5a', + type: 'defaultResizer', + data: { label: 'Child' }, + position: { x: 50, y: 50 }, + parentNode: '5', + style: nodeStyle + }, + { + id: '5b', + type: 'defaultResizer', + data: { label: 'Child' }, + position: { x: 100, y: 100 }, + parentNode: '5', + style: nodeStyle } ]); diff --git a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx index 2f16f150..525b68f7 100644 --- a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx +++ b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx @@ -1,6 +1,12 @@ import { useRef, useEffect, memo } from 'react'; import cc from 'classcat'; -import { XYResizer, ResizeControlVariant, type XYResizerInstance, type XYResizerChange } from '@xyflow/system'; +import { + XYResizer, + ResizeControlVariant, + type XYResizerInstance, + type XYResizerChange, + XYResizerChildChange, +} from '@xyflow/system'; import { useStoreApi } from '../../hooks/useStore'; import { useNodeId } from '../../contexts/NodeIdContext'; @@ -52,7 +58,7 @@ function ResizeControl({ snapToGrid, }; }, - onChange: (change: XYResizerChange) => { + onChange: (change: XYResizerChange, childChanges: XYResizerChildChange[]) => { const { triggerNodeChanges } = store.getState(); const changes: NodeChange[] = []; @@ -83,6 +89,17 @@ function ResizeControl({ changes.push(dimensionChange); } + + childChanges.forEach((childChange) => { + const positionChange: NodePositionChange = { + id: childChange.id, + type: 'position', + position: childChange.position, + }; + + changes.push(positionChange); + }); + triggerNodeChanges(changes); }, onEnd: () => { diff --git a/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte b/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte index 4688b4e7..3ec0ad71 100644 --- a/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte +++ b/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte @@ -7,7 +7,8 @@ ResizeControlVariant, type ControlPosition, type XYResizerInstance, - type XYResizerChange + type XYResizerChange, + type XYResizerChildChange } from '@xyflow/system'; import type { ResizeControlProps } from './types'; @@ -66,7 +67,7 @@ snapToGrid: !!$snapGrid }; }, - onChange: (change: XYResizerChange) => { + onChange: (change: XYResizerChange, childChanges: XYResizerChildChange[]) => { const node = $nodeLookup.get(id); if (node) { node.height = change.isHeightChange ? change.height : node.height; @@ -76,6 +77,13 @@ ? { x: change.x, y: change.y } : node.position; + childChanges.forEach((childChange) => { + const childNode = $nodeLookup.get(childChange.id); + if (childNode) { + childNode.position = childChange.position; + } + }); + $nodes = $nodes; } } diff --git a/packages/system/src/xyresizer/XYResizer.ts b/packages/system/src/xyresizer/XYResizer.ts index 63f146d5..c4c97237 100644 --- a/packages/system/src/xyresizer/XYResizer.ts +++ b/packages/system/src/xyresizer/XYResizer.ts @@ -3,7 +3,7 @@ import { select } from 'd3-selection'; import { getControlDirection, getDimensionsAfterResize, getPositionAfterResize, getResizeDirection } from './utils'; import { getPointerPosition } from '../utils'; -import type { NodeLookup, Transform } from '../types'; +import type { NodeBase, NodeLookup, Transform } from '../types'; import type { OnResize, OnResizeEnd, OnResizeStart, ResizeDragEvent, ShouldResize, ControlPosition } from './types'; const initPrevValues = { width: 0, height: 0, x: 0, y: 0 }; @@ -28,6 +28,14 @@ const initChange = { export type XYResizerChange = typeof initChange; +export type XYResizerChildChange = { + id: string; + position: { + x: number; + y: number; + }; +}; + type XYResizerParams = { domNode: HTMLDivElement; nodeId: string; @@ -37,7 +45,7 @@ type XYResizerParams = { snapGrid?: [number, number]; snapToGrid: boolean; }; - onChange: (changes: XYResizerChange) => void; + onChange: (changes: XYResizerChange, childChanges: XYResizerChildChange[]) => void; onEnd?: () => void; }; @@ -78,6 +86,8 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange }: XYResize const controlDirection = getControlDirection(controlPosition); + let childNodes: XYResizerChildChange[] = []; + const dragHandler = drag() .on('start', (event: ResizeDragEvent) => { const { nodeLookup, transform, snapGrid, snapToGrid } = getStoreItems(); @@ -98,6 +108,15 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange }: XYResize aspectRatio: prevValues.width / prevValues.height, }; + childNodes = []; + nodeLookup.forEach((_node, _nodeId) => { + if (_node.parentNode === nodeId) { + childNodes.push({ + id: _nodeId, + position: { ..._node.position }, + }); + } + }); onResizeStart?.(event, { ...prevValues }); }) .on('drag', (event: ResizeDragEvent) => { @@ -105,6 +124,8 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange }: XYResize const pointerPosition = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid }); const node = nodeLookup.get(nodeId); + let childChanges: XYResizerChildChange[] = []; + if (node) { const change = { ...initChange }; @@ -137,6 +158,18 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange }: XYResize prevValues.x = change.x; prevValues.y = change.y; } + + if (childNodes.length > 0) { + const xChange = x - prevX; + const yChange = y - prevY; + childNodes.forEach((childNode) => { + childNode.position = { + x: childNode.position.x - xChange, + y: childNode.position.y - yChange, + }; + childChanges.push(childNode); + }); + } } if (isWidthChange || isHeightChange) { @@ -170,7 +203,7 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange }: XYResize } onResize?.(event, nextValues); - onChange(change); + onChange(change, childChanges); } }) .on('end', (event: ResizeDragEvent) => {