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>