Merge pull request #5047 from xyflow/fix/on-selection-change-generics

fix(OnSelectionChangeFunc): pass node and edge type generics
This commit is contained in:
Moritz Klack
2025-02-24 09:03:14 +01:00
committed by GitHub
7 changed files with 37 additions and 24 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@xyflow/react': patch
---
Pass generics to OnSelectionChangeFunc so that users can type it correctly
@@ -10,8 +10,8 @@ import { shallow } from 'zustand/shallow';
import { useStore, useStoreApi } from '../../hooks/useStore'; import { useStore, useStoreApi } from '../../hooks/useStore';
import type { ReactFlowState, OnSelectionChangeFunc, Node, Edge } from '../../types'; import type { ReactFlowState, OnSelectionChangeFunc, Node, Edge } from '../../types';
type SelectionListenerProps = { type SelectionListenerProps<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
onSelectionChange?: OnSelectionChangeFunc; onSelectionChange?: OnSelectionChangeFunc<NodeType, EdgeType>;
}; };
const selector = (s: ReactFlowState) => { const selector = (s: ReactFlowState) => {
@@ -44,12 +44,14 @@ function areEqual(a: SelectorSlice, b: SelectorSlice) {
); );
} }
function SelectionListenerInner({ onSelectionChange }: SelectionListenerProps) { function SelectionListenerInner<NodeType extends Node = Node, EdgeType extends Edge = Edge>({
const store = useStoreApi(); onSelectionChange,
}: SelectionListenerProps<NodeType, EdgeType>) {
const store = useStoreApi<NodeType, EdgeType>();
const { selectedNodes, selectedEdges } = useStore(selector, areEqual); const { selectedNodes, selectedEdges } = useStore(selector, areEqual);
useEffect(() => { useEffect(() => {
const params = { nodes: selectedNodes, edges: selectedEdges }; const params = { nodes: selectedNodes as NodeType[], edges: selectedEdges as EdgeType[] };
onSelectionChange?.(params); onSelectionChange?.(params);
store.getState().onSelectionChangeHandlers.forEach((fn) => fn(params)); store.getState().onSelectionChangeHandlers.forEach((fn) => fn(params));
@@ -60,11 +62,13 @@ function SelectionListenerInner({ onSelectionChange }: SelectionListenerProps) {
const changeSelector = (s: ReactFlowState) => !!s.onSelectionChangeHandlers; const changeSelector = (s: ReactFlowState) => !!s.onSelectionChangeHandlers;
export function SelectionListener({ onSelectionChange }: SelectionListenerProps) { export function SelectionListener<NodeType extends Node = Node, EdgeType extends Edge = Edge>({
onSelectionChange,
}: SelectionListenerProps<NodeType, EdgeType>) {
const storeHasSelectionChangeHandlers = useStore(changeSelector); const storeHasSelectionChangeHandlers = useStore(changeSelector);
if (onSelectionChange || storeHasSelectionChangeHandlers) { if (onSelectionChange || storeHasSelectionChangeHandlers) {
return <SelectionListenerInner onSelectionChange={onSelectionChange} />; return <SelectionListenerInner<NodeType, EdgeType> onSelectionChange={onSelectionChange} />;
} }
return null; return null;
@@ -303,7 +303,7 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
paneClickDistance={paneClickDistance} paneClickDistance={paneClickDistance}
debug={debug} debug={debug}
/> />
<SelectionListener onSelectionChange={onSelectionChange} /> <SelectionListener<NodeType, EdgeType> onSelectionChange={onSelectionChange} />
{children} {children}
<Attribution proOptions={proOptions} position={attributionPosition} /> <Attribution proOptions={proOptions} position={attributionPosition} />
<A11yDescriptions rfId={rfId} disableKeyboardA11y={disableKeyboardA11y} /> <A11yDescriptions rfId={rfId} disableKeyboardA11y={disableKeyboardA11y} />
@@ -1,10 +1,10 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useStoreApi } from './useStore'; import { useStoreApi } from './useStore';
import type { OnSelectionChangeFunc } from '../types'; import type { OnSelectionChangeFunc, Node, Edge } from '../types';
export type UseOnSelectionChangeOptions = { export type UseOnSelectionChangeOptions<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
onChange: OnSelectionChangeFunc; onChange: OnSelectionChangeFunc<NodeType, EdgeType>;
}; };
/** /**
@@ -45,8 +45,10 @@ export type UseOnSelectionChangeOptions = {
* *
* @remarks You need to memoize the passed `onChange` handler, otherwise the hook will not work correctly. * @remarks You need to memoize the passed `onChange` handler, otherwise the hook will not work correctly.
*/ */
export function useOnSelectionChange({ onChange }: UseOnSelectionChangeOptions) { export function useOnSelectionChange<NodeType extends Node = Node, EdgeType extends Edge = Edge>({
const store = useStoreApi(); onChange,
}: UseOnSelectionChangeOptions<NodeType, EdgeType>) {
const store = useStoreApi<NodeType, EdgeType>();
useEffect(() => { useEffect(() => {
const nextOnSelectionChangeHandlers = [...store.getState().onSelectionChangeHandlers, onChange]; const nextOnSelectionChangeHandlers = [...store.getState().onSelectionChangeHandlers, onChange];
+1 -1
View File
@@ -216,7 +216,7 @@ export interface ReactFlowProps<NodeType extends Node = Node, EdgeType extends E
/** This event handler gets called when a user stops panning or zooming the viewport */ /** This event handler gets called when a user stops panning or zooming the viewport */
onMoveEnd?: OnMoveEnd; onMoveEnd?: OnMoveEnd;
/** This event handler gets called when a user changes group of selected elements in the flow */ /** This event handler gets called when a user changes group of selected elements in the flow */
onSelectionChange?: OnSelectionChangeFunc; onSelectionChange?: OnSelectionChangeFunc<NodeType, EdgeType>;
/** This event handler gets called when user scroll inside the pane */ /** This event handler gets called when user scroll inside the pane */
onPaneScroll?: (event?: WheelEvent) => void; onPaneScroll?: (event?: WheelEvent) => void;
/** This event handler gets called when user clicks inside the pane */ /** This event handler gets called when user clicks inside the pane */
+9 -7
View File
@@ -85,17 +85,19 @@ export type EdgeTypes = Record<
> >
>; >;
export type UnselectNodesAndEdgesParams = { export type UnselectNodesAndEdgesParams<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
nodes?: Node[]; nodes?: NodeType[];
edges?: Edge[]; edges?: EdgeType[];
}; };
export type OnSelectionChangeParams = { export type OnSelectionChangeParams<NodeType extends Node = Node, EdgeType extends Edge = Edge> = {
nodes: Node[]; nodes: NodeType[];
edges: Edge[]; edges: EdgeType[];
}; };
export type OnSelectionChangeFunc = (params: OnSelectionChangeParams) => void; export type OnSelectionChangeFunc<NodeType extends Node = Node, EdgeType extends Edge = Edge> = (
params: OnSelectionChangeParams<NodeType, EdgeType>
) => void;
export type FitViewParams<NodeType extends Node = Node> = FitViewParamsBase<NodeType>; export type FitViewParams<NodeType extends Node = Node> = FitViewParamsBase<NodeType>;
+2 -2
View File
@@ -134,7 +134,7 @@ export type ReactFlowStore<NodeType extends Node = Node, EdgeType extends Edge =
onViewportChangeEnd?: OnViewportChange; onViewportChangeEnd?: OnViewportChange;
onBeforeDelete?: OnBeforeDelete<NodeType, EdgeType>; onBeforeDelete?: OnBeforeDelete<NodeType, EdgeType>;
onSelectionChangeHandlers: OnSelectionChangeFunc[]; onSelectionChangeHandlers: OnSelectionChangeFunc<NodeType, EdgeType>[];
ariaLiveMessage: string; ariaLiveMessage: string;
autoPanOnConnect: boolean; autoPanOnConnect: boolean;
@@ -155,7 +155,7 @@ export type ReactFlowActions<NodeType extends Node, EdgeType extends Edge> = {
updateNodeInternals: (updates: Map<string, InternalNodeUpdate>, params?: { triggerFitView: boolean }) => void; updateNodeInternals: (updates: Map<string, InternalNodeUpdate>, params?: { triggerFitView: boolean }) => void;
updateNodePositions: UpdateNodePositions; updateNodePositions: UpdateNodePositions;
resetSelectedElements: () => void; resetSelectedElements: () => void;
unselectNodesAndEdges: (params?: UnselectNodesAndEdgesParams) => void; unselectNodesAndEdges: (params?: UnselectNodesAndEdgesParams<NodeType, EdgeType>) => void;
addSelectedNodes: (nodeIds: string[]) => void; addSelectedNodes: (nodeIds: string[]) => void;
addSelectedEdges: (edgeIds: string[]) => void; addSelectedEdges: (edgeIds: string[]) => void;
setMinZoom: (minZoom: number) => void; setMinZoom: (minZoom: number) => void;