feat(svelte): selection box
This commit is contained in:
@@ -16,8 +16,9 @@
|
||||
".": "./index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@reactflow/system": "workspace:11.5.5",
|
||||
"@reactflow/utils": "workspace:^11.5.5",
|
||||
"@reactflow/system": "workspace:*",
|
||||
"@reactflow/utils": "workspace:*",
|
||||
"@svelte-put/shortcut": "^2.0.0",
|
||||
"classcat": "^5.0.4",
|
||||
"d3-drag": "^3.0.0",
|
||||
"d3-selection": "^3.0.0",
|
||||
|
||||
119
packages/svelte/src/lib/actions/zoom/index.ts
Normal file
119
packages/svelte/src/lib/actions/zoom/index.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type { Writable } from 'svelte/store';
|
||||
import { select } from 'd3-selection';
|
||||
import { zoom as d3Zoom, zoomIdentity } from 'd3-zoom';
|
||||
import type { D3ZoomEvent } from 'd3-zoom';
|
||||
import type { D3SelectionInstance, D3ZoomInstance, Transform } from '@reactflow/system';
|
||||
|
||||
const isWrappedWithClass = (event: any, className: string | undefined) =>
|
||||
event.target.closest(`.${className}`);
|
||||
|
||||
function filter(event: any, params: ZoomParams): boolean {
|
||||
const zoomScroll = true;
|
||||
const pinchZoom = true;
|
||||
|
||||
if (
|
||||
event.button === 1 &&
|
||||
event.type === 'mousedown' &&
|
||||
(isWrappedWithClass(event, 'react-flow__node') || isWrappedWithClass(event, 'react-flow__edge'))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if all interactions are disabled, we prevent all zoom events
|
||||
// if (!panOnDrag && !zoomScroll && !panOnScroll && !zoomOnDoubleClick && !zoomOnPinch) {
|
||||
// return false;
|
||||
// }
|
||||
// // during a selection we prevent all other interactions
|
||||
if (params.selecting) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// // if zoom on double click is disabled, we prevent the double click event
|
||||
// if (!zoomOnDoubleClick && event.type === 'dblclick') {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // if the target element is inside an element with the nowheel class, we prevent zooming
|
||||
// if (isWrappedWithClass(event, noWheelClassName) && event.type === 'wheel') {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // if the target element is inside an element with the nopan class, we prevent panning
|
||||
// if (isWrappedWithClass(event, noPanClassName) && event.type !== 'wheel') {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// if (!zoomOnPinch && event.ctrlKey && event.type === 'wheel') {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // when there is no scroll handling enabled, we prevent all wheel events
|
||||
// if (!zoomScroll && !panOnScroll && !pinchZoom && event.type === 'wheel') {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // if the pane is not movable, we prevent dragging it with mousestart or touchstart
|
||||
// if (!panOnDrag && (event.type === 'mousedown' || event.type === 'touchstart')) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // if the pane is only movable using allowed clicks
|
||||
// if (
|
||||
// Array.isArray(panOnDrag) &&
|
||||
// !panOnDrag.includes(event.button) &&
|
||||
// (event.type === 'mousedown' || event.type === 'touchstart')
|
||||
// ) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // We only allow right clicks if pan on drag is set to right click
|
||||
// const buttonAllowed =
|
||||
// (Array.isArray(panOnDrag) && panOnDrag.includes(event.button)) ||
|
||||
// !event.button ||
|
||||
// event.button <= 1;
|
||||
|
||||
// default filter for d3-zoom
|
||||
return ((!event.ctrlKey || event.type === 'wheel') && !event.button) || event.button <= 1;
|
||||
}
|
||||
|
||||
type ZoomParams = {
|
||||
transformStore: Writable<Transform>;
|
||||
selecting: boolean;
|
||||
d3Store: Writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>;
|
||||
};
|
||||
|
||||
export default function zoom(domNode: Element, params: ZoomParams) {
|
||||
const { transformStore, d3Store } = params;
|
||||
const d3ZoomInstance = d3Zoom();
|
||||
const selection = select(domNode).call(d3ZoomInstance);
|
||||
const d3ZoomHandler = selection.on('wheel.zoom');
|
||||
|
||||
d3ZoomInstance.transform(selection, zoomIdentity);
|
||||
|
||||
selection.on('wheel.zoom', function (event: any, d: any) {
|
||||
if (isWrappedWithClass(event, 'nowheel')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
d3ZoomHandler!.call(this, event, d);
|
||||
});
|
||||
|
||||
d3Store.set({
|
||||
zoom: d3ZoomInstance,
|
||||
selection
|
||||
});
|
||||
|
||||
d3ZoomInstance.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
||||
transformStore.set([event.transform.x, event.transform.y, event.transform.k]);
|
||||
});
|
||||
|
||||
d3ZoomInstance.filter((event: any) => filter(event, params));
|
||||
|
||||
return {
|
||||
update(params: ZoomParams) {
|
||||
d3ZoomInstance.filter((event: any) => filter(event, params));
|
||||
}
|
||||
};
|
||||
}
|
||||
26
packages/svelte/src/lib/components/KeyHandler/index.svelte
Normal file
26
packages/svelte/src/lib/components/KeyHandler/index.svelte
Normal file
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { shortcut } from '@svelte-put/shortcut';
|
||||
|
||||
import { useStore } from '$lib/store';
|
||||
|
||||
const { selectionKeyPressedStore } = useStore();
|
||||
|
||||
function onSelectionKeyDown() {
|
||||
selectionKeyPressedStore.set(true);
|
||||
}
|
||||
|
||||
function onSelectionKeyUp() {
|
||||
selectionKeyPressedStore.set(false);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window
|
||||
use:shortcut={{
|
||||
trigger: [{ key: 'Shift', callback: onSelectionKeyDown }],
|
||||
type: 'keydown'
|
||||
}}
|
||||
use:shortcut={{
|
||||
trigger: [{ key: 'Shift', callback: onSelectionKeyUp }],
|
||||
type: 'keyup'
|
||||
}}
|
||||
/>
|
||||
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { getRectOfNodes } from '@reactflow/utils';
|
||||
|
||||
import { useStore } from '$lib/store';
|
||||
import Selection from '$lib/components/Selection/index.svelte';
|
||||
|
||||
const { selectionRectModeStore, nodesStore, nodeOriginStore } = useStore();
|
||||
|
||||
$: selectedNodes = $nodesStore.filter(n => n.selected);
|
||||
$: rect = getRectOfNodes(selectedNodes, $nodeOriginStore);
|
||||
|
||||
// @todo: use zoom action for selection
|
||||
</script>
|
||||
|
||||
{#if selectedNodes}
|
||||
<Selection
|
||||
isVisible={$selectionRectModeStore === 'nodes'}
|
||||
width={rect.width}
|
||||
height={rect.height}
|
||||
x={rect.x}
|
||||
y={rect.y}
|
||||
/>
|
||||
{/if}
|
||||
30
packages/svelte/src/lib/components/Selection/index.svelte
Normal file
30
packages/svelte/src/lib/components/Selection/index.svelte
Normal file
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
export let x: number | null = 0;
|
||||
export let y: number | null = 0;
|
||||
export let width: number | null = 0;
|
||||
export let height: number | null = 0;
|
||||
export let isVisible: boolean = true;
|
||||
</script>
|
||||
|
||||
{#if isVisible}
|
||||
<div
|
||||
class="react-flow__selection"
|
||||
style={`width: ${width}px; height: ${height}px;transform: translate(${x}px, ${y}px)`}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.react-flow__selection {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: rgba(0, 89, 220, 0.08);
|
||||
border: 1px dotted rgba(0, 89, 220, 0.8);
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
.react-flow__selection:focus,
|
||||
.react-flow__selection:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { useStore } from '$lib/store';
|
||||
import Selection from '$lib/components/Selection/index.svelte';
|
||||
|
||||
const { selectionRectStore, selectionRectModeStore } = useStore();
|
||||
</script>
|
||||
|
||||
<Selection
|
||||
isVisible={!!($selectionRectStore && $selectionRectModeStore === 'user')}
|
||||
width={$selectionRectStore?.width}
|
||||
height={$selectionRectStore?.height}
|
||||
x={$selectionRectStore?.x}
|
||||
y={$selectionRectStore?.y}
|
||||
/>
|
||||
@@ -2,7 +2,7 @@
|
||||
import { onMount, setContext } from 'svelte';
|
||||
import type { XYPosition } from '@reactflow/system';
|
||||
|
||||
import drag from '$lib/hooks/drag'
|
||||
import drag from '$lib/actions/drag'
|
||||
import { useStore } from '$lib/store';
|
||||
import DefaultNode from './DefaultNode.svelte';
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
resizeObserver?.unobserve(nodeRef);
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -58,6 +57,8 @@
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
cursor: grab;
|
||||
pointer-events: all;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.dragging {
|
||||
|
||||
@@ -31,5 +31,7 @@
|
||||
.react-flow__nodes {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
</style>
|
||||
180
packages/svelte/src/lib/container/Pane/index.svelte
Normal file
180
packages/svelte/src/lib/container/Pane/index.svelte
Normal file
@@ -0,0 +1,180 @@
|
||||
<script lang="ts">
|
||||
import { useStore } from '$lib/store';
|
||||
import { SelectionMode, type Node, type Edge } from '@reactflow/system';
|
||||
import { getConnectedEdges, getEventPosition, getNodesInside } from '@reactflow/utils';
|
||||
|
||||
const { nodesStore, edgesStore, transformStore, nodeOriginStore, draggingStore, selectionRectStore, selectionRectModeStore, selectionKeyPressedStore, resetSelectedElements } = useStore();
|
||||
|
||||
const wrapHandler = (
|
||||
handler: (evt: MouseEvent) => void,
|
||||
container: HTMLDivElement
|
||||
): (evt: MouseEvent) => void => {
|
||||
return (event: MouseEvent) => {
|
||||
if (event.target !== container) {
|
||||
return;
|
||||
}
|
||||
|
||||
handler?.(event);
|
||||
};
|
||||
};
|
||||
|
||||
// @todo take from props
|
||||
const elementsSelectable = true;
|
||||
const selectionMode = SelectionMode.Partial
|
||||
|
||||
let container: HTMLDivElement;
|
||||
let containerBounds: DOMRect | null = null
|
||||
let selectedNodes: Node[] = [];
|
||||
|
||||
$: isSelecting = $selectionKeyPressedStore;
|
||||
$: hasActiveSelection = elementsSelectable && (isSelecting || $selectionRectModeStore === 'user');
|
||||
|
||||
|
||||
function onClick (event: MouseEvent) {
|
||||
// onPaneClick?.(event);
|
||||
|
||||
resetSelectedElements();
|
||||
selectionRectModeStore.set(null)
|
||||
}
|
||||
|
||||
function onMouseDown(event: MouseEvent) {
|
||||
containerBounds = container.getBoundingClientRect();
|
||||
|
||||
if (
|
||||
!elementsSelectable ||
|
||||
!isSelecting ||
|
||||
event.button !== 0 ||
|
||||
event.target !== container ||
|
||||
!containerBounds
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { x, y } = getEventPosition(event, containerBounds);
|
||||
|
||||
resetSelectedElements();
|
||||
|
||||
selectionRectStore.set({
|
||||
width: 0,
|
||||
height: 0,
|
||||
startX: x,
|
||||
startY: y,
|
||||
x,
|
||||
y
|
||||
});
|
||||
|
||||
|
||||
// onSelectionStart?.(event);
|
||||
};
|
||||
|
||||
function toggleSelected<Item extends Node | Edge>(ids: string[]) {
|
||||
return (item: Item) => {
|
||||
const isSelected = ids.includes(item.id);
|
||||
|
||||
if (item.selected !== isSelected) {
|
||||
return {
|
||||
...item,
|
||||
selected: isSelected,
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseMove(event: MouseEvent) {
|
||||
if (!isSelecting || !containerBounds || !$selectionRectStore) {
|
||||
return;
|
||||
}
|
||||
const mousePos = getEventPosition(event, containerBounds);
|
||||
const startX = $selectionRectStore.startX ?? 0;
|
||||
const startY = $selectionRectStore.startY ?? 0;
|
||||
const nextUserSelectRect = {
|
||||
...$selectionRectStore,
|
||||
x: mousePos.x < startX ? mousePos.x : startX,
|
||||
y: mousePos.y < startY ? mousePos.y : startY,
|
||||
width: Math.abs(mousePos.x - startX),
|
||||
height: Math.abs(mousePos.y - startY),
|
||||
};
|
||||
|
||||
selectedNodes = getNodesInside(
|
||||
new Map($nodesStore.map(node => [node.id, node])),
|
||||
nextUserSelectRect,
|
||||
$transformStore,
|
||||
selectionMode === SelectionMode.Partial,
|
||||
true,
|
||||
$nodeOriginStore
|
||||
);
|
||||
const selectedEdgeIds = getConnectedEdges(selectedNodes, $edgesStore).map((e) => e.id);
|
||||
const selectedNodeIds = selectedNodes.map((n) => n.id);
|
||||
|
||||
nodesStore.update(nodes => nodes.map(toggleSelected(selectedNodeIds)));
|
||||
edgesStore.update(edges => edges.map(toggleSelected(selectedEdgeIds)));
|
||||
|
||||
selectionRectModeStore.set('user');
|
||||
selectionRectStore.set(nextUserSelectRect);
|
||||
}
|
||||
|
||||
function onMouseUp(event: MouseEvent) {
|
||||
if (event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We only want to trigger click functions when in selection mode if
|
||||
// the user did not move the mouse.
|
||||
// if (!userSelectionActive && userSelectionRect && event.target === container.current) {
|
||||
// onClick?.(event);
|
||||
// }
|
||||
selectionRectStore.set(null);
|
||||
|
||||
if (selectedNodes.length > 0) {
|
||||
selectionRectModeStore.set('nodes');
|
||||
}
|
||||
|
||||
|
||||
// onSelectionEnd?.(event);
|
||||
}
|
||||
|
||||
const onMouseLeave = (event: MouseEvent) => {
|
||||
if ($selectionRectModeStore === 'user') {
|
||||
selectionRectModeStore.set(selectedNodes.length > 0 ? 'nodes' : null);
|
||||
// onSelectionEnd?.(event);
|
||||
}
|
||||
|
||||
selectionRectStore.set(null);
|
||||
};
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={container}
|
||||
class="react-flow__pane"
|
||||
class:dragging={$draggingStore}
|
||||
class:selection={isSelecting}
|
||||
on:click={hasActiveSelection ? undefined : wrapHandler(onClick, container)}
|
||||
on:mousedown={hasActiveSelection ? onMouseDown : undefined}
|
||||
on:mousemove={hasActiveSelection? onMouseMove : undefined}
|
||||
on:mouseup={hasActiveSelection ? onMouseUp : undefined}
|
||||
on:mouseleave={hasActiveSelection ? onMouseLeave : undefined}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.react-flow__pane {
|
||||
cursor: grab;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.selection {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
</style>
|
||||
@@ -4,18 +4,23 @@
|
||||
import type { Node, Edge } from '@reactflow/system';
|
||||
|
||||
import { key, createStore } from '$lib/store';
|
||||
import Zoom from '$lib/container/Zoom/index.svelte';
|
||||
import Pane from '$lib/container/Pane/index.svelte';
|
||||
import Viewport from '$lib/container/Viewport.svelte';
|
||||
import NodeRenderer from '$lib/container/NodeRenderer.svelte';
|
||||
import EdgeRenderer from '$lib/container/EdgeRenderer/index.svelte';
|
||||
import UserSelection from '$lib/components/UserSelection/index.svelte';
|
||||
import NodeSelection from '$lib/components/NodeSelection/index.svelte';
|
||||
import KeyHandler from '$lib/components/KeyHandler/index.svelte';
|
||||
|
||||
export let nodes: Node[];
|
||||
export let edges: Edge[];
|
||||
export let fitView: boolean;
|
||||
|
||||
export let className: string | null = null;
|
||||
export let id: string = '1';
|
||||
export let nodes: Node[] = [];
|
||||
export let edges: Edge[] = [];
|
||||
export let fitView: boolean = false;
|
||||
|
||||
let className: string = '';
|
||||
export { className as class };
|
||||
|
||||
let props = { ...$$restProps };
|
||||
let domNode: Element;
|
||||
|
||||
const store = createStore({
|
||||
@@ -35,19 +40,24 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
<div
|
||||
{...props}
|
||||
{...$$restProps}
|
||||
class={cc(['react-flow', className])}
|
||||
data-testid="rf__wrapper"
|
||||
id={id}
|
||||
bind:this={domNode}
|
||||
>
|
||||
<Viewport>
|
||||
<NodeRenderer />
|
||||
<EdgeRenderer />
|
||||
</Viewport>
|
||||
|
||||
<KeyHandler />
|
||||
<Zoom>
|
||||
<Pane>
|
||||
<Viewport>
|
||||
<NodeSelection />
|
||||
<NodeRenderer />
|
||||
<EdgeRenderer />
|
||||
</Viewport>
|
||||
<UserSelection />
|
||||
</Pane>
|
||||
</Zoom>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -59,5 +69,4 @@
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,35 +1,25 @@
|
||||
<script lang="ts">
|
||||
import zoom from '$lib/hooks/zoom'
|
||||
import { useStore } from '$lib/store';
|
||||
|
||||
const { transformStore, d3Store } = useStore();
|
||||
const { transformStore} = useStore();
|
||||
</script>
|
||||
|
||||
<div class="react-flow__pane" use:zoom={{ transformStore, d3Store }}>
|
||||
<div
|
||||
class="react-flow__viewport"
|
||||
style="transform: translate({$transformStore[0]}px, {$transformStore[1]}px) scale({$transformStore[2]})"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div
|
||||
class="react-flow__viewport"
|
||||
style="transform: translate({$transformStore[0]}px, {$transformStore[1]}px) scale({$transformStore[2]})"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.react-flow__pane {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.react-flow__viewport {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
transform-origin: 0 0;
|
||||
top:0;
|
||||
left:0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
23
packages/svelte/src/lib/container/Zoom/index.svelte
Normal file
23
packages/svelte/src/lib/container/Zoom/index.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { useStore } from '$lib/store';
|
||||
import zoom from '$lib/actions/zoom';
|
||||
|
||||
const { transformStore, d3Store, selectionKeyPressedStore, selectionRectModeStore } = useStore();
|
||||
|
||||
$: selecting = $selectionKeyPressedStore || $selectionRectModeStore === 'user';
|
||||
</script>
|
||||
|
||||
<div class="react-flow__zoom" use:zoom={{ transformStore, d3Store, selecting }}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.react-flow__zoom {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
</style>
|
||||
@@ -1,114 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type { Writable } from 'svelte/store';
|
||||
import { select } from 'd3-selection';
|
||||
import { zoom as d3Zoom, zoomIdentity } from 'd3-zoom';
|
||||
import type { D3ZoomEvent } from 'd3-zoom';
|
||||
import type { D3SelectionInstance, D3ZoomInstance, Transform } from '@reactflow/system';
|
||||
|
||||
const isWrappedWithClass = (event: any, className: string | undefined) =>
|
||||
event.target.closest(`.${className}`);
|
||||
|
||||
export default function zoom(
|
||||
domNode: Element,
|
||||
{
|
||||
transformStore,
|
||||
d3Store
|
||||
}: {
|
||||
transformStore: Writable<Transform>;
|
||||
d3Store: Writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>;
|
||||
}
|
||||
) {
|
||||
const d3ZoomInstance = d3Zoom();
|
||||
const selection = select(domNode).call(d3ZoomInstance);
|
||||
const d3ZoomHandler = selection.on('wheel.zoom');
|
||||
d3ZoomInstance.transform(selection, zoomIdentity);
|
||||
|
||||
selection.on('wheel.zoom', function (event: any, d: any) {
|
||||
if (isWrappedWithClass(event, 'nowheel')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
d3ZoomHandler!.call(this, event, d);
|
||||
});
|
||||
|
||||
d3Store.set({
|
||||
zoom: d3ZoomInstance,
|
||||
selection
|
||||
});
|
||||
|
||||
d3ZoomInstance.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
||||
transformStore.set([event.transform.x, event.transform.y, event.transform.k]);
|
||||
});
|
||||
|
||||
d3ZoomInstance.filter((event: any) => {
|
||||
const zoomScroll = true;
|
||||
const pinchZoom = true;
|
||||
|
||||
if (
|
||||
event.button === 1 &&
|
||||
event.type === 'mousedown' &&
|
||||
(isWrappedWithClass(event, 'react-flow__node') ||
|
||||
isWrappedWithClass(event, 'react-flow__edge'))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if all interactions are disabled, we prevent all zoom events
|
||||
// if (!panOnDrag && !zoomScroll && !panOnScroll && !zoomOnDoubleClick && !zoomOnPinch) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // during a selection we prevent all other interactions
|
||||
// if (userSelectionActive) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // if zoom on double click is disabled, we prevent the double click event
|
||||
// if (!zoomOnDoubleClick && event.type === 'dblclick') {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // if the target element is inside an element with the nowheel class, we prevent zooming
|
||||
// if (isWrappedWithClass(event, noWheelClassName) && event.type === 'wheel') {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // if the target element is inside an element with the nopan class, we prevent panning
|
||||
// if (isWrappedWithClass(event, noPanClassName) && event.type !== 'wheel') {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// if (!zoomOnPinch && event.ctrlKey && event.type === 'wheel') {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // when there is no scroll handling enabled, we prevent all wheel events
|
||||
// if (!zoomScroll && !panOnScroll && !pinchZoom && event.type === 'wheel') {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // if the pane is not movable, we prevent dragging it with mousestart or touchstart
|
||||
// if (!panOnDrag && (event.type === 'mousedown' || event.type === 'touchstart')) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // if the pane is only movable using allowed clicks
|
||||
// if (
|
||||
// Array.isArray(panOnDrag) &&
|
||||
// !panOnDrag.includes(event.button) &&
|
||||
// (event.type === 'mousedown' || event.type === 'touchstart')
|
||||
// ) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // We only allow right clicks if pan on drag is set to right click
|
||||
// const buttonAllowed =
|
||||
// (Array.isArray(panOnDrag) && panOnDrag.includes(event.button)) ||
|
||||
// !event.button ||
|
||||
// event.button <= 1;
|
||||
|
||||
// default filter for d3-zoom
|
||||
return ((!event.ctrlKey || event.type === 'wheel') && !event.button) || event.button <= 1;
|
||||
});
|
||||
}
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
type NodeOrigin,
|
||||
type D3ZoomInstance,
|
||||
type D3SelectionInstance,
|
||||
type ViewportHelperFunctionOptions
|
||||
type ViewportHelperFunctionOptions,
|
||||
type SelectionRect
|
||||
} from '@reactflow/system';
|
||||
import { fitView, getD3Transition, getDimensions } from '@reactflow/utils';
|
||||
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
getNodeData,
|
||||
type EdgePosition
|
||||
} from '$lib/container/EdgeRenderer/utils';
|
||||
import { SelectionMode } from 'reactflow';
|
||||
|
||||
export const key = Symbol();
|
||||
|
||||
@@ -52,6 +54,11 @@ type SvelteFlowStore = {
|
||||
edgesWithDataStore: Readable<EdgeWithData[]>;
|
||||
idStore: Writable<string>;
|
||||
nodeOriginStore: Writable<NodeOrigin>;
|
||||
draggingStore: Writable<boolean>;
|
||||
selectionRectStore: Writable<SelectionRect | null>;
|
||||
selectionRectModeStore: Writable<string | null>;
|
||||
selectionMode: Writable<SelectionMode>;
|
||||
selectionKeyPressedStore: Writable<boolean>;
|
||||
zoomIn: (options?: ViewportHelperFunctionOptions) => void;
|
||||
zoomOut: (options?: ViewportHelperFunctionOptions) => void;
|
||||
fitView: (options?: ViewportHelperFunctionOptions) => boolean;
|
||||
@@ -61,6 +68,7 @@ type SvelteFlowStore = {
|
||||
dragging?: boolean
|
||||
) => void;
|
||||
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
|
||||
resetSelectedElements: () => void;
|
||||
};
|
||||
|
||||
export function createStore({
|
||||
@@ -81,6 +89,11 @@ export function createStore({
|
||||
selection: null
|
||||
});
|
||||
const idStore = writable(id);
|
||||
const draggingStore = writable(false);
|
||||
const selectionRectStore = writable(null);
|
||||
const selectionKeyPressedStore = writable(false);
|
||||
const selectionRectModeStore = writable(null);
|
||||
const selectionMode = writable(SelectionMode.Partial);
|
||||
|
||||
let fitViewOnInitDone = false;
|
||||
|
||||
@@ -235,6 +248,22 @@ export function createStore({
|
||||
);
|
||||
}
|
||||
|
||||
function resetSelectedItem<T extends Node | Edge>(item: T) {
|
||||
if (item.selected) {
|
||||
return {
|
||||
...item,
|
||||
selected: false
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
function resetSelectedElements() {
|
||||
nodesStore.update((ns) => ns.map(resetSelectedItem));
|
||||
edgesStore.update((es) => es.map(resetSelectedItem));
|
||||
}
|
||||
|
||||
return {
|
||||
nodesStore,
|
||||
edgesStore,
|
||||
@@ -245,11 +274,17 @@ export function createStore({
|
||||
edgesWithDataStore,
|
||||
idStore,
|
||||
nodeOriginStore,
|
||||
draggingStore,
|
||||
selectionRectStore,
|
||||
selectionKeyPressedStore,
|
||||
selectionRectModeStore,
|
||||
selectionMode,
|
||||
updateNodePositions,
|
||||
updateNodeDimensions,
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
fitView: _fitView
|
||||
fitView: _fitView,
|
||||
resetSelectedElements
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -312,6 +312,7 @@ importers:
|
||||
specifiers:
|
||||
'@reactflow/system': workspace:11.5.5
|
||||
'@reactflow/utils': workspace:^11.5.5
|
||||
'@svelte-put/shortcut': ^2.0.0
|
||||
'@sveltejs/adapter-auto': ^2.0.0
|
||||
'@sveltejs/kit': ^1.5.6
|
||||
'@sveltejs/package': ^1.0.2
|
||||
@@ -336,6 +337,7 @@ importers:
|
||||
dependencies:
|
||||
'@reactflow/system': link:../system
|
||||
'@reactflow/utils': link:../utils
|
||||
'@svelte-put/shortcut': registry.npmjs.org/@svelte-put/shortcut/2.0.0
|
||||
classcat: registry.npmjs.org/classcat/5.0.4
|
||||
d3-drag: registry.npmjs.org/d3-drag/3.0.0
|
||||
d3-selection: registry.npmjs.org/d3-selection/3.0.0
|
||||
@@ -1964,6 +1966,12 @@ packages:
|
||||
version: 2.0.0
|
||||
dev: true
|
||||
|
||||
registry.npmjs.org/@svelte-put/shortcut/2.0.0:
|
||||
resolution: {integrity: sha512-dDyxIFlRz+W6wxwQtl2jUen0rX8ofV2VBWaJdz0QehLb0IMDzP3aWM9W4k8cAZQIGmTT0adr1+Gt7HPPUugcpQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@svelte-put/shortcut/-/shortcut-2.0.0.tgz}
|
||||
name: '@svelte-put/shortcut'
|
||||
version: 2.0.0
|
||||
dev: false
|
||||
|
||||
registry.npmjs.org/@sveltejs/adapter-auto/2.0.0_@sveltejs+kit@1.5.6:
|
||||
resolution: {integrity: sha512-b+gkHFZgD771kgV3aO4avHFd7y1zhmMYy9i6xOK7m/rwmwaRO8gnF5zBc0Rgca80B2PMU1bKNxyBTHA14OzUAQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-2.0.0.tgz}
|
||||
id: registry.npmjs.org/@sveltejs/adapter-auto/2.0.0
|
||||
|
||||
Reference in New Issue
Block a user