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

View File

@@ -21,7 +21,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.3.0",
"zustand": "^4.3.1"
"zustand": "^4.4.1"
},
"devDependencies": {
"@cypress/skip-test": "^2.6.1",

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'];
@@ -21,6 +21,10 @@ const onPaneContextMenu = (e: any) => {
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 = () => {
return (
<ReactFlow
@@ -36,11 +40,15 @@ const BasicFlow = () => {
fitView
selectNodesOnDrag={false}
onSelectionContextMenu={onPaneContextMenu}
onMoveStart={onMoveStart}
onMove={onMove}
onMoveEnd={onMoveEnd}
>
<Background variant={BackgroundVariant.Cross} />
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
<Controls />
<Panel position="top-right">
<input type={'text'} placeholder={'name'} />
</div>
</Panel>
</ReactFlow>
);
};

View File

@@ -26,6 +26,8 @@ export type ZoomPanValues = {
prevViewport: Viewport;
mouseButton: number;
timerId: ReturnType<typeof setTimeout> | undefined;
panScrollTimeout: ReturnType<typeof setTimeout> | undefined;
isPanScrolling: boolean;
};
export function XYPanZoom({
@@ -46,6 +48,8 @@ export function XYPanZoom({
prevViewport: { x: 0, y: 0, zoom: 0 },
mouseButton: 0,
timerId: undefined,
panScrollTimeout: undefined,
isPanScrolling: false,
};
const bbox = domNode.getBoundingClientRect();
const d3ZoomInstance = zoom().scaleExtent([minZoom, maxZoom]).translateExtent(translateExtent);
@@ -98,12 +102,16 @@ export function XYPanZoom({
const wheelHandler = isPanOnScroll
? createPanOnScrollHandler({
zoomPanValues,
noWheelClassName,
d3Selection,
d3Zoom: d3ZoomInstance,
panOnScrollMode,
panOnScrollSpeed,
zoomOnPinch,
onPanZoomStart,
onPanZoom,
onPanZoomEnd,
})
: createZoomOnScrollHandler({
noWheelClassName,

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-ts-comment */
import type { D3ZoomEvent } from 'd3-zoom';
import { pointer } from 'd3-selection';
@@ -8,28 +9,24 @@ import {
type D3ZoomHandler,
type D3ZoomInstance,
type OnPanZoom,
type Viewport,
type OnDraggingChange,
type OnTransformChange,
} from '../types';
import { isRightClickPan, isWrappedWithClass, transformToViewport, viewChanged, wheelDelta } from './utils';
import { clamp, isMacOs } from '../utils';
export type ZoomPanValues = {
isZoomingOrPanning: boolean;
usedRightMouseButton: boolean;
prevViewport: Viewport;
mouseButton: number;
timerId: ReturnType<typeof setTimeout> | undefined;
};
import { isMacOs } from '../utils';
import { ZoomPanValues } from './XYPanZoom';
export type PanOnScrollParams = {
zoomPanValues: ZoomPanValues;
noWheelClassName: string;
d3Selection: D3SelectionInstance;
d3Zoom: D3ZoomInstance;
panOnScrollMode: PanOnScrollMode;
panOnScrollSpeed: number;
zoomOnPinch: boolean;
onPanZoomStart?: OnPanZoom;
onPanZoom?: OnPanZoom;
onPanZoomEnd?: OnPanZoom;
};
export type ZoomOnScrollParams = {
@@ -62,12 +59,16 @@ export type PanZoomEndParams = {
};
export function createPanOnScrollHandler({
zoomPanValues,
noWheelClassName,
d3Selection,
d3Zoom,
panOnScrollMode,
panOnScrollSpeed,
zoomOnPinch,
onPanZoomStart,
onPanZoom,
onPanZoomEnd,
}: PanOnScrollParams) {
return (event: any) => {
if (isWrappedWithClass(event, noWheelClassName)) {
@@ -105,8 +106,33 @@ export function createPanOnScrollHandler({
d3Zoom.translateBy(
d3Selection,
-(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) {
return (event: D3ZoomEvent<HTMLDivElement, any>) => {
if (event.sourceEvent?.internal) {
return;
}
const viewport = transformToViewport(event.transform);
// 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]);
if (onPanZoom) {
if (onPanZoom && !event.sourceEvent?.internal) {
onPanZoom?.(event.sourceEvent as MouseEvent | TouchEvent, transformToViewport(event.transform));
}
};
@@ -170,6 +200,9 @@ export function createPanZoomEndHandler({
onPaneContextMenu,
}: PanZoomEndParams) {
return (event: D3ZoomEvent<HTMLDivElement, any>) => {
if (event.sourceEvent?.internal) {
return;
}
zoomPanValues.isZoomingOrPanning = false;
if (

60
pnpm-lock.yaml generated
View File

@@ -99,8 +99,8 @@ importers:
specifier: ^6.3.0
version: registry.npmjs.org/react-router-dom@6.12.1(react-dom@18.2.0)(react@18.2.0)
zustand:
specifier: ^4.3.1
version: registry.npmjs.org/zustand@4.3.8(react@18.2.0)
specifier: ^4.4.1
version: registry.npmjs.org/zustand@4.4.1(@types/react@18.2.12)(react@18.2.0)
devDependencies:
'@cypress/skip-test':
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)
eslint: registry.npmjs.org/eslint@8.43.0
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:
- supports-color
- typescript
@@ -4140,7 +4140,7 @@ packages:
dependencies:
acorn: registry.npmjs.org/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
registry.npmjs.org/esprima@4.0.1:
@@ -5076,14 +5076,6 @@ packages:
ci-info: registry.npmjs.org/ci-info@3.8.0
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:
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
@@ -6772,7 +6764,7 @@ packages:
version: 1.22.2
hasBin: true
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
supports-preserve-symlinks-flag: registry.npmjs.org/supports-preserve-symlinks-flag@1.0.0
dev: true
@@ -8338,25 +8330,6 @@ packages:
engines: {node: '>=10'}
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):
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
@@ -8379,3 +8352,26 @@ packages:
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.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