implement onSelectionChanged
This commit is contained in:
2
examples/svelte/src/routes/+layout.ts
Normal file
2
examples/svelte/src/routes/+layout.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const ssr = false;
|
||||
export const prerender = false;
|
||||
@@ -1,11 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { Handle, Position, type BuiltInNode, type NodeProps } from '@xyflow/svelte';
|
||||
import {
|
||||
Handle,
|
||||
Position,
|
||||
useSelectionChanged,
|
||||
type BuiltInNode,
|
||||
type NodeProps
|
||||
} from '@xyflow/svelte';
|
||||
|
||||
let {
|
||||
data = { label: 'Node' },
|
||||
positionAbsoluteX = 0,
|
||||
positionAbsoluteY = 0
|
||||
}: NodeProps<BuiltInNode> = $props();
|
||||
|
||||
useSelectionChanged(({ nodes, edges }) => {
|
||||
console.log('on selection changed via hook', { nodes, edges });
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="custom">
|
||||
|
||||
@@ -216,6 +216,10 @@
|
||||
connectionMode={ConnectionMode.Strict}
|
||||
attributionPosition={'top-center'}
|
||||
deleteKey={['Backspace', 'd']}
|
||||
onselectionchanged={({ nodes, edges }) => {
|
||||
console.log('on selection changed via prop', { nodes, edges });
|
||||
}}
|
||||
selectNodesOnDrag
|
||||
>
|
||||
<Controls orientation="horizontal" {fitViewOptions}>
|
||||
{#snippet before()}
|
||||
@@ -240,7 +244,6 @@
|
||||
}}>left-right</button
|
||||
>
|
||||
</Panel>
|
||||
|
||||
<InitTracker />
|
||||
</SvelteFlow>
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import { defineConfig } from 'vite';
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()],
|
||||
build: {
|
||||
sourcemap: true
|
||||
sourcemap: true,
|
||||
minify: false
|
||||
},
|
||||
server: {
|
||||
fs: {
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { portal } from '$lib/actions/portal';
|
||||
import type { ViewportPortalProps } from './types';
|
||||
|
||||
let { moveTo = 'front', children, ...rest }: ViewportPortalProps = $props();
|
||||
let { target = 'front', children, ...rest }: ViewportPortalProps = $props();
|
||||
</script>
|
||||
|
||||
<div use:portal={`viewport-${moveTo}`} {...rest}>
|
||||
<div use:portal={`viewport-${target}`} {...rest}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,6 @@ import type { Snippet } from 'svelte';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
export type ViewportPortalProps = {
|
||||
moveTo: 'front' | 'back';
|
||||
target: 'front' | 'back';
|
||||
children?: Snippet;
|
||||
} & HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
|
||||
import type { Edge, Node } from '$lib/types';
|
||||
import { getContext, setContext, onDestroy } from 'svelte';
|
||||
import { getContext, setContext, onDestroy, untrack } from 'svelte';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { ConnectionLineType, PanOnScrollMode } from '@xyflow/system';
|
||||
|
||||
@@ -110,6 +110,15 @@
|
||||
}
|
||||
} satisfies StoreContext<NodeType, EdgeType>);
|
||||
|
||||
// handle selection change
|
||||
$effect(() => {
|
||||
const params = { nodes: store.selectedNodes, edges: store.selectedEdges };
|
||||
untrack(() => props.onselectionchanged)?.(params);
|
||||
for (const handler of store.selectionChangedHandlers.values()) {
|
||||
handler(params);
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
store.reset();
|
||||
});
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
onclickconnectstart,
|
||||
onclickconnectend,
|
||||
oninit,
|
||||
onselectionchanged,
|
||||
clickConnect,
|
||||
fitView,
|
||||
fitViewOptions,
|
||||
|
||||
@@ -36,7 +36,8 @@ import type {
|
||||
OnBeforeConnect,
|
||||
OnBeforeDelete,
|
||||
IsValidConnection,
|
||||
OnBeforeReconnect
|
||||
OnBeforeReconnect,
|
||||
OnSelectionChanged
|
||||
} from '$lib/types';
|
||||
|
||||
import type { Component } from 'svelte';
|
||||
@@ -391,6 +392,8 @@ export type SvelteFlowProps<
|
||||
* If you have custom connection logic its preferred to use this callback over the
|
||||
* `isValidConnection` prop on the handle component for performance reasons.
|
||||
*/
|
||||
/** Toggles ability to make connections via clicking the handles */
|
||||
clickConnect?: boolean;
|
||||
isValidConnection?: IsValidConnection;
|
||||
/** This event handler is called when the user begins to pan or zoom the viewport */
|
||||
onmovestart?: OnMoveStart;
|
||||
@@ -434,8 +437,8 @@ export type SvelteFlowProps<
|
||||
onclickconnectstart?: OnConnectStart;
|
||||
/** A connection is finished by clicking on a handle */
|
||||
onclickconnectend?: OnConnectEnd;
|
||||
/** Toggles ability to make connections via clicking the handles */
|
||||
clickConnect?: boolean;
|
||||
/** This handler gets called when the flow is finished initializing */
|
||||
oninit?: () => void;
|
||||
/** This event handler gets called when the selected nodes & edges change */
|
||||
onselectionchanged?: OnSelectionChanged<NodeType, EdgeType>;
|
||||
};
|
||||
|
||||
15
packages/svelte/src/lib/hooks/useSelectionChanged.svelte.ts
Normal file
15
packages/svelte/src/lib/hooks/useSelectionChanged.svelte.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { OnSelectionChanged } from '$lib/types';
|
||||
import { useStore } from '$lib/hooks/useStore';
|
||||
|
||||
export function useSelectionChanged(onselectionchanged: OnSelectionChanged) {
|
||||
const store = $derived(useStore());
|
||||
const symbol = Symbol();
|
||||
|
||||
$effect(() => {
|
||||
store.selectionChangedHandlers.set(symbol, onselectionchanged);
|
||||
|
||||
return () => {
|
||||
store.selectionChangedHandlers.delete(symbol);
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -39,6 +39,7 @@ export * from '$lib/hooks/useNodeConnections.svelte';
|
||||
export * from '$lib/hooks/useNodesData.svelte';
|
||||
export * from '$lib/hooks/useInternalNode.svelte';
|
||||
export * from '$lib/hooks/useInitialized.svelte';
|
||||
export * from '$lib/hooks/useSelectionChanged.svelte';
|
||||
|
||||
//actions
|
||||
export * from '$lib/actions/portal';
|
||||
|
||||
@@ -59,7 +59,8 @@ import type {
|
||||
Node,
|
||||
EdgeLayouted,
|
||||
InternalNode,
|
||||
OnBeforeReconnect
|
||||
OnBeforeReconnect,
|
||||
OnSelectionChanged
|
||||
} from '$lib/types';
|
||||
|
||||
import type { StoreSignals } from './types';
|
||||
@@ -138,6 +139,52 @@ export function getInitialStore<NodeType extends Node = Node, EdgeType extends E
|
||||
signals.edges = edges;
|
||||
}
|
||||
|
||||
_prevSelectedNodes: NodeType[] = [];
|
||||
_prevSelectedNodeIds = new Set<string>();
|
||||
selectedNodes = $derived.by(() => {
|
||||
const selectedNodesCount = this._prevSelectedNodeIds.size;
|
||||
const selectedNodeIds = new Set<string>();
|
||||
const selectedNodes = this.nodes.filter((node) => {
|
||||
if (node.selected) {
|
||||
selectedNodeIds.add(node.id);
|
||||
this._prevSelectedNodeIds.delete(node.id);
|
||||
}
|
||||
return node.selected;
|
||||
});
|
||||
|
||||
// Either the number of selected nodes has changed or two nodes changed their selection state
|
||||
// at the same time. However then the previously selected node will be inside _prevSelectedNodeIds
|
||||
if (selectedNodesCount !== selectedNodeIds.size || this._prevSelectedNodeIds.size > 0) {
|
||||
this._prevSelectedNodes = selectedNodes;
|
||||
}
|
||||
|
||||
this._prevSelectedNodeIds = selectedNodeIds;
|
||||
return this._prevSelectedNodes;
|
||||
});
|
||||
|
||||
_prevSelectedEdges: EdgeType[] = [];
|
||||
_prevSelectedEdgeIds = new Set<string>();
|
||||
selectedEdges = $derived.by(() => {
|
||||
const selectedEdgesCount = this._prevSelectedEdgeIds.size;
|
||||
const selectedEdgeIds = new Set<string>();
|
||||
const selectedEdges = this.edges.filter((edge) => {
|
||||
if (edge.selected) {
|
||||
selectedEdgeIds.add(edge.id);
|
||||
this._prevSelectedEdgeIds.delete(edge.id);
|
||||
}
|
||||
return edge.selected;
|
||||
});
|
||||
// Either the number of selected edges has changed or two edges changed their selection state
|
||||
// at the same time. However then the previously selected edge will be inside _prevSelectedEdgeIds
|
||||
if (selectedEdgesCount !== selectedEdgeIds.size || this._prevSelectedEdgeIds.size > 0) {
|
||||
this._prevSelectedEdges = selectedEdges;
|
||||
}
|
||||
this._prevSelectedEdgeIds = selectedEdgeIds;
|
||||
return this._prevSelectedEdges;
|
||||
});
|
||||
|
||||
selectionChangedHandlers = new Map<symbol, OnSelectionChanged<NodeType, EdgeType>>();
|
||||
|
||||
nodeLookup: NodeLookup<InternalNode<NodeType>> = new Map();
|
||||
parentLookup: ParentLookup<InternalNode<NodeType>> = new Map();
|
||||
connectionLookup: ConnectionLookup = new Map();
|
||||
|
||||
@@ -43,3 +43,8 @@ export type OnBeforeDelete<
|
||||
export type IsValidConnection<EdgeType extends Edge = Edge> = (
|
||||
edge: EdgeType | Connection
|
||||
) => boolean;
|
||||
|
||||
export type OnSelectionChanged<
|
||||
NodeType extends Node = Node,
|
||||
EdgeType extends Edge = Edge
|
||||
> = (params: { nodes: NodeType[]; edges: EdgeType[] }) => void;
|
||||
|
||||
Reference in New Issue
Block a user