Naive copy&paste implementation of node resizer in svelte, some stuff pulled out to system
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
<script lang="ts">
|
||||
import ResizeControl from './ResizeControl.svelte';
|
||||
import type { NodeResizerProps } from './types';
|
||||
import {
|
||||
XY_RESIZER_HANDLE_CONTROLS,
|
||||
XY_RESIZER_LINE_CONTROLS,
|
||||
ResizeControlVariant
|
||||
} from '@xyflow/system';
|
||||
|
||||
type $$Props = NodeResizerProps;
|
||||
|
||||
export let nodeId: $$Props['nodeId'] = undefined;
|
||||
export let isVisible: $$Props['isVisible'] = true;
|
||||
export let handleClass: $$Props['handleClass'] = undefined;
|
||||
export let handleStyle: $$Props['handleStyle'] = undefined;
|
||||
export let lineClass: $$Props['lineClass'] = undefined;
|
||||
export let lineStyle: $$Props['lineStyle'] = undefined;
|
||||
export let color: $$Props['color'] = undefined;
|
||||
export let minWidth: $$Props['minWidth'] = 10;
|
||||
export let minHeight: $$Props['minHeight'] = 10;
|
||||
export let maxWidth: $$Props['maxWidth'] = Number.MAX_VALUE;
|
||||
export let maxHeight: $$Props['maxHeight'] = Number.MAX_VALUE;
|
||||
export let keepAspectRatio: $$Props['keepAspectRatio'] = false;
|
||||
export let shouldResize: $$Props['shouldResize'] = undefined;
|
||||
export let onResizeStart: $$Props['onResizeStart'] = undefined;
|
||||
export let onResize: $$Props['onResize'] = undefined;
|
||||
export let onResizeEnd: $$Props['onResizeEnd'] = undefined;
|
||||
|
||||
let _minWidth = minWidth || 10;
|
||||
let _minHeight = minHeight || 10;
|
||||
let _maxWidth = maxWidth || Number.MAX_VALUE;
|
||||
let _maxHeight = maxHeight || Number.MAX_VALUE;
|
||||
|
||||
$: console.log(isVisible);
|
||||
</script>
|
||||
|
||||
{#if isVisible}
|
||||
{#each XY_RESIZER_LINE_CONTROLS as c (c)}
|
||||
<ResizeControl
|
||||
class={lineClass}
|
||||
style={lineStyle}
|
||||
{nodeId}
|
||||
position={c}
|
||||
variant={ResizeControlVariant.Line}
|
||||
{color}
|
||||
minWidth={_minWidth}
|
||||
minHeight={_minHeight}
|
||||
maxWidth={_maxWidth}
|
||||
maxHeight={_maxHeight}
|
||||
{onResizeStart}
|
||||
{keepAspectRatio}
|
||||
{shouldResize}
|
||||
{onResize}
|
||||
{onResizeEnd}
|
||||
/>
|
||||
{/each}
|
||||
{#each XY_RESIZER_HANDLE_CONTROLS as c (c)}
|
||||
<ResizeControl
|
||||
class={handleClass}
|
||||
style={handleStyle}
|
||||
{nodeId}
|
||||
position={c}
|
||||
{color}
|
||||
minWidth={_minWidth}
|
||||
minHeight={_minHeight}
|
||||
maxWidth={_maxWidth}
|
||||
maxHeight={_maxHeight}
|
||||
{onResizeStart}
|
||||
{keepAspectRatio}
|
||||
{shouldResize}
|
||||
{onResize}
|
||||
{onResizeEnd}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
@@ -0,0 +1,234 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount } from 'svelte';
|
||||
import { drag } from 'd3-drag';
|
||||
import { select } from 'd3-selection';
|
||||
import cc from 'classcat';
|
||||
import type { ResizeControlProps, ResizeDragEvent } from './types';
|
||||
import {
|
||||
ResizeControlVariant,
|
||||
getPointerPosition,
|
||||
clamp,
|
||||
getResizeDirection
|
||||
} from '@xyflow/system';
|
||||
import { useStore } from '$lib/store';
|
||||
|
||||
type $$Props = ResizeControlProps;
|
||||
|
||||
export let nodeId: $$Props['nodeId'] = undefined;
|
||||
export let position: $$Props['position'] = undefined;
|
||||
export let variant: $$Props['variant'] = ResizeControlVariant.Handle;
|
||||
export let color: $$Props['color'] = undefined;
|
||||
export let minWidth: $$Props['minWidth'] = 10;
|
||||
$: _minWidth = minWidth ?? 10;
|
||||
export let minHeight: $$Props['minHeight'] = 10;
|
||||
$: _minHeight = minHeight ?? 10;
|
||||
export let maxWidth: $$Props['maxWidth'] = Number.MAX_VALUE;
|
||||
$: _maxWidth = maxWidth ?? Number.MAX_VALUE;
|
||||
export let maxHeight: $$Props['maxHeight'] = Number.MAX_VALUE;
|
||||
$: _maxHeight = maxHeight ?? Number.MAX_VALUE;
|
||||
export let keepAspectRatio: $$Props['keepAspectRatio'] = false;
|
||||
export let shouldResize: $$Props['shouldResize'] = undefined;
|
||||
export let onResizeStart: $$Props['onResizeStart'] = undefined;
|
||||
export let onResize: $$Props['onResize'] = undefined;
|
||||
export let onResizeEnd: $$Props['onResizeEnd'] = undefined;
|
||||
export let style: $$Props['style'] = '';
|
||||
let className: $$Props['class'] = '';
|
||||
export { className as class };
|
||||
|
||||
const initPrevValues = { width: 0, height: 0, x: 0, y: 0 };
|
||||
|
||||
const initStartValues = {
|
||||
...initPrevValues,
|
||||
pointerX: 0,
|
||||
pointerY: 0,
|
||||
aspectRatio: 1
|
||||
};
|
||||
|
||||
const { nodeLookup, snapGrid, viewport, nodes } = useStore();
|
||||
const contextNodeId = getContext<string>('svelteflow__node_id');
|
||||
$: id = typeof nodeId === 'string' ? nodeId : contextNodeId;
|
||||
let resizeControlRef: HTMLDivElement;
|
||||
|
||||
let startValues = { ...initStartValues };
|
||||
let prevValues = { ...initPrevValues };
|
||||
|
||||
$: defaultPosition = variant === ResizeControlVariant.Line ? 'right' : 'bottom-right';
|
||||
$: controlPosition = position ?? defaultPosition;
|
||||
|
||||
$: positionClassNames = controlPosition.split('-');
|
||||
$: colorStyleProp = variant === ResizeControlVariant.Line ? 'border-color' : 'background-color';
|
||||
|
||||
$: _style = style ?? '';
|
||||
$: controlStyle = !!color ? `${_style} ${colorStyleProp}: ${color};` : _style;
|
||||
|
||||
onMount(() => {
|
||||
if (!resizeControlRef || !id) {
|
||||
return;
|
||||
}
|
||||
const selection = select(resizeControlRef);
|
||||
|
||||
const enableX = controlPosition.includes('right') || controlPosition.includes('left');
|
||||
const enableY = controlPosition.includes('bottom') || controlPosition.includes('top');
|
||||
const invertX = controlPosition.includes('left');
|
||||
const invertY = controlPosition.includes('top');
|
||||
|
||||
const dragHandler = drag<HTMLDivElement, unknown>()
|
||||
.on('start', (event: ResizeDragEvent) => {
|
||||
const node = $nodeLookup.get(id);
|
||||
const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, {
|
||||
transform: [$viewport.x, $viewport.y, $viewport.zoom],
|
||||
snapGrid: $snapGrid ?? undefined,
|
||||
snapToGrid: !!$snapGrid
|
||||
});
|
||||
|
||||
prevValues = {
|
||||
width: node?.computed?.width ?? 0,
|
||||
height: node?.computed?.height ?? 0,
|
||||
x: node?.position.x ?? 0,
|
||||
y: node?.position.y ?? 0
|
||||
};
|
||||
|
||||
startValues = {
|
||||
...prevValues,
|
||||
pointerX: xSnapped,
|
||||
pointerY: ySnapped,
|
||||
aspectRatio: prevValues.width / prevValues.height
|
||||
};
|
||||
|
||||
onResizeStart?.(event, { ...prevValues });
|
||||
})
|
||||
.on('drag', (event: ResizeDragEvent) => {
|
||||
const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, {
|
||||
transform: [$viewport.x, $viewport.y, $viewport.zoom],
|
||||
snapGrid: $snapGrid ?? undefined,
|
||||
snapToGrid: !!$snapGrid
|
||||
});
|
||||
const node = $nodeLookup.get(id);
|
||||
|
||||
if (node) {
|
||||
const {
|
||||
pointerX: startX,
|
||||
pointerY: startY,
|
||||
width: startWidth,
|
||||
height: startHeight,
|
||||
x: startNodeX,
|
||||
y: startNodeY,
|
||||
aspectRatio
|
||||
} = startValues;
|
||||
|
||||
const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues;
|
||||
|
||||
const distX = Math.floor(enableX ? xSnapped - startX : 0);
|
||||
const distY = Math.floor(enableY ? ySnapped - startY : 0);
|
||||
|
||||
let width = clamp(startWidth + (invertX ? -distX : distX), minWidth, maxWidth);
|
||||
let height = clamp(startHeight + (invertY ? -distY : distY), minHeight, maxHeight);
|
||||
|
||||
if (keepAspectRatio) {
|
||||
const nextAspectRatio = width / height;
|
||||
const isDiagonal = enableX && enableY;
|
||||
const isHorizontal = enableX && !enableY;
|
||||
const isVertical = enableY && !enableX;
|
||||
|
||||
width =
|
||||
(nextAspectRatio <= aspectRatio && isDiagonal) || isVertical
|
||||
? height * aspectRatio
|
||||
: width;
|
||||
height =
|
||||
(nextAspectRatio > aspectRatio && isDiagonal) || isHorizontal
|
||||
? width / aspectRatio
|
||||
: height;
|
||||
|
||||
if (width >= _maxWidth) {
|
||||
width = _maxWidth;
|
||||
height = _maxWidth / aspectRatio;
|
||||
} else if (width <= _minWidth) {
|
||||
width = _minWidth;
|
||||
height = _minWidth / aspectRatio;
|
||||
}
|
||||
|
||||
if (height >= _maxHeight) {
|
||||
height = _maxHeight;
|
||||
width = _maxHeight * aspectRatio;
|
||||
} else if (height <= _minHeight) {
|
||||
height = _minHeight;
|
||||
width = _minHeight * aspectRatio;
|
||||
}
|
||||
}
|
||||
|
||||
const isWidthChange = width !== prevWidth;
|
||||
const isHeightChange = height !== prevHeight;
|
||||
|
||||
let changes = false;
|
||||
|
||||
if (invertX || invertY) {
|
||||
const x = invertX ? startNodeX - (width - startWidth) : startNodeX;
|
||||
const y = invertY ? startNodeY - (height - startHeight) : startNodeY;
|
||||
|
||||
// only transform the node if the width or height changes
|
||||
const isXPosChange = x !== prevX && isWidthChange;
|
||||
const isYPosChange = y !== prevY && isHeightChange;
|
||||
|
||||
if (isXPosChange || isYPosChange) {
|
||||
let newX = isXPosChange ? x : prevX;
|
||||
let newY = isYPosChange ? y : prevY;
|
||||
|
||||
node.position = { x: newX, y: newY };
|
||||
prevValues.x = newX;
|
||||
prevValues.y = newY;
|
||||
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isWidthChange || isHeightChange) {
|
||||
node.width = width;
|
||||
node.height = height;
|
||||
prevValues.width = width;
|
||||
prevValues.height = height;
|
||||
|
||||
changes = true;
|
||||
}
|
||||
|
||||
if (!changes) {
|
||||
return;
|
||||
}
|
||||
|
||||
const direction = getResizeDirection({
|
||||
width: prevValues.width,
|
||||
prevWidth,
|
||||
height: prevValues.height,
|
||||
prevHeight,
|
||||
invertX,
|
||||
invertY
|
||||
});
|
||||
|
||||
const nextValues = { ...prevValues, direction };
|
||||
|
||||
const callResize = shouldResize?.(event, nextValues);
|
||||
|
||||
if (callResize === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
onResize?.(event, nextValues);
|
||||
|
||||
$nodes = $nodes;
|
||||
}
|
||||
});
|
||||
|
||||
selection.call(dragHandler);
|
||||
|
||||
return () => {
|
||||
selection.on('.drag', null);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={cc(['svelte-flow__resize-control', 'nodrag', ...positionClassNames, variant, className])}
|
||||
bind:this={resizeControlRef}
|
||||
style={controlStyle}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as NodeResizer } from './NodeResizer.svelte';
|
||||
export { default as NodeResizeControl } from './ResizeControl.svelte';
|
||||
@@ -0,0 +1,57 @@
|
||||
import type {
|
||||
XYResizeParams,
|
||||
XYResizeParamsWithDirection,
|
||||
XYResizeControlPosition,
|
||||
ResizeControlVariant,
|
||||
D3DragEvent,
|
||||
SubjectPosition
|
||||
} from '@xyflow/system';
|
||||
|
||||
export type NodeResizerProps = {
|
||||
nodeId?: string;
|
||||
color?: string;
|
||||
handleClass?: string;
|
||||
handleStyle?: string;
|
||||
lineClass?: string;
|
||||
lineStyle?: string;
|
||||
isVisible?: boolean;
|
||||
minWidth?: number;
|
||||
minHeight?: number;
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
keepAspectRatio?: boolean;
|
||||
shouldResize?: ShouldResize;
|
||||
onResizeStart?: OnResizeStart;
|
||||
onResize?: OnResize;
|
||||
onResizeEnd?: OnResizeEnd;
|
||||
};
|
||||
|
||||
export type ResizeControlProps = Pick<
|
||||
NodeResizerProps,
|
||||
| 'nodeId'
|
||||
| 'color'
|
||||
| 'minWidth'
|
||||
| 'minHeight'
|
||||
| 'maxWidth'
|
||||
| 'maxHeight'
|
||||
| 'keepAspectRatio'
|
||||
| 'shouldResize'
|
||||
| 'onResizeStart'
|
||||
| 'onResize'
|
||||
| 'onResizeEnd'
|
||||
> & {
|
||||
position?: XYResizeControlPosition;
|
||||
variant?: ResizeControlVariant;
|
||||
class?: string;
|
||||
style?: string;
|
||||
};
|
||||
|
||||
type OnResizeHandler<Params = XYResizeParams, Result = void> = (
|
||||
event: ResizeDragEvent,
|
||||
params: Params
|
||||
) => Result;
|
||||
export type ResizeDragEvent = D3DragEvent<HTMLDivElement, null, SubjectPosition>;
|
||||
export type ShouldResize = OnResizeHandler<XYResizeParamsWithDirection, boolean>;
|
||||
export type OnResizeStart = OnResizeHandler;
|
||||
export type OnResize = OnResizeHandler<XYResizeParamsWithDirection>;
|
||||
export type OnResizeEnd = OnResizeHandler;
|
||||
Reference in New Issue
Block a user