feat(svelte): add snapGrid

This commit is contained in:
moklick
2023-03-14 15:43:14 +01:00
parent ae244526bd
commit 9c7bd6dc68
7 changed files with 36 additions and 16 deletions
+22 -5
View File
@@ -5,6 +5,7 @@ import type { XYPosition, CoordinateExtent, Transform } from '@reactflow/system'
import { getDragItems, hasSelector, calcNextPosition } from './utils'; import { getDragItems, hasSelector, calcNextPosition } from './utils';
import type { Node } from '$lib/types'; import type { Node } from '$lib/types';
import type { SvelteFlowStore } from '$lib/store/types';
export type UseDragData = { dx: number; dy: number }; export type UseDragData = { dx: number; dy: number };
export type UseDragEvent = D3DragEvent<HTMLDivElement, null, SubjectPosition>; export type UseDragEvent = D3DragEvent<HTMLDivElement, null, SubjectPosition>;
@@ -25,13 +26,21 @@ type UseDragParams = {
handleSelector?: string; handleSelector?: string;
nodeId?: string; nodeId?: string;
updateNodePositions: (dragItems: NodeDragItem[], d: boolean, p: boolean) => void; updateNodePositions: (dragItems: NodeDragItem[], d: boolean, p: boolean) => void;
nodes: Writable<Node[]>; nodes: SvelteFlowStore['nodes'];
transform: Writable<Transform>; transform: SvelteFlowStore['transform'];
snapGrid: SvelteFlowStore['snapGrid'];
}; };
export default function drag( export default function drag(
nodeRef: Element, nodeRef: Element,
{ handleSelector, nodeId, updateNodePositions, nodes, transform: transformStore }: UseDragParams {
handleSelector,
nodeId,
updateNodePositions,
nodes,
transform: transformStore,
snapGrid: snapGridStore
}: UseDragParams
) { ) {
let dragging = false; let dragging = false;
let dragItems: NodeDragItem[] = []; let dragItems: NodeDragItem[] = [];
@@ -43,6 +52,7 @@ export default function drag(
const x = sourceEvent.touches ? sourceEvent.touches[0].clientX : sourceEvent.clientX; const x = sourceEvent.touches ? sourceEvent.touches[0].clientX : sourceEvent.clientX;
const y = sourceEvent.touches ? sourceEvent.touches[0].clientY : sourceEvent.clientY; const y = sourceEvent.touches ? sourceEvent.touches[0].clientY : sourceEvent.clientY;
const transform = get(transformStore); const transform = get(transformStore);
const snapGrid = get(snapGridStore);
const pointerPos = { const pointerPos = {
x: (x - transform[0]) / transform[2], x: (x - transform[0]) / transform[2],
@@ -51,17 +61,24 @@ export default function drag(
// we need the snapped position in order to be able to skip unnecessary drag events // we need the snapped position in order to be able to skip unnecessary drag events
return { return {
xSnapped: pointerPos.x, xSnapped: snapGrid ? snapGrid[0] * Math.round(pointerPos.x / snapGrid[0]) : pointerPos.x,
ySnapped: pointerPos.y, ySnapped: snapGrid ? snapGrid[1] * Math.round(pointerPos.y / snapGrid[1]) : pointerPos.y,
...pointerPos ...pointerPos
}; };
}; };
const updateNodes = ({ x, y }: XYPosition) => { const updateNodes = ({ x, y }: XYPosition) => {
let hasChange = false; let hasChange = false;
const snapGrid = get(snapGridStore);
dragItems = dragItems.map((n) => { dragItems = dragItems.map((n) => {
const nextPosition = { x: x - n.distance.x, y: y - n.distance.y }; const nextPosition = { x: x - n.distance.x, y: y - n.distance.y };
if (snapGrid) {
nextPosition.x = snapGrid[0] * Math.round(nextPosition.x / snapGrid[0]);
nextPosition.y = snapGrid[1] * Math.round(nextPosition.y / snapGrid[1]);
}
const updatedPos = calcNextPosition(n, nextPosition, get(nodes)); const updatedPos = calcNextPosition(n, nextPosition, get(nodes));
// we want to make sure that we only fire a change event when there is a changes // we want to make sure that we only fire a change event when there is a changes
@@ -5,7 +5,7 @@
import { Selection } from '$lib/components/Selection'; import { Selection } from '$lib/components/Selection';
import drag from '$lib/actions/drag'; import drag from '$lib/actions/drag';
const { selectionRectMode, nodes, transform, updateNodePositions } = useStore(); const { selectionRectMode, nodes, transform, snapGrid, updateNodePositions } = useStore();
$: selectedNodes = $nodes.filter((n) => n.selected); $: selectedNodes = $nodes.filter((n) => n.selected);
$: rect = getRectOfNodes(selectedNodes); $: rect = getRectOfNodes(selectedNodes);
@@ -15,7 +15,7 @@
<div <div
class="selection-wrapper nopan" class="selection-wrapper nopan"
style={`width: ${rect.width}px; height: ${rect.height}px; transform: translate(${rect.x}px, ${rect.y}px)`} style={`width: ${rect.width}px; height: ${rect.height}px; transform: translate(${rect.x}px, ${rect.y}px)`}
use:drag={{ nodes, transform, updateNodePositions }} use:drag={{ nodes, snapGrid, transform, updateNodePositions }}
/> />
<Selection <Selection
isVisible={$selectionRectMode === 'nodes'} isVisible={$selectionRectMode === 'nodes'}
@@ -30,7 +30,7 @@
let className: string = ''; let className: string = '';
export { className as class }; export { className as class };
const { nodes, transform, nodeTypes, updateNodePositions, addSelectedNodes } = useStore(); const { nodes, transform, nodeTypes, snapGrid, updateNodePositions, addSelectedNodes } = useStore();
let nodeRef: HTMLDivElement; let nodeRef: HTMLDivElement;
const nodeTypeValid = !!$nodeTypes[type!]; const nodeTypeValid = !!$nodeTypes[type!];
@@ -74,7 +74,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<div <div
use:drag={{ nodeId: id, nodes, transform, updateNodePositions }} use:drag={{ nodeId: id, nodes, snapGrid, transform, updateNodePositions }}
bind:this={nodeRef} bind:this={nodeRef}
data-id={id} data-id={id}
class={cc(['svelte-flow__node', `svelte-flow__node-${type}`, className])} class={cc(['svelte-flow__node', `svelte-flow__node-${type}`, className])}
@@ -26,6 +26,7 @@
export let edgeTypes: $$Props['edgeTypes'] = undefined; export let edgeTypes: $$Props['edgeTypes'] = undefined;
export let selectionKey: $$Props['selectionKey'] = undefined; export let selectionKey: $$Props['selectionKey'] = undefined;
export let selectionMode: $$Props['selectionMode'] = undefined; export let selectionMode: $$Props['selectionMode'] = undefined;
export let snapGrid: $$Props['snapGrid'] = undefined;
export let deleteKey: $$Props['deleteKey'] = undefined; export let deleteKey: $$Props['deleteKey'] = undefined;
export let connectionRadius: $$Props['connectionRadius'] = undefined; export let connectionRadius: $$Props['connectionRadius'] = undefined;
export let connectionLineType: $$Props['connectionLineType'] = undefined; export let connectionLineType: $$Props['connectionLineType'] = undefined;
@@ -56,6 +57,7 @@
connectionLineType, connectionLineType,
connectionRadius, connectionRadius,
selectionMode, selectionMode,
snapGrid,
isValidConnection isValidConnection
}; };
@@ -4,7 +4,8 @@ import type {
NodeOrigin, NodeOrigin,
OnConnectStartParams, OnConnectStartParams,
Viewport, Viewport,
SelectionMode SelectionMode,
SnapGrid
} from '@reactflow/system'; } from '@reactflow/system';
import type { import type {
@@ -29,6 +30,7 @@ export type SvelteFlowProps = {
initialViewport?: Viewport; initialViewport?: Viewport;
connectionRadius?: number; connectionRadius?: number;
selectionMode?: SelectionMode; selectionMode?: SelectionMode;
snapGrid?: SnapGrid;
class?: string; class?: string;
style?: string; style?: string;
@@ -6,7 +6,8 @@ import {
ConnectionMode, ConnectionMode,
ConnectionLineType, ConnectionLineType,
type SelectionRect, type SelectionRect,
type Transform type Transform,
type SnapGrid
} from '@reactflow/system'; } from '@reactflow/system';
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte'; import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
@@ -60,6 +61,7 @@ export const initialStoreState = {
zoom: null, zoom: null,
selection: null selection: null
}), }),
snapGrid: writable<SnapGrid | null>(null),
dragging: writable<boolean>(false), dragging: writable<boolean>(false),
selectionRect: writable<SelectionRect | null>(null), selectionRect: writable<SelectionRect | null>(null),
selectionKeyPressed: writable<boolean>(false), selectionKeyPressed: writable<boolean>(false),
@@ -63,7 +63,7 @@
data: { label: 'Custom Node' }, data: { label: 'Custom Node' },
position: { x: 150, y: 300 } position: { x: 150, y: 300 }
} }
]); ], { style: 'width: 125px;' });
const edges = createEdges([ const edges = createEdges([
{ {
@@ -116,6 +116,7 @@
maxZoom={2.5} maxZoom={2.5}
selectionMode={SelectionMode.Full} selectionMode={SelectionMode.Full}
initialViewport={{ x: 100, y: 100, zoom: 2 }} initialViewport={{ x: 100, y: 100, zoom: 2 }}
snapGrid={[25, 25]}
on:node:click={(event) => console.log('on node click', event)} on:node:click={(event) => console.log('on node click', event)}
on:node:mouseenter={(event) => console.log('on node enter', event)} on:node:mouseenter={(event) => console.log('on node enter', event)}
on:node:mouseleave={(event) => console.log('on node leave', event)} on:node:mouseleave={(event) => console.log('on node leave', event)}
@@ -136,10 +137,6 @@
</SvelteFlowProvider> </SvelteFlowProvider>
<style> <style>
:root {
--node-width: 50;
}
:global(.svelte-flow .custom-style) { :global(.svelte-flow .custom-style) {
background: #ff5050; background: #ff5050;
color: white; color: white;