diff --git a/docs/api-reference/core/controller.md b/docs/api-reference/core/controller.md index e7ea89ca35b..ab2453000a5 100644 --- a/docs/api-reference/core/controller.md +++ b/docs/api-reference/core/controller.md @@ -53,22 +53,9 @@ The constructor takes one argument: #### `handleEvent(event)` {#handleevent} -Called by the event manager to handle pointer events. This method delegate to the following methods to handle the default events: +Called by the event manager to handle pointer events. -* `_onPanStart(event)` -* `_onPan(event)` -* `_onPanEnd(event)` -* `_onPinchStart(event)` -* `_onPinch(event)` -* `_onPinchEnd(event)` -* `_onTriplePanStart(event)` -* `_onTriplePan(event)` -* `_onTriplePanEnd(event)` -* `_onDoubleTap(event)` -* `_onWheel(event)` -* `_onKeyDown(event)` - -See [Event object documentation](https://uber-web.github.io/mjolnir.js/docs/api-reference/event). +See [Event object documentation](https://visgl.github.io/mjolnir.js/docs/api-reference/event). #### `setProps(props)` {#setprops} @@ -98,6 +85,39 @@ If `event` is provided, returns `false` if the event is already handled, and mar Returns `true` if the user is dragging the view. +## Members + +#### `events` (string[]) + +In its constructor, a controller class can optionally specify a list of event names that it subscribes to with the `events` field. +Supported events are: + +* `click` +* `dblclick` +* `pan` +* `pinch`: 2-finger free-form manipulation, used for touch zooming and rotation +* `multipan`: 2-finger vertical panning, used for touch pitching in `MapController` +* `keydown` +* `keyup` +* `pointerdown` +* `pointermove` +* `pointerup` +* `pointerover` +* `pointerout` +* `pointerleave` +* `wheel` +* `contextmenu` + +Note that the following events are always toggled on/off by user options: + +* `scrollZoom` - `['wheel']` +* `dragPan` and `dragRotate` - `['pan']` +* `touchZoom` - `['pinch']` +* `touchRotate` - `['pinch', 'multipan']` +* `doubleClickZoom` - `['dblclick']` +* `keyboard` - `['keydown']` + + ## Example: Implementing A Custom Controller ```js @@ -119,18 +139,6 @@ class MyController extends Controller{ } ``` -In its constructor, a controller class can optionally specify a list of event names that it subscribes to with the `events` field. A full list of supported events can be found [here](https://uber-web.github.io/mjolnir.js/docs/api-reference/event-manager#supported-events-and-gestures). - -Note that the following events are always toggled on/off by user options: - -* `scrollZoom` - `['wheel']` -* `dragPan` and `dragRotate` - `['pan']` -* `touchZoom` - `['pinch']` -* `touchRotate` - `['pinch', 'tripan']` -* `doubleClickZoom` - `['doubletap']` -* `keyboard` - `['keydown']` - - ## Source [modules/core/src/controllers/controller.ts](https://github.com/visgl/deck.gl/blob/master/modules/core/src/controllers/controller.ts) diff --git a/docs/api-reference/core/deck.md b/docs/api-reference/core/deck.md index 2c2c94fbbea..6bea8f00e20 100644 --- a/docs/api-reference/core/deck.md +++ b/docs/api-reference/core/deck.md @@ -401,11 +401,11 @@ By default, the deck canvas captures all touch interactions. This prop is useful Set options for gesture recognition. May contain the following fields: -- `pan` - an object that is [Hammer.Pan](http://hammerjs.github.io/recognizer-pan/) options. This gesture is used for drag events. -- `pinch` - an object that is [Hammer.Pinch](http://hammerjs.github.io/recognizer-pinch/) options This gesture is used for two-finger touch events. -- `tripan` - an object that is [Hammer.Pan](http://hammerjs.github.io/recognizer-pan/) options. This gesture is used for three-finger touch events. -- `tap` - an object that is [Hammer.Tap](http://hammerjs.github.io/recognizer-tap/) options. This gesture is used for the `onClick` callback. -- `doubletap` - an object that is [Hammer.Tap](http://hammerjs.github.io/recognizer-tap/) options. This gesture is used for double click events. +- `pan` - an object that is [Pan](https://visgl.github.io/mjolnir.js/docs/api-reference/pan) options. This gesture is used for `onDrag` events, viewport panning (mouse/touch) and rotating (mouse+ctrl). Default `{threshold: 1}`. +- `pinch` - an object that is [Pinch](https://visgl.github.io/mjolnir.js/docs/api-reference/pinch) options This gesture is used for multi-touch zooming/rotating. +- `multipan` - an object that is [Pan](https://visgl.github.io/mjolnir.js/docs/api-reference/pan) options. This gesture is used for multi-touch pitching. Default `{threshold: 10, direction: InputDirection.Vertical, pointers: 2}`. +- `click` - an object that is [Tap](https://visgl.github.io/mjolnir.js/docs/api-reference/tap) options. This gesture is used for the `onClick` event. +- `dblclick` - an object that is [Tap](https://visgl.github.io/mjolnir.js/docs/api-reference/tap) options. This gesture is used for double-click zooming. For example, the following setting makes panning less sensitive and clicking easier on mobile: diff --git a/docs/upgrade-guide.md b/docs/upgrade-guide.md index 634b33e7a17..d3387602c3d 100644 --- a/docs/upgrade-guide.md +++ b/docs/upgrade-guide.md @@ -2,6 +2,22 @@ ## Upgrading to v9.1 +### User input handling + +- The main gesture recognition library, [mjolnir.js](https://visgl.github.io/mjolnir.js), is upgraded to v3.0. Hammer.js is no longer an (indirect) dependency, due to its lack of maintenance and various issues with SSR and test environments. +- The default gesture to manipulate viewport `pitch` by touch has been changed from three-finger to two-finger dragging vertically. You can revert this behavior by setting the [eventRecognizerOptions](./api-reference/core/deck.md#eventrecognizeroptions) prop of `Deck`: + + ```ts + eventRecognizerOptions: { + multipan: {pointers: 3} + } + ``` + +- If your app already uses Deck's `eventRecognizerOptions` prop or implement a custom [Controller](./api-reference/core/controller.md), some events have been renamed: + + `tripan` -> `multipan` + + `tap` -> `click` + + `doubletap` -> `dblclick` + ### Aggregation layers Breaking changes: diff --git a/examples/vite.config.local.mjs b/examples/vite.config.local.mjs index 14b3572f529..711fcd0fac8 100644 --- a/examples/vite.config.local.mjs +++ b/examples/vite.config.local.mjs @@ -13,6 +13,7 @@ export default defineConfig(async () => { alias: { ...aliases, // Use root dependencies + 'mjolnir.js': join(rootDir, './node_modules/mjolnir.js'), '@luma.gl': join(rootDir, './node_modules/@luma.gl'), '@math.gl': join(rootDir, './node_modules/@math.gl'), '@arcgis/core': join(rootDir, './node_modules/@arcgis/core'), diff --git a/modules/core/package.json b/modules/core/package.json index 5270c4382cd..927e66ee657 100644 --- a/modules/core/package.json +++ b/modules/core/package.json @@ -56,7 +56,7 @@ "@probe.gl/stats": "^4.0.9", "@types/offscreencanvas": "^2019.6.4", "gl-matrix": "^3.0.0", - "mjolnir.js": "^2.7.0" + "mjolnir.js": "^3.0.0-alpha.3" }, "gitHead": "13ace64fc2cee08c133afc882fc307253489a4e4" } diff --git a/modules/core/src/controllers/controller.ts b/modules/core/src/controllers/controller.ts index 3ff4c79a88c..02ffa4f7e6e 100644 --- a/modules/core/src/controllers/controller.ts +++ b/modules/core/src/controllers/controller.ts @@ -40,8 +40,8 @@ const EVENT_TYPES = { WHEEL: ['wheel'], PAN: ['panstart', 'panmove', 'panend'], PINCH: ['pinchstart', 'pinchmove', 'pinchend'], - TRIPLE_PAN: ['tripanstart', 'tripanmove', 'tripanend'], - DOUBLE_TAP: ['doubletap'], + MULTI_PAN: ['multipanstart', 'multipanmove', 'multipanend'], + DOUBLE_CLICK: ['dblclick'], KEYBOARD: ['keydown'] } as const; @@ -228,14 +228,14 @@ export default abstract class Controller; + multipan?: Omit; + pan?: Omit; + dblclick?: Omit; + click?: Omit; +}; + /** * @deprecated Use string constants directly */ diff --git a/modules/core/src/lib/deck.ts b/modules/core/src/lib/deck.ts index 74d29bee617..7ed1bf381e3 100644 --- a/modules/core/src/lib/deck.ts +++ b/modules/core/src/lib/deck.ts @@ -43,14 +43,14 @@ import {Stats} from '@probe.gl/stats'; import {EventManager} from 'mjolnir.js'; import assert from '../utils/assert'; -import {EVENTS} from './constants'; +import {EVENT_HANDLERS, RECOGNIZERS, RecognizerOptions} from './constants'; import type {Effect} from './effect'; import type {FilterContext} from '../passes/layers-pass'; import type Layer from './layer'; import type View from '../views/view'; import type Viewport from '../viewports/viewport'; -import type {RecognizerOptions, MjolnirGestureEvent, MjolnirPointerEvent} from 'mjolnir.js'; +import type {EventManagerOptions, MjolnirGestureEvent, MjolnirPointerEvent} from 'mjolnir.js'; import type {TypedArrayManagerOptions} from '../utils/typed-array-manager'; import type {ViewStateChangeParameters, InteractionState} from '../controllers/controller'; import type {PickingInfo} from './picking/pick-info'; @@ -167,11 +167,11 @@ export type DeckProps = { /** Allow browser default touch actions. * @default `'none'` */ - touchAction?: string; - /** Set Hammer.js recognizer options for gesture recognition. See documentation for details. */ - eventRecognizerOptions?: { - [type: string]: RecognizerOptions; - }; + touchAction?: EventManagerOptions['touchAction']; + /** + * Optional mjolnir.js recognizer options + */ + eventRecognizerOptions?: RecognizerOptions; /** (Experimental) Render to a custom frame buffer other than to screen. */ _framebuffer?: Framebuffer | null; @@ -234,7 +234,7 @@ export type DeckProps = { drawPickingColors?: boolean; }; -const defaultProps = { +const defaultProps: DeckProps = { id: '', width: '100%', height: '100%', @@ -971,15 +971,26 @@ export default class Deck { this.eventManager = new EventManager(this.props.parent || this.canvas, { touchAction: this.props.touchAction, - recognizerOptions: this.props.eventRecognizerOptions, + recognizers: Object.keys(RECOGNIZERS).map((eventName: string) => { + // Resolve recognizer settings + const [RecognizerConstructor, defaultOptions, recognizeWith, requestFailure] = + RECOGNIZERS[eventName]; + const optionsOverride = this.props.eventRecognizerOptions?.[eventName]; + const options = {...defaultOptions, ...optionsOverride, event: eventName}; + return { + recognizer: new RecognizerConstructor(options), + recognizeWith, + requestFailure + }; + }), events: { pointerdown: this._onPointerDown, pointermove: this._onPointerMove, pointerleave: this._onPointerMove } }); - for (const eventType in EVENTS) { - this.eventManager.on(eventType as keyof typeof EVENTS, this._onEvent); + for (const eventType in EVENT_HANDLERS) { + this.eventManager.on(eventType, this._onEvent); } this.viewManager = new ViewManager({ @@ -1133,10 +1144,10 @@ export default class Deck { /** Internal use only: event handler for click & drag */ _onEvent = (event: MjolnirGestureEvent) => { - const eventOptions = EVENTS[event.type]; + const eventHandlerProp = EVENT_HANDLERS[event.type]; const pos = event.offsetCenter; - if (!eventOptions || !pos || !this.layerManager) { + if (!eventHandlerProp || !pos || !this.layerManager) { return; } @@ -1153,9 +1164,8 @@ export default class Deck { ) as PickingInfo; const {layer} = info; - const layerHandler = - layer && (layer[eventOptions.handler] || layer.props[eventOptions.handler]); - const rootHandler = this.props[eventOptions.handler]; + const layerHandler = layer && (layer[eventHandlerProp] || layer.props[eventHandlerProp]); + const rootHandler = this.props[eventHandlerProp]; let handled = false; if (layerHandler) { diff --git a/modules/core/src/lib/widget-manager.ts b/modules/core/src/lib/widget-manager.ts index 2ae837ee21f..c9baaf84e28 100644 --- a/modules/core/src/lib/widget-manager.ts +++ b/modules/core/src/lib/widget-manager.ts @@ -4,7 +4,7 @@ import type {PickingInfo} from './picking/pick-info'; import type {MjolnirPointerEvent, MjolnirGestureEvent} from 'mjolnir.js'; import type Layer from './layer'; -import {EVENTS} from './constants'; +import {EVENT_HANDLERS} from './constants'; import {deepEqual} from '../utils/deep-equal'; export interface Widget { @@ -278,14 +278,14 @@ export class WidgetManager { } onEvent(info: PickingInfo, event: MjolnirGestureEvent) { - const eventOptions = EVENTS[event.type]; - if (!eventOptions) { + const eventHandlerProp = EVENT_HANDLERS[event.type]; + if (!eventHandlerProp) { return; } for (const widget of this.getWidgets()) { const {viewId} = widget; if (!viewId || viewId === info.viewport?.id) { - widget[eventOptions.handler]?.(info, event); + widget[eventHandlerProp]?.(info, event); } } } diff --git a/package.json b/package.json index 85997567059..f3bed089cbf 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ ], "scripts": { "bootstrap": "yarn && ocular-bootstrap", - "postinstall": "./scripts/postinstall.sh", "clean": "ocular-clean", "build": "npm run clean && ocular-build && lerna run build", "version": "node scripts/verify-changelog.js && git add CHANGELOG.md", diff --git a/scripts/postinstall.sh b/scripts/postinstall.sh deleted file mode 100755 index 95967f928cf..00000000000 --- a/scripts/postinstall.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -# hammerjs has a baked-in AMD template that breaks esbuild's module resolution. Disable AMD -sed -i.bak 's/typeof define/undefined/' node_modules/hammerjs/hammer.js diff --git a/test/modules/core/controllers/controllers.spec.ts b/test/modules/core/controllers/controllers.spec.ts index f76717702ee..64b9196339f 100644 --- a/test/modules/core/controllers/controllers.spec.ts +++ b/test/modules/core/controllers/controllers.spec.ts @@ -44,7 +44,7 @@ test('GlobeController', async t => { zoom: 0 }, // GlobeView cannot be rotated - ['pan#function key', 'pinch', 'tripan'] + ['pan#function key', 'pinch', 'multipan'] ); t.end(); @@ -71,7 +71,7 @@ test('OrthographicController', async t => { zoom: 1 }, // OrthographicView cannot be rotated - ['pan#function key', 'tripan'] + ['pan#function key', 'multipan'] ); t.end(); @@ -86,7 +86,7 @@ test('OrthographicController#2d zoom', async t => { zoom: [1, 2] }, // OrthographicView cannot be rotated - ['pan#function key', 'tripan'] + ['pan#function key', 'multipan'] ); t.end(); diff --git a/test/modules/core/controllers/test-controller.ts b/test/modules/core/controllers/test-controller.ts index 59278c7bab0..224b7b0b917 100644 --- a/test/modules/core/controllers/test-controller.ts +++ b/test/modules/core/controllers/test-controller.ts @@ -110,23 +110,23 @@ const TEST_CASES = [ }, { - title: 'tripan', + title: 'multipan', props: {}, - events: () => makeEvents(['tripanstart', 'tripanmove', 'tripanend']), + events: () => makeEvents(['multipanstart', 'multipanmove', 'multipanend']), viewStateChanges: 3, interactionStates: 2 // isDragging, isRotating }, { - title: 'tripan#out of bounds', + title: 'multipan#out of bounds', props: {x: 200}, - events: () => makeEvents(['tripanstart', 'tripanmove', 'tripanend']), + events: () => makeEvents(['multipanstart', 'multipanmove', 'multipanend']), viewStateChanges: 0, interactionStates: 0 }, { - title: 'tripan#disabled', + title: 'multipan#disabled', props: {touchRotate: false}, - events: () => makeEvents(['tripanstart', 'tripanmove', 'tripanend']), + events: () => makeEvents(['multipanstart', 'multipanmove', 'multipanend']), viewStateChanges: 2, interactionStates: 1 // isDragging }, @@ -154,23 +154,23 @@ const TEST_CASES = [ }, { - title: 'doubletap', + title: 'dblclick', props: {}, - events: () => makeEvents(['doubletap']), + events: () => makeEvents(['dblclick']), viewStateChanges: 1, interactionStates: 2 }, { - title: 'doubletap#out of bounds', + title: 'dblclick#out of bounds', props: {x: 200}, - events: () => makeEvents(['doubletap']), + events: () => makeEvents(['dblclick']), viewStateChanges: 0, interactionStates: 0 }, { - title: 'doubletap#disabled', + title: 'dblclick#disabled', props: {doubleClickZoom: false}, - events: () => makeEvents(['doubletap']), + events: () => makeEvents(['dblclick']), viewStateChanges: 0, interactionStates: 0 }, diff --git a/yarn.lock b/yarn.lock index c9a5aa89ca3..32a96a47402 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2961,11 +2961,6 @@ resolved "https://registry.yarnpkg.com/@types/google.maps/-/google.maps-3.55.12.tgz#66b50be48533d116dddb3d705d277d06457735b0" integrity sha512-Q8MsLE+YYIrE1H8wdN69YHHAF8h7ApvF5MiMXh/zeCpP9Ut745mV9M0F4X4eobZ2WJe9k8tW2ryYjLa87IO2Sg== -"@types/hammerjs@^2.0.41": - version "2.0.45" - resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.45.tgz#ffa764bb68a66c08db6efb9c816eb7be850577b1" - integrity sha512-qkcUlZmX6c4J8q45taBKTL3p+LbITgyx7qhlPYOdOHZB7B31K0mXbP5YA7i7SgDeEGuI9MnumiKPEMrxg8j3KQ== - "@types/istanbul-lib-coverage@^2.0.1": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" @@ -6067,11 +6062,6 @@ h3-js@^4.1.0: resolved "https://registry.yarnpkg.com/h3-js/-/h3-js-4.1.0.tgz#f8c4a8ad36612489a954f1a0bb3f4b7657d364e5" integrity sha512-LQhmMl1dRQQjMXPzJc7MpZ/CqPOWWuAvVEoVJM9n/s7vHypj+c3Pd5rLQCkAsOgAoAYKbNCsYFE++LF7MvSfCQ== -hammerjs@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1" - integrity sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ== - handlebars@^4.7.7: version "4.7.8" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" @@ -7709,13 +7699,10 @@ mitt@3.0.1: resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1" integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== -mjolnir.js@^2.7.0: - version "2.7.3" - resolved "https://registry.yarnpkg.com/mjolnir.js/-/mjolnir.js-2.7.3.tgz#b71902edaa387f14c7fe6e9b1f611c0ce814240a" - integrity sha512-Z5z/+FzZqOSO3juSVKV3zcm4R2eAlWwlKMcqHmyFEJAaLILNcDKnIbnb4/kbcGyIuhtdWrzu8WOIR7uM6I34aw== - dependencies: - "@types/hammerjs" "^2.0.41" - hammerjs "^2.0.8" +mjolnir.js@^3.0.0-alpha.3: + version "3.0.0-alpha.4" + resolved "https://registry.yarnpkg.com/mjolnir.js/-/mjolnir.js-3.0.0-alpha.4.tgz#c37d3c2b20a6eeff709509ecc282263359839642" + integrity sha512-FVfy5kc3umt8mvwTtJqnuOUSLF1rMgqO/tXva4V0XPfSTYmfbVxP8CjIBE1H0I1dAPTktaUriwBf2YRnfSr55Q== mkdirp@^1.0.3: version "1.0.4"