Files
xyflow/packages/react/src/container/ZoomPane/index.tsx
T
Moritz KlackandGitHub ae33cba887 feat(react/svelte): controlled viewport (#3461)
* feat(react): controlled viewport

* refactor(react): cleanup viewport

* feat(svelte): two way data binding for viewport, cleanup transform <> viewport stuff

* refactor(svelte): viewport stuff
2023-09-28 16:19:03 +02:00

148 lines
3.9 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-explicit-any */
import { 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'
| 'selectionOnDrag'
> & {
isControlledViewport: boolean;
};
const selector = (s: ReactFlowState) => ({
userSelectionActive: s.userSelectionActive,
lib: s.lib,
});
const 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,
}: ZoomPaneProps) => {
const store = useStoreApi();
const zoomPane = useRef<HTMLDivElement>(null);
const { userSelectionActive, lib } = useStore(selector, shallow);
const zoomActivationKeyPressed = useKeyPress(zoomActivationKeyCode);
const panZoom = useRef<PanZoomInstance>();
useResizeHandler(zoomPane);
useEffect(() => {
if (zoomPane.current) {
panZoom.current = XYPanZoom({
domNode: zoomPane.current,
minZoom,
maxZoom,
translateExtent,
viewport: defaultViewport,
onTransformChange: (transform: Transform) => {
onViewportChange?.({ x: transform[0], y: transform[1], zoom: transform[2] });
if (!isControlledViewport) {
store.setState({ transform });
}
},
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,
});
}, [
onPaneContextMenu,
zoomOnScroll,
zoomOnPinch,
panOnScroll,
panOnScrollSpeed,
panOnScrollMode,
zoomOnDoubleClick,
panOnDrag,
zoomActivationKeyPressed,
preventScrolling,
noPanClassName,
userSelectionActive,
noWheelClassName,
lib,
]);
return (
<div className="react-flow__renderer" ref={zoomPane} style={containerStyle}>
{children}
</div>
);
};
export default ZoomPane;