Files
windmill/frontend/src/lib/components/graph/SelectionTool.svelte
T
GuilhemandClaude Opus 5 b4be8bc535 fix(frontend): clear the flow graph selection through xyflow's store (#11056)
Selecting a step in the flow editor sometimes opened the Settings panel
instead of the step that was clicked.

SelectionManager.selectId() clears xyflow's own selection before setting
ours, and that clear was wired to clearFlowSelection(), which resets
offsetNodeCache and reassigns nodes. That hands xyflow node objects it
does not recognise, which is what makes it drop the flag — but it also
makes it re-create every node's DOM: 126 elements on a 25-node flow, on
every selection.

Graph nodes select on pointerdown. A click is dispatched on the closest
common ancestor of its pointerdown and pointerup targets, so when the
release lands in that teardown gap the browser hit-tests to the pane, the
click addresses the pane, and onpaneclick clears the selection back to the
settings sentinel.

Clear through store.unselectNodesAndEdges() instead, so no node object
changes identity and there is no gap to fall into. clearFlowSelection
keeps its two group-creation callers, which rebuild the graph anyway.


Claude-Session: https://claude.ai/code/session_01R7caEKonPr2DmvsWRR6bCK

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:37:10 +02:00

47 lines
1.6 KiB
Svelte

<script lang="ts">
import { untrack } from 'svelte'
import { useOnSelectionChange, useStore, type Node } from '@xyflow/svelte'
import type { SelectionManager } from './selectionUtils.svelte'
interface Props {
selectionManager: SelectionManager
}
let { selectionManager }: Props = $props()
const store = useStore()
// Clear through xyflow's store, never by handing it fresh node objects: replacing them
// re-creates every node's DOM, and a click whose node is rebuilt between pointerdown and
// release retargets to the pane, which clears the selection that same gesture just made.
// While xyflow holds a selection, useOnSelectionChange below re-broadcasts it over ours.
untrack(() => selectionManager).setClearGraphSelection(() => store.unselectNodesAndEdges())
// Handle selection changes from SvelteFlow
useOnSelectionChange(({ nodes: selectedNodes, edges: _selectedEdges }) => {
// Notes are already non-selectable, so no filtering needed
const selectedNodeIds = selectedNodes.map((node: Node) => node.id)
// Only select nodes if multiple nodes are selected
// To avoid conflicting with the node-level click events
if (selectedNodeIds.length > 0) {
selectionManager.selectNodes(selectedNodes)
}
})
</script>
<!-- Render custom selection box during drag selection -->
{#if store.selectionRect}
{@const bounds = store.selectionRect!}
<div
class="absolute rounded cursor-pointer bg-surface-selected/30 pointer-events-none"
style="
left: {bounds.x}px;
top: {bounds.y}px;
width: {bounds.width}px;
height: {bounds.height}px;
z-index: 10;
"
>
</div>
{/if}