File size: 5,715 Bytes
bc20498 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
<script context="module">export function wrapHandler(handler, container) {
return (event) => {
if (event.target !== container) {
return;
}
handler?.(event);
};
}
export function toggleSelected(ids) {
return (item) => {
const isSelected = ids.includes(item.id);
if (item.selected !== isSelected) {
item.selected = isSelected;
}
return item;
};
}
</script>
<script>import { createEventDispatcher } from 'svelte';
import { SelectionMode, getEventPosition, getNodesInside, getConnectedEdges } from '@xyflow/system';
import { useStore } from '../../store';
export let panOnDrag = undefined;
export let selectionOnDrag = undefined;
const dispatch = createEventDispatcher();
const { nodes, nodeLookup, edges, viewport, dragging, elementsSelectable, selectionRect, selectionRectMode, selectionKeyPressed, selectionMode, panActivationKeyPressed, unselectNodesAndEdges } = useStore();
let container;
let containerBounds = null;
let selectedNodes = [];
$: _panOnDrag = $panActivationKeyPressed || panOnDrag;
$: isSelecting =
$selectionKeyPressed || $selectionRect || (selectionOnDrag && _panOnDrag !== true);
$: hasActiveSelection = $elementsSelectable && (isSelecting || $selectionRectMode === 'user');
// Used to prevent click events when the user lets go of the selectionKey during a selection
let selectionInProgress = false;
function onClick(event) {
// We prevent click events when the user let go of the selectionKey during a selection
if (selectionInProgress) {
selectionInProgress = false;
return;
}
dispatch('paneclick', { event });
unselectNodesAndEdges();
selectionRectMode.set(null);
}
function onPointerDown(event) {
containerBounds = container.getBoundingClientRect();
if (!elementsSelectable ||
!isSelecting ||
event.button !== 0 ||
event.target !== container ||
!containerBounds) {
return;
}
event.target?.setPointerCapture?.(event.pointerId);
const { x, y } = getEventPosition(event, containerBounds);
unselectNodesAndEdges();
selectionRect.set({
width: 0,
height: 0,
startX: x,
startY: y,
x,
y
});
// onSelectionStart?.(event);
}
function onPointerMove(event) {
if (!isSelecting || !containerBounds || !$selectionRect) {
return;
}
selectionInProgress = true;
const mousePos = getEventPosition(event, containerBounds);
const startX = $selectionRect.startX ?? 0;
const startY = $selectionRect.startY ?? 0;
const nextUserSelectRect = {
...$selectionRect,
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)
};
const prevSelectedNodeIds = selectedNodes.map((n) => n.id);
const prevSelectedEdgeIds = getConnectedEdges(selectedNodes, $edges).map((e) => e.id);
selectedNodes = getNodesInside($nodeLookup, nextUserSelectRect, [$viewport.x, $viewport.y, $viewport.zoom], $selectionMode === SelectionMode.Partial, true);
const selectedEdgeIds = getConnectedEdges(selectedNodes, $edges).map((e) => e.id);
const selectedNodeIds = selectedNodes.map((n) => n.id);
// this prevents unnecessary updates while updating the selection rectangle
if (prevSelectedNodeIds.length !== selectedNodeIds.length ||
selectedNodeIds.some((id) => !prevSelectedNodeIds.includes(id))) {
nodes.update((nodes) => nodes.map(toggleSelected(selectedNodeIds)));
}
if (prevSelectedEdgeIds.length !== selectedEdgeIds.length ||
selectedEdgeIds.some((id) => !prevSelectedEdgeIds.includes(id))) {
edges.update((edges) => edges.map(toggleSelected(selectedEdgeIds)));
}
selectionRectMode.set('user');
selectionRect.set(nextUserSelectRect);
}
function onPointerUp(event) {
if (event.button !== 0) {
return;
}
event.target?.releasePointerCapture?.(event.pointerId);
// We only want to trigger click functions when in selection mode if
// the user did not move the mouse.
if (!isSelecting && $selectionRectMode === 'user' && event.target === container) {
onClick?.(event);
}
selectionRect.set(null);
if (selectedNodes.length > 0) {
$selectionRectMode = 'nodes';
}
// If the user kept holding the selectionKey during the selection,
// we need to reset the selectionInProgress, so the next click event is not prevented
if ($selectionKeyPressed) {
selectionInProgress = false;
}
// onSelectionEnd?.(event);
}
const onContextMenu = (event) => {
if (Array.isArray(_panOnDrag) && _panOnDrag?.includes(2)) {
event.preventDefault();
return;
}
dispatch('panecontextmenu', { event });
};
</script>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
bind:this={container}
class="svelte-flow__pane"
class:draggable={panOnDrag === true || (Array.isArray(panOnDrag) && panOnDrag.includes(0))}
class:dragging={$dragging}
class:selection={isSelecting}
on:click={hasActiveSelection ? undefined : wrapHandler(onClick, container)}
on:pointerdown={hasActiveSelection ? onPointerDown : undefined}
on:pointermove={hasActiveSelection ? onPointerMove : undefined}
on:pointerup={hasActiveSelection ? onPointerUp : undefined}
on:contextmenu={wrapHandler(onContextMenu, container)}
>
<slot />
</div>
<style>
.svelte-flow__pane {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
|