diff --git a/docs/Advanced/Plugins.mdx b/docs/Advanced/Plugins.mdx deleted file mode 100644 index deb09d55..00000000 --- a/docs/Advanced/Plugins.mdx +++ /dev/null @@ -1,45 +0,0 @@ -import { Meta } from '@storybook/addon-docs/blocks'; - - - -# Plugins - -Below is a list of some libraries and examples to further enhance reaflow. - -## Zooming and panning -> Super fast and light react npm package for zooming, panning and pinching html elements in easy way - -[React-zoom-pan-pinch](https://github.com/prc5/react-zoom-pan-pinch): By wrapping -the Reaflow graph in the `TransformWrapper` of this library Mobile gestures, -touchpad gestures and desktop mouse events are enabled. The graph can be -scrolled via the mouse wheel and panned by dragging the canvas. The library -provides a rich interface for interaction events. - -Properties: -- `wheel={{step: 40}}` - Define the zoom steps for each mouse wheel step -- `minScale` and `maxScale` - Define the minimum and maximum zoom -- `limitToBounds` - Define if the graph should be allowed to be pannable out of the view - -To mitigate complications, make sure to deactivate the reaflow native -zoom feature by setting `zoomable={false}`. - -```tsx -import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch"; -... - - - - - -``` diff --git a/package-lock.json b/package-lock.json index f9533a50..cb20a61d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,7 +58,6 @@ "prettier": "^3.2.5", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-zoom-pan-pinch": "^3.1.0", "rollup-plugin-peer-deps-external": "2.2.4", "storybook": "^7.6.17", "typescript": "^4.9.5", @@ -16964,20 +16963,6 @@ "react": ">= 16.8.0" } }, - "node_modules/react-zoom-pan-pinch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-3.1.0.tgz", - "integrity": "sha512-a3LlP8QPgTikvteCNkZ3X6wIWC0lrg1geP5WkUJyx2MXXAhHQek3r17N1nT/esOiWGuPIECnsd9AGoK8jOeGcg==", - "dev": true, - "engines": { - "node": ">=8", - "npm": ">=5" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, "node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -31924,13 +31909,6 @@ "integrity": "sha512-CXzUNkulUdgouaAlvAsC5ZVo0fi9KGSBSk81WrE4kOIcJccpANe9zZkAYr5YZZhqpicIFxitsrGVS4wmoMun9A==", "requires": {} }, - "react-zoom-pan-pinch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-3.1.0.tgz", - "integrity": "sha512-a3LlP8QPgTikvteCNkZ3X6wIWC0lrg1geP5WkUJyx2MXXAhHQek3r17N1nT/esOiWGuPIECnsd9AGoK8jOeGcg==", - "dev": true, - "requires": {} - }, "read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", diff --git a/package.json b/package.json index e09c205e..77253b06 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,6 @@ "prettier": "^3.2.5", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-zoom-pan-pinch": "^3.1.0", "rollup-plugin-peer-deps-external": "2.2.4", "storybook": "^7.6.17", "typescript": "^4.9.5", diff --git a/src/Canvas.module.css b/src/Canvas.module.css index 5526ea2b..0e598238 100644 --- a/src/Canvas.module.css +++ b/src/Canvas.module.css @@ -20,3 +20,12 @@ .dragNode { pointer-events: none; } + +.hideScrollbars { + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* Internet Explorer 10+ */ +} + +.hideScrollbars::-webkit-scrollbar { + display: none; /* WebKit */ +} diff --git a/src/Canvas.tsx b/src/Canvas.tsx index e46f7a92..fb44c779 100644 --- a/src/Canvas.tsx +++ b/src/Canvas.tsx @@ -1,5 +1,6 @@ import React, { FC, ReactElement, Ref, useImperativeHandle, forwardRef, useLayoutEffect, useRef, Fragment, useMemo, useState, useCallback, useEffect } from 'react'; import { useId, CloneElement } from 'reablocks'; +import { useGesture } from 'react-use-gesture'; import { Node, NodeDragType, NodeProps } from './symbols/Node'; import { Edge, EdgeProps } from './symbols/Edge'; import { ElkRoot, CanvasDirection, LayoutResult, ElkCanvasLayoutOptions } from './layout'; @@ -38,6 +39,11 @@ export interface CanvasContainerProps extends CanvasProps { */ pannable?: boolean; + /** + * Type of interaction to use for panning. + */ + panType?: 'scroll' | 'drag'; + /** * Whether the canvas is zoomable or not. */ @@ -180,7 +186,7 @@ export type CanvasRef = LayoutResult & ZoomResult; const InternalCanvas: FC }> = forwardRef(({ className, height = '100%', width = '100%', readonly, disabled = false, animated = true, arrow = , node = , edge = , dragNode = , dragEdge = , onMouseEnter = () => undefined, onMouseLeave = () => undefined, onCanvasClick = () => undefined }, ref: Ref) => { const id = useId(); - const { pannable, dragCoords, dragNode: canvasDragNode, layout, containerRef, svgRef, canvasHeight, canvasWidth, xy, zoom, setZoom, observe, zoomIn, zoomOut, positionCanvas, fitCanvas, setScrollXY, ...rest } = useCanvas(); + const { pannable, dragCoords, dragNode: canvasDragNode, layout, containerRef, svgRef, canvasHeight, canvasWidth, xy, zoom, setZoom, observe, zoomIn, zoomOut, positionCanvas, fitCanvas, setScrollXY, panType, ...rest } = useCanvas(); const [dragType, setDragType] = useState(null); useImperativeHandle(ref, () => ({ @@ -202,6 +208,8 @@ const InternalCanvas: FC }> = forwardRef(({ })); const mount = useRef(false); + const panStartScrollPosition = useRef<{ x: number; y: number }>({ x: 0, y: 0 }); + const dragNodeData = useMemo(() => getDragNodeData(canvasDragNode, layout?.children), [canvasDragNode, layout?.children]); const [dragNodeDataWithChildren, setDragNodeDataWithChildren] = useState<{ [key: string]: any; @@ -213,6 +221,39 @@ const InternalCanvas: FC }> = forwardRef(({ } }, [layout, xy]); + useGesture( + { + onDrag: ({ movement: [mx, my] }) => { + // Update container scroll position during drag + if (containerRef.current) { + containerRef.current.scrollLeft = panStartScrollPosition.current.x - mx; + containerRef.current.scrollTop = panStartScrollPosition.current.y - my; + } + }, + onDragStart: () => { + // Store the initial scroll position of the container when drag starts + panStartScrollPosition.current = { + x: containerRef.current?.scrollLeft || 0, + y: containerRef.current?.scrollTop || 0 + }; + }, + onWheel: ({ event, delta, last }) => { + !last && event.preventDefault(); + + if (delta[1] > 0) { + zoomOut(); + } else { + zoomIn(); + } + } + }, + { + enabled: pannable && panType === 'drag', + eventOptions: { passive: false }, + domTarget: containerRef + } + ); + const onDragStart = useCallback((event) => { setDragType(event.dragType); }, []); @@ -248,7 +289,8 @@ const InternalCanvas: FC }> = forwardRef(({
{ // Really not a fan of this API change... @@ -343,8 +385,8 @@ const InternalCanvas: FC }> = forwardRef(({ ); }); -export const Canvas: FC }> = forwardRef(({ selections = [], readonly = false, fit = false, nodes = [], edges = [], maxHeight = 2000, maxWidth = 2000, direction = 'DOWN', pannable = true, zoom = 1, defaultPosition = CanvasPosition.CENTER, zoomable = true, minZoom = -0.5, maxZoom = 1, onNodeLink = () => undefined, onNodeLinkCheck = () => undefined, onLayoutChange = () => undefined, onZoomChange = () => undefined, layoutOptions, ...rest }, ref: Ref) => ( - +export const Canvas: FC }> = forwardRef(({ selections = [], readonly = false, fit = false, nodes = [], edges = [], maxHeight = 2000, maxWidth = 2000, direction = 'DOWN', pannable = true, panType = 'scroll', zoom = 1, defaultPosition = CanvasPosition.CENTER, zoomable = true, minZoom = -0.5, maxZoom = 1, onNodeLink = () => undefined, onNodeLinkCheck = () => undefined, onLayoutChange = () => undefined, onZoomChange = () => undefined, layoutOptions, ...rest }, ref: Ref) => ( + )); diff --git a/src/utils/CanvasProvider.tsx b/src/utils/CanvasProvider.tsx index 94c2541d..f65836bf 100644 --- a/src/utils/CanvasProvider.tsx +++ b/src/utils/CanvasProvider.tsx @@ -11,6 +11,7 @@ export interface CanvasProviderValue selections?: string[]; readonly?: boolean; pannable: boolean; + panType: 'scroll' | 'drag'; } export const CanvasContext = createContext({} as any); @@ -43,6 +44,7 @@ export const CanvasProvider = ({ direction, layoutOptions, pannable, + panType, defaultPosition, zoomable, zoom, @@ -67,6 +69,7 @@ export const CanvasProvider = ({ maxWidth, direction, pannable, + panType, defaultPosition, fit, layoutOptions, @@ -86,6 +89,7 @@ export const CanvasProvider = ({ selections, readonly, pannable, + panType, ...layoutProps, ...zoomProps, ...dragProps diff --git a/stories/Controls.stories.tsx b/stories/Controls.stories.tsx index 1e6b299c..975e8a82 100644 --- a/stories/Controls.stories.tsx +++ b/stories/Controls.stories.tsx @@ -1,7 +1,6 @@ import React, { useRef, useState } from 'react'; import { Canvas, CanvasRef } from '../src/Canvas'; import { Node, Edge, MarkerArrow, Port, Icon, Arrow, Label, Remove, Add } from '../src/symbols'; -import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch'; import { CanvasPosition } from '../src/types'; export default { @@ -266,52 +265,38 @@ export const Zoom = () => { ); }; -export const ZoomExternal = () => { - const ref = useRef(null); - - return ( - - - - - - ); -}; +export const DragPan = () => ( +
+ console.log('Layout', layout)} + /> +
+);