From 552392deeb9383999718c0609b868bdf8c32a146 Mon Sep 17 00:00:00 2001 From: Juan Lopez Date: Fri, 31 May 2024 20:16:36 -0700 Subject: [PATCH] Allow custom gesture handlers in Zoom Introduce 'createGestureHandlers' function prop in the Zoom component to enable developers to define custom gestures. This allows for flexibility in disabling wheel events or overriding default pinch gestures, improving customization options --- packages/visx-zoom/src/Zoom.tsx | 75 ++++++++++++++++----------- packages/visx-zoom/src/types.ts | 12 ++++- packages/visx-zoom/test/Zoom.test.tsx | 31 +++++++++-- 3 files changed, 82 insertions(+), 36 deletions(-) diff --git a/packages/visx-zoom/src/Zoom.tsx b/packages/visx-zoom/src/Zoom.tsx index cd7c51070..844436dfe 100644 --- a/packages/visx-zoom/src/Zoom.tsx +++ b/packages/visx-zoom/src/Zoom.tsx @@ -18,6 +18,7 @@ import { ScaleSignature, ProvidedZoom, PinchDelta, + CreateGestureHandlers, } from './types'; // default prop values @@ -38,6 +39,40 @@ const defaultPinchDelta: PinchDelta = ({ offset: [s], lastOffset: [lastS] }) => scaleY: s - lastS < 0 ? 0.9 : 1.1, }); +const defaultCreateGestureHandlers: CreateGestureHandlers = ({ + dragStart, + dragEnd, + dragMove, + handlePinch, + handleWheel, +}: ProvidedZoom) => ({ + onDragStart: ({ event }) => { + if (!(event instanceof KeyboardEvent)) dragStart(event); + }, + onDrag: ({ event, pinching, cancel }) => { + if (pinching) { + cancel(); + dragEnd(); + } else if (!(event instanceof KeyboardEvent)) { + dragMove(event); + } + }, + onDragEnd: dragEnd, + onPinch: handlePinch, + onWheel: ({ event, active, pinching }) => { + if ( + // Outside of Safari, the wheel event is fired together with the pinch event + pinching || + // currently onWheelEnd emits one final wheel event which causes 2x scale + // updates for the last tick. ensuring that the gesture is active avoids this + !active + ) { + return; + } + handleWheel(event); + }, +}); + export type ZoomProps = { /** Width of the zoom container. */ width: number; @@ -94,6 +129,8 @@ export type ZoomProps = { /** Initial transform matrix to apply. */ initialTransformMatrix?: TransformMatrix; children: (zoom: ProvidedZoom & ZoomState) => React.ReactElement; + /** A function that returns gesture handlers for managing UI interactions */ + createGestureHandlers?: CreateGestureHandlers; }; type ZoomState = { @@ -114,6 +151,7 @@ function Zoom({ height, constrain, children, + createGestureHandlers = defaultCreateGestureHandlers, }: ZoomProps): React.ReactElement { const containerRef = useRef(null); const matrixStateRef = useRef(initialTransformMatrix); @@ -312,37 +350,6 @@ function Zoom({ setTransformMatrix(identityMatrix()); }, [setTransformMatrix]); - useGesture( - { - onDragStart: ({ event }) => { - if (!(event instanceof KeyboardEvent)) dragStart(event); - }, - onDrag: ({ event, pinching, cancel }) => { - if (pinching) { - cancel(); - dragEnd(); - } else if (!(event instanceof KeyboardEvent)) { - dragMove(event); - } - }, - onDragEnd: dragEnd, - onPinch: handlePinch, - onWheel: ({ event, active, pinching }) => { - if ( - // Outside of Safari, the wheel event is fired together with the pinch event - pinching || - // currently onWheelEnd emits one final wheel event which causes 2x scale - // updates for the last tick. ensuring that the gesture is active avoids this - !active - ) { - return; - } - handleWheel(event); - }, - }, - { target: containerRef, eventOptions: { passive: false }, drag: { filterTaps: true } }, - ); - const zoom: ProvidedZoom & ZoomState = { initialTransformMatrix, transformMatrix, @@ -368,6 +375,12 @@ function Zoom({ containerRef, }; + useGesture(createGestureHandlers(zoom), { + target: containerRef, + eventOptions: { passive: false }, + drag: { filterTaps: true }, + }); + return <>{children(zoom)}; } diff --git a/packages/visx-zoom/src/types.ts b/packages/visx-zoom/src/types.ts index efc39574e..ce8e25d43 100644 --- a/packages/visx-zoom/src/types.ts +++ b/packages/visx-zoom/src/types.ts @@ -1,4 +1,10 @@ -import { UserHandlers, WebKitGestureEvent, Handler } from '@use-gesture/react'; +import { + UserHandlers, + WebKitGestureEvent, + Handler, + GestureHandlers, + EventTypes, +} from '@use-gesture/react'; import { RefObject, MouseEvent as ReactMouseEvent, @@ -46,6 +52,10 @@ export interface ScaleSignature { point?: Point; } +export type CreateGestureHandlers = ( + zoom: ProvidedZoom, +) => GestureHandlers; + export interface ProvidedZoom { /** Sets translateX/Y to the center defined by width and height. */ center: () => void; diff --git a/packages/visx-zoom/test/Zoom.test.tsx b/packages/visx-zoom/test/Zoom.test.tsx index b63c7e007..10a2a9e65 100644 --- a/packages/visx-zoom/test/Zoom.test.tsx +++ b/packages/visx-zoom/test/Zoom.test.tsx @@ -1,11 +1,14 @@ import React from 'react'; -import { render } from 'enzyme'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Zoom, inverseMatrix } from '../src'; +import { CreateGestureHandlers } from '../lib/types'; describe('', () => { it('should be defined', () => { expect(Zoom).toBeDefined(); }); + it('should render the children and pass zoom params', () => { const initialTransform = { scaleX: 1.27, @@ -16,7 +19,7 @@ describe('', () => { skewY: 0, }; - const wrapper = render( + render( ', () => { > {({ transformMatrix }) => { const { scaleX, scaleY, translateX, translateY } = transformMatrix; - return
{[scaleX, scaleY, translateX, translateY].join(',')}
; + return ( +
{[scaleX, scaleY, translateX, translateY].join(',')}
+ ); }}
, ); + expect(screen.getByTestId('zoom-child').innerHTML).toBe('1.27,1.27,-211.62,162.59'); + }); + it('should accept custom gesture handlers', () => { + const onClick = jest.fn(); + + const createGestureHandlers: CreateGestureHandlers = () => ({ onClick }); + + render( + + width={400} + height={400} + createGestureHandlers={createGestureHandlers} + > + {({ containerRef }) =>