fix(move-events): fire for panOnScroll
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
Generated
+28
-32
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user