fix(move-events): fire for panOnScroll

This commit is contained in:
moklick
2023-08-16 10:51:55 +02:00
parent 3122d06987
commit 258244f368
5 changed files with 93 additions and 48 deletions
+1 -1
View File
@@ -21,7 +21,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.3.0", "react-router-dom": "^6.3.0",
"zustand": "^4.3.1" "zustand": "^4.4.1"
}, },
"devDependencies": { "devDependencies": {
"@cypress/skip-test": "^2.6.1", "@cypress/skip-test": "^2.6.1",
+11 -3
View File
@@ -1,4 +1,4 @@
import { ReactFlow, Background, BackgroundVariant, Node, Edge, SelectionMode } from '@xyflow/react'; import { ReactFlow, Background, BackgroundVariant, Node, Edge, SelectionMode, Controls, Panel } from '@xyflow/react';
const MULTI_SELECT_KEY = ['Meta', 'Shift']; const MULTI_SELECT_KEY = ['Meta', 'Shift'];
@@ -21,6 +21,10 @@ const onPaneContextMenu = (e: any) => {
const panOnDrag = [1, 2]; const panOnDrag = [1, 2];
const onMoveStart = (e: any) => console.log('move start', e);
const onMove = (e: any) => console.log('move', e);
const onMoveEnd = (e: any) => console.log('move end', e);
const BasicFlow = () => { const BasicFlow = () => {
return ( return (
<ReactFlow <ReactFlow
@@ -36,11 +40,15 @@ const BasicFlow = () => {
fitView fitView
selectNodesOnDrag={false} selectNodesOnDrag={false}
onSelectionContextMenu={onPaneContextMenu} onSelectionContextMenu={onPaneContextMenu}
onMoveStart={onMoveStart}
onMove={onMove}
onMoveEnd={onMoveEnd}
> >
<Background variant={BackgroundVariant.Cross} /> <Background variant={BackgroundVariant.Cross} />
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}> <Controls />
<Panel position="top-right">
<input type={'text'} placeholder={'name'} /> <input type={'text'} placeholder={'name'} />
</div> </Panel>
</ReactFlow> </ReactFlow>
); );
}; };
@@ -26,6 +26,8 @@ export type ZoomPanValues = {
prevViewport: Viewport; prevViewport: Viewport;
mouseButton: number; mouseButton: number;
timerId: ReturnType<typeof setTimeout> | undefined; timerId: ReturnType<typeof setTimeout> | undefined;
panScrollTimeout: ReturnType<typeof setTimeout> | undefined;
isPanScrolling: boolean;
}; };
export function XYPanZoom({ export function XYPanZoom({
@@ -46,6 +48,8 @@ export function XYPanZoom({
prevViewport: { x: 0, y: 0, zoom: 0 }, prevViewport: { x: 0, y: 0, zoom: 0 },
mouseButton: 0, mouseButton: 0,
timerId: undefined, timerId: undefined,
panScrollTimeout: undefined,
isPanScrolling: false,
}; };
const bbox = domNode.getBoundingClientRect(); const bbox = domNode.getBoundingClientRect();
const d3ZoomInstance = zoom().scaleExtent([minZoom, maxZoom]).translateExtent(translateExtent); const d3ZoomInstance = zoom().scaleExtent([minZoom, maxZoom]).translateExtent(translateExtent);
@@ -98,12 +102,16 @@ export function XYPanZoom({
const wheelHandler = isPanOnScroll const wheelHandler = isPanOnScroll
? createPanOnScrollHandler({ ? createPanOnScrollHandler({
zoomPanValues,
noWheelClassName, noWheelClassName,
d3Selection, d3Selection,
d3Zoom: d3ZoomInstance, d3Zoom: d3ZoomInstance,
panOnScrollMode, panOnScrollMode,
panOnScrollSpeed, panOnScrollSpeed,
zoomOnPinch, zoomOnPinch,
onPanZoomStart,
onPanZoom,
onPanZoomEnd,
}) })
: createZoomOnScrollHandler({ : createZoomOnScrollHandler({
noWheelClassName, noWheelClassName,
+45 -12
View File
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/ban-ts-comment */
import type { D3ZoomEvent } from 'd3-zoom'; import type { D3ZoomEvent } from 'd3-zoom';
import { pointer } from 'd3-selection'; import { pointer } from 'd3-selection';
@@ -8,28 +9,24 @@ import {
type D3ZoomHandler, type D3ZoomHandler,
type D3ZoomInstance, type D3ZoomInstance,
type OnPanZoom, type OnPanZoom,
type Viewport,
type OnDraggingChange, type OnDraggingChange,
type OnTransformChange, type OnTransformChange,
} from '../types'; } from '../types';
import { isRightClickPan, isWrappedWithClass, transformToViewport, viewChanged, wheelDelta } from './utils'; import { isRightClickPan, isWrappedWithClass, transformToViewport, viewChanged, wheelDelta } from './utils';
import { clamp, isMacOs } from '../utils'; import { isMacOs } from '../utils';
import { ZoomPanValues } from './XYPanZoom';
export type ZoomPanValues = {
isZoomingOrPanning: boolean;
usedRightMouseButton: boolean;
prevViewport: Viewport;
mouseButton: number;
timerId: ReturnType<typeof setTimeout> | undefined;
};
export type PanOnScrollParams = { export type PanOnScrollParams = {
zoomPanValues: ZoomPanValues;
noWheelClassName: string; noWheelClassName: string;
d3Selection: D3SelectionInstance; d3Selection: D3SelectionInstance;
d3Zoom: D3ZoomInstance; d3Zoom: D3ZoomInstance;
panOnScrollMode: PanOnScrollMode; panOnScrollMode: PanOnScrollMode;
panOnScrollSpeed: number; panOnScrollSpeed: number;
zoomOnPinch: boolean; zoomOnPinch: boolean;
onPanZoomStart?: OnPanZoom;
onPanZoom?: OnPanZoom;
onPanZoomEnd?: OnPanZoom;
}; };
export type ZoomOnScrollParams = { export type ZoomOnScrollParams = {
@@ -62,12 +59,16 @@ export type PanZoomEndParams = {
}; };
export function createPanOnScrollHandler({ export function createPanOnScrollHandler({
zoomPanValues,
noWheelClassName, noWheelClassName,
d3Selection, d3Selection,
d3Zoom, d3Zoom,
panOnScrollMode, panOnScrollMode,
panOnScrollSpeed, panOnScrollSpeed,
zoomOnPinch, zoomOnPinch,
onPanZoomStart,
onPanZoom,
onPanZoomEnd,
}: PanOnScrollParams) { }: PanOnScrollParams) {
return (event: any) => { return (event: any) => {
if (isWrappedWithClass(event, noWheelClassName)) { if (isWrappedWithClass(event, noWheelClassName)) {
@@ -105,8 +106,33 @@ export function createPanOnScrollHandler({
d3Zoom.translateBy( d3Zoom.translateBy(
d3Selection, d3Selection,
-(deltaX / currentZoom) * panOnScrollSpeed, -(deltaX / currentZoom) * panOnScrollSpeed,
-(deltaY / currentZoom) * panOnScrollSpeed -(deltaY / currentZoom) * panOnScrollSpeed,
// @ts-ignore
{ internal: true }
); );
const nextViewport = transformToViewport(d3Selection.property('__zoom'));
clearTimeout(zoomPanValues.panScrollTimeout);
// for pan on scroll we need to handle the event calls on our own
// we can't use the start, zoom and end events from d3-zoom
// because start and move gets called on every scroll event and not once at the beginning
if (!zoomPanValues.isPanScrolling) {
zoomPanValues.isPanScrolling = true;
onPanZoomStart?.(event, nextViewport);
}
if (zoomPanValues.isPanScrolling) {
onPanZoom?.(event, nextViewport);
zoomPanValues.panScrollTimeout = setTimeout(() => {
onPanZoomEnd?.(event, nextViewport);
zoomPanValues.isPanScrolling = false;
}, 150);
}
}; };
} }
@@ -124,6 +150,10 @@ export function createZoomOnScrollHandler({ noWheelClassName, preventScrolling,
export function createPanZoomStartHandler({ zoomPanValues, onDraggingChange, onPanZoomStart }: PanZoomStartParams) { export function createPanZoomStartHandler({ zoomPanValues, onDraggingChange, onPanZoomStart }: PanZoomStartParams) {
return (event: D3ZoomEvent<HTMLDivElement, any>) => { return (event: D3ZoomEvent<HTMLDivElement, any>) => {
if (event.sourceEvent?.internal) {
return;
}
const viewport = transformToViewport(event.transform); const viewport = transformToViewport(event.transform);
// we need to remember it here, because it's always 0 in the "zoom" event // we need to remember it here, because it's always 0 in the "zoom" event
@@ -155,7 +185,7 @@ export function createPanZoomHandler({
onTransformChange([event.transform.x, event.transform.y, event.transform.k]); onTransformChange([event.transform.x, event.transform.y, event.transform.k]);
if (onPanZoom) { if (onPanZoom && !event.sourceEvent?.internal) {
onPanZoom?.(event.sourceEvent as MouseEvent | TouchEvent, transformToViewport(event.transform)); onPanZoom?.(event.sourceEvent as MouseEvent | TouchEvent, transformToViewport(event.transform));
} }
}; };
@@ -170,6 +200,9 @@ export function createPanZoomEndHandler({
onPaneContextMenu, onPaneContextMenu,
}: PanZoomEndParams) { }: PanZoomEndParams) {
return (event: D3ZoomEvent<HTMLDivElement, any>) => { return (event: D3ZoomEvent<HTMLDivElement, any>) => {
if (event.sourceEvent?.internal) {
return;
}
zoomPanValues.isZoomingOrPanning = false; zoomPanValues.isZoomingOrPanning = false;
if ( if (
+28 -32
View File
@@ -99,8 +99,8 @@ importers:
specifier: ^6.3.0 specifier: ^6.3.0
version: registry.npmjs.org/react-router-dom@6.12.1(react-dom@18.2.0)(react@18.2.0) version: registry.npmjs.org/react-router-dom@6.12.1(react-dom@18.2.0)(react@18.2.0)
zustand: zustand:
specifier: ^4.3.1 specifier: ^4.4.1
version: registry.npmjs.org/zustand@4.3.8(react@18.2.0) version: registry.npmjs.org/zustand@4.4.1(@types/react@18.2.12)(react@18.2.0)
devDependencies: devDependencies:
'@cypress/skip-test': '@cypress/skip-test':
specifier: ^2.6.1 specifier: ^2.6.1
@@ -2421,7 +2421,7 @@ packages:
'@typescript-eslint/typescript-estree': registry.npmjs.org/@typescript-eslint/typescript-estree@5.60.0(typescript@5.1.3) '@typescript-eslint/typescript-estree': registry.npmjs.org/@typescript-eslint/typescript-estree@5.60.0(typescript@5.1.3)
eslint: registry.npmjs.org/eslint@8.43.0 eslint: registry.npmjs.org/eslint@8.43.0
eslint-scope: registry.npmjs.org/eslint-scope@5.1.1 eslint-scope: registry.npmjs.org/eslint-scope@5.1.1
semver: registry.npmjs.org/semver@7.5.1 semver: registry.npmjs.org/semver@7.5.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
- typescript - typescript
@@ -4140,7 +4140,7 @@ packages:
dependencies: dependencies:
acorn: registry.npmjs.org/acorn@8.8.2 acorn: registry.npmjs.org/acorn@8.8.2
acorn-jsx: registry.npmjs.org/acorn-jsx@5.3.2(acorn@8.8.2) acorn-jsx: registry.npmjs.org/acorn-jsx@5.3.2(acorn@8.8.2)
eslint-visitor-keys: registry.npmjs.org/eslint-visitor-keys@3.4.1 eslint-visitor-keys: registry.npmjs.org/eslint-visitor-keys@3.4.3
dev: true dev: true
registry.npmjs.org/esprima@4.0.1: registry.npmjs.org/esprima@4.0.1:
@@ -5076,14 +5076,6 @@ packages:
ci-info: registry.npmjs.org/ci-info@3.8.0 ci-info: registry.npmjs.org/ci-info@3.8.0
dev: true dev: true
registry.npmjs.org/is-core-module@2.12.1:
resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz}
name: is-core-module
version: 2.12.1
dependencies:
has: registry.npmjs.org/has@1.0.3
dev: true
registry.npmjs.org/is-core-module@2.13.0: registry.npmjs.org/is-core-module@2.13.0:
resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz} resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz}
name: is-core-module name: is-core-module
@@ -6772,7 +6764,7 @@ packages:
version: 1.22.2 version: 1.22.2
hasBin: true hasBin: true
dependencies: dependencies:
is-core-module: registry.npmjs.org/is-core-module@2.12.1 is-core-module: registry.npmjs.org/is-core-module@2.13.0
path-parse: registry.npmjs.org/path-parse@1.0.7 path-parse: registry.npmjs.org/path-parse@1.0.7
supports-preserve-symlinks-flag: registry.npmjs.org/supports-preserve-symlinks-flag@1.0.0 supports-preserve-symlinks-flag: registry.npmjs.org/supports-preserve-symlinks-flag@1.0.0
dev: true dev: true
@@ -8338,25 +8330,6 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
registry.npmjs.org/zustand@4.3.8(react@18.2.0):
resolution: {integrity: sha512-4h28KCkHg5ii/wcFFJ5Fp+k1J3gJoasaIbppdgZFO4BPJnsNxL0mQXBSFgOgAdCdBj35aDTPvdAJReTMntFPGg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/zustand/-/zustand-4.3.8.tgz}
id: registry.npmjs.org/zustand/4.3.8
name: zustand
version: 4.3.8
engines: {node: '>=12.7.0'}
peerDependencies:
immer: '>=9.0'
react: '>=16.8'
peerDependenciesMeta:
immer:
optional: true
react:
optional: true
dependencies:
react: registry.npmjs.org/react@18.2.0
use-sync-external-store: registry.npmjs.org/use-sync-external-store@1.2.0(react@18.2.0)
dev: false
registry.npmjs.org/zustand@4.4.0(@types/react@18.2.12)(react@18.2.0): registry.npmjs.org/zustand@4.4.0(@types/react@18.2.12)(react@18.2.0):
resolution: {integrity: sha512-2dq6wq4dSxbiPTamGar0NlIG/av0wpyWZJGeQYtUOLegIUvhM2Bf86ekPlmgpUtS5uR7HyetSiktYrGsdsyZgQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/zustand/-/zustand-4.4.0.tgz} resolution: {integrity: sha512-2dq6wq4dSxbiPTamGar0NlIG/av0wpyWZJGeQYtUOLegIUvhM2Bf86ekPlmgpUtS5uR7HyetSiktYrGsdsyZgQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/zustand/-/zustand-4.4.0.tgz}
id: registry.npmjs.org/zustand/4.4.0 id: registry.npmjs.org/zustand/4.4.0
@@ -8379,3 +8352,26 @@ packages:
react: registry.npmjs.org/react@18.2.0 react: registry.npmjs.org/react@18.2.0
use-sync-external-store: registry.npmjs.org/use-sync-external-store@1.2.0(react@18.2.0) use-sync-external-store: registry.npmjs.org/use-sync-external-store@1.2.0(react@18.2.0)
dev: false dev: false
registry.npmjs.org/zustand@4.4.1(@types/react@18.2.12)(react@18.2.0):
resolution: {integrity: sha512-QCPfstAS4EBiTQzlaGP1gmorkh/UL1Leaj2tdj+zZCZ/9bm0WS7sI2wnfD5lpOszFqWJ1DcPnGoY8RDL61uokw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/zustand/-/zustand-4.4.1.tgz}
id: registry.npmjs.org/zustand/4.4.1
name: zustand
version: 4.4.1
engines: {node: '>=12.7.0'}
peerDependencies:
'@types/react': '>=16.8'
immer: '>=9.0'
react: '>=16.8'
peerDependenciesMeta:
'@types/react':
optional: true
immer:
optional: true
react:
optional: true
dependencies:
'@types/react': registry.npmjs.org/@types/react@18.2.12
react: registry.npmjs.org/react@18.2.0
use-sync-external-store: registry.npmjs.org/use-sync-external-store@1.2.0(react@18.2.0)
dev: false