feat(hooks): add useOnViewportChange

This commit is contained in:
moklick
2022-08-10 16:01:29 +02:00
parent a5a9a60c11
commit 820cb1c880
7 changed files with 87 additions and 9 deletions
@@ -1,5 +1,5 @@
import React, { memo, FC, CSSProperties } from 'react'; import React, { memo, FC, CSSProperties } from 'react';
import { Handle, Position, NodeProps, Connection, Edge } from '@react-flow/bundle'; import { Handle, Position, NodeProps, Connection, Edge, useOnViewportChange } from '@react-flow/bundle';
const targetHandleStyle: CSSProperties = { background: '#555' }; const targetHandleStyle: CSSProperties = { background: '#555' };
const sourceHandleStyleA: CSSProperties = { ...targetHandleStyle, top: 10 }; const sourceHandleStyleA: CSSProperties = { ...targetHandleStyle, top: 10 };
@@ -12,6 +12,12 @@ const sourceHandleStyleB: CSSProperties = {
const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params); const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params);
const ColorSelectorNode: FC<NodeProps> = ({ data, isConnectable }) => { const ColorSelectorNode: FC<NodeProps> = ({ data, isConnectable }) => {
useOnViewportChange({
onStart: (viewport) => console.log('start', viewport),
onChange: (viewport) => console.log('change', viewport),
onEnd: (viewport) => console.log('end', viewport),
});
return ( return (
<> <>
<Handle type="target" position={Position.Left} style={targetHandleStyle} onConnect={onConnect} /> <Handle type="target" position={Position.Left} style={targetHandleStyle} onConnect={onConnect} />
@@ -2,7 +2,7 @@ import { memo, useEffect } from 'react';
import shallow from 'zustand/shallow'; import shallow from 'zustand/shallow';
import { ReactFlowState, OnSelectionChangeFunc, Node, Edge } from '../../types'; import { ReactFlowState, OnSelectionChangeFunc, Node, Edge } from '../../types';
import { useStore } from '../../hooks/useStore'; import { useStore, useStoreApi } from '../../hooks/useStore';
interface SelectionListenerProps { interface SelectionListenerProps {
onSelectionChange: OnSelectionChangeFunc; onSelectionChange: OnSelectionChangeFunc;
@@ -28,10 +28,14 @@ function areEqual(objA: SelectorSlice, objB: SelectorSlice) {
// This is just a helper component for calling the onSelectionChange listener. // This is just a helper component for calling the onSelectionChange listener.
// @TODO: Now that we have the onNodesChange and on EdgesChange listeners, do we still need this component? // @TODO: Now that we have the onNodesChange and on EdgesChange listeners, do we still need this component?
function SelectionListener({ onSelectionChange }: SelectionListenerProps) { function SelectionListener({ onSelectionChange }: SelectionListenerProps) {
const store = useStoreApi();
const { selectedNodes, selectedEdges } = useStore(selector, areEqual); const { selectedNodes, selectedEdges } = useStore(selector, areEqual);
useEffect(() => { useEffect(() => {
onSelectionChange({ nodes: selectedNodes, edges: selectedEdges }); const params = { nodes: selectedNodes, edges: selectedEdges };
onSelectionChange(params);
store.getState().onSelectionChange?.(params);
}, [selectedNodes, selectedEdges]); }, [selectedNodes, selectedEdges]);
return null; return null;
+18 -6
View File
@@ -155,10 +155,15 @@ const ZoomPane = ({
d3Zoom.on('zoom', null); d3Zoom.on('zoom', null);
} else if (!selectionKeyPressed) { } else if (!selectionKeyPressed) {
d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => { d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
const { onViewportChange } = store.getState();
store.setState({ transform: [event.transform.x, event.transform.y, event.transform.k] }); store.setState({ transform: [event.transform.x, event.transform.y, event.transform.k] });
if (onMove) {
if (onMove || onViewportChange) {
const flowTransform = eventToFlowTransform(event.transform); const flowTransform = eventToFlowTransform(event.transform);
onMove(event.sourceEvent as MouseEvent | TouchEvent, flowTransform);
onViewportChange?.(flowTransform);
onMove?.(event.sourceEvent as MouseEvent | TouchEvent, flowTransform);
} }
}); });
} }
@@ -168,15 +173,19 @@ const ZoomPane = ({
useEffect(() => { useEffect(() => {
if (d3Zoom) { if (d3Zoom) {
d3Zoom.on('start', (event: D3ZoomEvent<HTMLDivElement, any>) => { d3Zoom.on('start', (event: D3ZoomEvent<HTMLDivElement, any>) => {
const { onViewportChangeStart } = store.getState();
isZoomingOrPanning.current = true; isZoomingOrPanning.current = true;
if (event.sourceEvent?.type === 'mousedown') { if (event.sourceEvent?.type === 'mousedown') {
store.setState({ paneDragging: true }); store.setState({ paneDragging: true });
} }
if (onMoveStart) {
if (onMoveStart || onViewportChangeStart) {
const flowTransform = eventToFlowTransform(event.transform); const flowTransform = eventToFlowTransform(event.transform);
prevTransform.current = flowTransform; prevTransform.current = flowTransform;
onMoveStart(event.sourceEvent as MouseEvent | TouchEvent, flowTransform); onViewportChangeStart?.(flowTransform);
onMoveStart?.(event.sourceEvent as MouseEvent | TouchEvent, flowTransform);
} }
}); });
} }
@@ -185,14 +194,17 @@ const ZoomPane = ({
useEffect(() => { useEffect(() => {
if (d3Zoom) { if (d3Zoom) {
d3Zoom.on('end', (event: D3ZoomEvent<HTMLDivElement, any>) => { d3Zoom.on('end', (event: D3ZoomEvent<HTMLDivElement, any>) => {
const { onViewportChangeEnd } = store.getState();
isZoomingOrPanning.current = false; isZoomingOrPanning.current = false;
store.setState({ paneDragging: false }); store.setState({ paneDragging: false });
if (onMoveEnd && viewChanged(prevTransform.current, event.transform)) { if ((onMoveEnd || onViewportChangeEnd) && viewChanged(prevTransform.current, event.transform)) {
const flowTransform = eventToFlowTransform(event.transform); const flowTransform = eventToFlowTransform(event.transform);
prevTransform.current = flowTransform; prevTransform.current = flowTransform;
onMoveEnd(event.sourceEvent as MouseEvent | TouchEvent, flowTransform); onViewportChangeEnd?.(flowTransform);
onMoveEnd?.(event.sourceEvent as MouseEvent | TouchEvent, flowTransform);
} }
}); });
} }
@@ -0,0 +1,18 @@
import { useEffect } from 'react';
import { useStoreApi } from './useStore';
import type { OnSelectionChangeFunc } from '../types';
export type UseOnSelectionChangeOptions = {
onChange?: OnSelectionChangeFunc;
};
function useOnSelectionChange({ onChange }: UseOnSelectionChangeOptions) {
const store = useStoreApi();
useEffect(() => {
store.setState({ onSelectionChange: onChange });
}, [onChange]);
}
export default useOnSelectionChange;
@@ -0,0 +1,28 @@
import { useEffect } from 'react';
import { useStoreApi } from './useStore';
import type { OnViewportChange } from '../types';
export type UseOnViewportChangeOptions = {
onStart?: OnViewportChange;
onChange?: OnViewportChange;
onEnd?: OnViewportChange;
};
function useOnViewportChange({ onStart, onChange, onEnd }: UseOnViewportChangeOptions) {
const store = useStoreApi();
useEffect(() => {
store.setState({ onViewportChangeStart: onStart });
}, [onStart]);
useEffect(() => {
store.setState({ onViewportChange: onChange });
}, [onChange]);
useEffect(() => {
store.setState({ onViewportChangeEnd: onEnd });
}, [onEnd]);
}
export default useOnViewportChange;
+1
View File
@@ -40,5 +40,6 @@ export { default as useViewport } from './hooks/useViewport';
export { default as useKeyPress } from './hooks/useKeyPress'; export { default as useKeyPress } from './hooks/useKeyPress';
export * from './hooks/useNodesEdgesState'; export * from './hooks/useNodesEdgesState';
export { useStore, useStoreApi } from './hooks/useStore'; export { useStore, useStoreApi } from './hooks/useStore';
export { default as useOnViewportChange } from './hooks/useOnViewportChange';
export * from './types'; export * from './types';
+9
View File
@@ -112,6 +112,8 @@ export type UnselectNodesAndEdgesParams = {
edges?: Edge[]; edges?: Edge[];
}; };
export type OnViewportChange = (viewport: Viewport) => void;
export interface ViewportHelperFunctions { export interface ViewportHelperFunctions {
zoomIn: ZoomInOut; zoomIn: ZoomInOut;
zoomOut: ZoomInOut; zoomOut: ZoomInOut;
@@ -193,6 +195,13 @@ export type ReactFlowStore = {
onNodesDelete?: OnNodesDelete; onNodesDelete?: OnNodesDelete;
onEdgesDelete?: OnEdgesDelete; onEdgesDelete?: OnEdgesDelete;
// event handlers
onViewportChangeStart?: OnViewportChange;
onViewportChange?: OnViewportChange;
onViewportChangeEnd?: OnViewportChange;
onSelectionChange?: OnSelectionChangeFunc;
}; };
export type ReactFlowActions = { export type ReactFlowActions = {