/* eslint-disable @typescript-eslint/no-explicit-any */ import { useCallback, useEffect, useRef } from 'react'; import { shallow } from 'zustand/shallow'; import { XYPanZoom, PanOnScrollMode, type Transform, type PanZoomInstance } from '@xyflow/system'; import { useKeyPress } from '../../hooks/useKeyPress'; import { useResizeHandler } from '../../hooks/useResizeHandler'; import { useStore, useStoreApi } from '../../hooks/useStore'; import { containerStyle } from '../../styles/utils'; import type { FlowRendererProps } from '../FlowRenderer'; import type { ReactFlowState } from '../../types'; type ZoomPaneProps = Omit< FlowRendererProps, 'deleteKeyCode' | 'selectionKeyCode' | 'multiSelectionKeyCode' | 'noDragClassName' | 'disableKeyboardA11y' > & { isControlledViewport: boolean; }; const selector = (s: ReactFlowState) => ({ userSelectionActive: s.userSelectionActive, lib: s.lib, connectionInProgress: s.connection.inProgress, }); export function ZoomPane({ onPaneContextMenu, zoomOnScroll = true, zoomOnPinch = true, panOnScroll = false, panOnScrollSpeed = 0.5, panOnScrollMode = PanOnScrollMode.Free, zoomOnDoubleClick = true, panOnDrag = true, defaultViewport, translateExtent, minZoom, maxZoom, zoomActivationKeyCode, preventScrolling = true, children, noWheelClassName, noPanClassName, onViewportChange, isControlledViewport, paneClickDistance, selectionOnDrag, }: ZoomPaneProps) { const store = useStoreApi(); const zoomPane = useRef(null); const { userSelectionActive, lib, connectionInProgress } = useStore(selector, shallow); const zoomActivationKeyPressed = useKeyPress(zoomActivationKeyCode); const panZoom = useRef(); useResizeHandler(zoomPane); const onTransformChange = useCallback( (transform: Transform) => { onViewportChange?.({ x: transform[0], y: transform[1], zoom: transform[2] }); if (!isControlledViewport) { store.setState({ transform }); } }, [onViewportChange, isControlledViewport] ); useEffect(() => { if (zoomPane.current) { panZoom.current = XYPanZoom({ domNode: zoomPane.current, minZoom, maxZoom, translateExtent, viewport: defaultViewport, onDraggingChange: (paneDragging: boolean) => store.setState({ paneDragging }), onPanZoomStart: (event, vp) => { const { onViewportChangeStart, onMoveStart } = store.getState(); onMoveStart?.(event, vp); onViewportChangeStart?.(vp); }, onPanZoom: (event, vp) => { const { onViewportChange, onMove } = store.getState(); onMove?.(event, vp); onViewportChange?.(vp); }, onPanZoomEnd: (event, vp) => { const { onViewportChangeEnd, onMoveEnd } = store.getState(); onMoveEnd?.(event, vp); onViewportChangeEnd?.(vp); }, }); const { x, y, zoom } = panZoom.current.getViewport(); store.setState({ panZoom: panZoom.current, transform: [x, y, zoom], domNode: zoomPane.current.closest('.react-flow') as HTMLDivElement, }); return () => { panZoom.current?.destroy(); }; } }, []); useEffect(() => { panZoom.current?.update({ onPaneContextMenu, zoomOnScroll, zoomOnPinch, panOnScroll, panOnScrollSpeed, panOnScrollMode, zoomOnDoubleClick, panOnDrag, zoomActivationKeyPressed, preventScrolling, noPanClassName, userSelectionActive, noWheelClassName, lib, onTransformChange, connectionInProgress, selectionOnDrag, paneClickDistance, }); }, [ onPaneContextMenu, zoomOnScroll, zoomOnPinch, panOnScroll, panOnScrollSpeed, panOnScrollMode, zoomOnDoubleClick, panOnDrag, zoomActivationKeyPressed, preventScrolling, noPanClassName, userSelectionActive, noWheelClassName, lib, onTransformChange, connectionInProgress, selectionOnDrag, paneClickDistance, ]); return (
{children}
); }