implement onSelectionChanged
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
export const ssr = false;
|
||||||
|
export const prerender = false;
|
||||||
@@ -1,11 +1,21 @@
|
|||||||
<script lang="ts">
|
<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 {
|
let {
|
||||||
data = { label: 'Node' },
|
data = { label: 'Node' },
|
||||||
positionAbsoluteX = 0,
|
positionAbsoluteX = 0,
|
||||||
positionAbsoluteY = 0
|
positionAbsoluteY = 0
|
||||||
}: NodeProps<BuiltInNode> = $props();
|
}: NodeProps<BuiltInNode> = $props();
|
||||||
|
|
||||||
|
useSelectionChanged(({ nodes, edges }) => {
|
||||||
|
console.log('on selection changed via hook', { nodes, edges });
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="custom">
|
<div class="custom">
|
||||||
|
|||||||
@@ -216,6 +216,10 @@
|
|||||||
connectionMode={ConnectionMode.Strict}
|
connectionMode={ConnectionMode.Strict}
|
||||||
attributionPosition={'top-center'}
|
attributionPosition={'top-center'}
|
||||||
deleteKey={['Backspace', 'd']}
|
deleteKey={['Backspace', 'd']}
|
||||||
|
onselectionchanged={({ nodes, edges }) => {
|
||||||
|
console.log('on selection changed via prop', { nodes, edges });
|
||||||
|
}}
|
||||||
|
selectNodesOnDrag
|
||||||
>
|
>
|
||||||
<Controls orientation="horizontal" {fitViewOptions}>
|
<Controls orientation="horizontal" {fitViewOptions}>
|
||||||
{#snippet before()}
|
{#snippet before()}
|
||||||
@@ -240,7 +244,6 @@
|
|||||||
}}>left-right</button
|
}}>left-right</button
|
||||||
>
|
>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
<InitTracker />
|
<InitTracker />
|
||||||
</SvelteFlow>
|
</SvelteFlow>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { defineConfig } from 'vite';
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [sveltekit()],
|
plugins: [sveltekit()],
|
||||||
build: {
|
build: {
|
||||||
sourcemap: true
|
sourcemap: true,
|
||||||
|
minify: false
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
fs: {
|
fs: {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
import { portal } from '$lib/actions/portal';
|
import { portal } from '$lib/actions/portal';
|
||||||
import type { ViewportPortalProps } from './types';
|
import type { ViewportPortalProps } from './types';
|
||||||
|
|
||||||
let { moveTo = 'front', children, ...rest }: ViewportPortalProps = $props();
|
let { target = 'front', children, ...rest }: ViewportPortalProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:portal={`viewport-${moveTo}`} {...rest}>
|
<div use:portal={`viewport-${target}`} {...rest}>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ import type { Snippet } from 'svelte';
|
|||||||
import type { HTMLAttributes } from 'svelte/elements';
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
export type ViewportPortalProps = {
|
export type ViewportPortalProps = {
|
||||||
moveTo: 'front' | 'back';
|
target: 'front' | 'back';
|
||||||
children?: Snippet;
|
children?: Snippet;
|
||||||
} & HTMLAttributes<HTMLDivElement>;
|
} & HTMLAttributes<HTMLDivElement>;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
|
<script lang="ts" generics="NodeType extends Node = Node, EdgeType extends Edge = Edge">
|
||||||
import type { Edge, Node } from '$lib/types';
|
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 type { HTMLAttributes } from 'svelte/elements';
|
||||||
import { ConnectionLineType, PanOnScrollMode } from '@xyflow/system';
|
import { ConnectionLineType, PanOnScrollMode } from '@xyflow/system';
|
||||||
|
|
||||||
@@ -110,6 +110,15 @@
|
|||||||
}
|
}
|
||||||
} satisfies StoreContext<NodeType, EdgeType>);
|
} 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(() => {
|
onDestroy(() => {
|
||||||
store.reset();
|
store.reset();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
onclickconnectstart,
|
onclickconnectstart,
|
||||||
onclickconnectend,
|
onclickconnectend,
|
||||||
oninit,
|
oninit,
|
||||||
|
onselectionchanged,
|
||||||
clickConnect,
|
clickConnect,
|
||||||
fitView,
|
fitView,
|
||||||
fitViewOptions,
|
fitViewOptions,
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ import type {
|
|||||||
OnBeforeConnect,
|
OnBeforeConnect,
|
||||||
OnBeforeDelete,
|
OnBeforeDelete,
|
||||||
IsValidConnection,
|
IsValidConnection,
|
||||||
OnBeforeReconnect
|
OnBeforeReconnect,
|
||||||
|
OnSelectionChanged
|
||||||
} from '$lib/types';
|
} from '$lib/types';
|
||||||
|
|
||||||
import type { Component } from 'svelte';
|
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
|
* If you have custom connection logic its preferred to use this callback over the
|
||||||
* `isValidConnection` prop on the handle component for performance reasons.
|
* `isValidConnection` prop on the handle component for performance reasons.
|
||||||
*/
|
*/
|
||||||
|
/** Toggles ability to make connections via clicking the handles */
|
||||||
|
clickConnect?: boolean;
|
||||||
isValidConnection?: IsValidConnection;
|
isValidConnection?: IsValidConnection;
|
||||||
/** This event handler is called when the user begins to pan or zoom the viewport */
|
/** This event handler is called when the user begins to pan or zoom the viewport */
|
||||||
onmovestart?: OnMoveStart;
|
onmovestart?: OnMoveStart;
|
||||||
@@ -434,8 +437,8 @@ export type SvelteFlowProps<
|
|||||||
onclickconnectstart?: OnConnectStart;
|
onclickconnectstart?: OnConnectStart;
|
||||||
/** A connection is finished by clicking on a handle */
|
/** A connection is finished by clicking on a handle */
|
||||||
onclickconnectend?: OnConnectEnd;
|
onclickconnectend?: OnConnectEnd;
|
||||||
/** Toggles ability to make connections via clicking the handles */
|
|
||||||
clickConnect?: boolean;
|
|
||||||
/** This handler gets called when the flow is finished initializing */
|
/** This handler gets called when the flow is finished initializing */
|
||||||
oninit?: () => void;
|
oninit?: () => void;
|
||||||
|
/** This event handler gets called when the selected nodes & edges change */
|
||||||
|
onselectionchanged?: OnSelectionChanged<NodeType, EdgeType>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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/useNodesData.svelte';
|
||||||
export * from '$lib/hooks/useInternalNode.svelte';
|
export * from '$lib/hooks/useInternalNode.svelte';
|
||||||
export * from '$lib/hooks/useInitialized.svelte';
|
export * from '$lib/hooks/useInitialized.svelte';
|
||||||
|
export * from '$lib/hooks/useSelectionChanged.svelte';
|
||||||
|
|
||||||
//actions
|
//actions
|
||||||
export * from '$lib/actions/portal';
|
export * from '$lib/actions/portal';
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ import type {
|
|||||||
Node,
|
Node,
|
||||||
EdgeLayouted,
|
EdgeLayouted,
|
||||||
InternalNode,
|
InternalNode,
|
||||||
OnBeforeReconnect
|
OnBeforeReconnect,
|
||||||
|
OnSelectionChanged
|
||||||
} from '$lib/types';
|
} from '$lib/types';
|
||||||
|
|
||||||
import type { StoreSignals } from './types';
|
import type { StoreSignals } from './types';
|
||||||
@@ -138,6 +139,52 @@ export function getInitialStore<NodeType extends Node = Node, EdgeType extends E
|
|||||||
signals.edges = edges;
|
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();
|
nodeLookup: NodeLookup<InternalNode<NodeType>> = new Map();
|
||||||
parentLookup: ParentLookup<InternalNode<NodeType>> = new Map();
|
parentLookup: ParentLookup<InternalNode<NodeType>> = new Map();
|
||||||
connectionLookup: ConnectionLookup = new Map();
|
connectionLookup: ConnectionLookup = new Map();
|
||||||
|
|||||||
@@ -43,3 +43,8 @@ export type OnBeforeDelete<
|
|||||||
export type IsValidConnection<EdgeType extends Edge = Edge> = (
|
export type IsValidConnection<EdgeType extends Edge = Edge> = (
|
||||||
edge: EdgeType | Connection
|
edge: EdgeType | Connection
|
||||||
) => boolean;
|
) => 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