From 46490e4117d24d2eccf339dc1749022bf871bfc3 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 12 May 2020 16:37:43 -0400 Subject: [PATCH 1/2] fix multi-finger gestures touching markers Previously we were using e.targetTouches as the list of touches relevant to the map. It contains only the touches that first touched the same target. Touches that hit a marker have a different target. This fixes uses e.touches and filters out touches who's target is not a child of the map. fix #9675 --- src/ui/handler/handler_util.js | 2 +- src/ui/handler/tap_drag_zoom.js | 20 ++++++++++---------- src/ui/handler/tap_recognizer.js | 28 ++++++++++++++-------------- src/ui/handler/tap_zoom.js | 18 +++++++++--------- src/ui/handler/touch_pan.js | 20 ++++++++++---------- src/ui/handler/touch_zoom_rotate.js | 28 +++++++++++++++------------- src/ui/handler_manager.js | 26 ++++++++++++++++++-------- 7 files changed, 77 insertions(+), 65 deletions(-) diff --git a/src/ui/handler/handler_util.js b/src/ui/handler/handler_util.js index e26f8a80d5b..493f93d6264 100644 --- a/src/ui/handler/handler_util.js +++ b/src/ui/handler/handler_util.js @@ -2,7 +2,7 @@ import assert from 'assert'; -export function indexTouches(touches: TouchList, points: Array) { +export function indexTouches(touches: Array, points: Array) { assert(touches.length === points.length); const obj = {}; for (let i = 0; i < touches.length; i++) { diff --git a/src/ui/handler/tap_drag_zoom.js b/src/ui/handler/tap_drag_zoom.js index c1e57bf931f..d8bf435a2c5 100644 --- a/src/ui/handler/tap_drag_zoom.js +++ b/src/ui/handler/tap_drag_zoom.js @@ -30,7 +30,7 @@ export default class TapDragZoomHandler { this._tap.reset(); } - touchstart(e: TouchEvent, points: Array) { + touchstart(e: TouchEvent, points: Array, mapTouches: Array) { if (this._swipePoint) return; if (this._tapTime && e.timeStamp - this._tapTime > MAX_TAP_INTERVAL) { @@ -38,19 +38,19 @@ export default class TapDragZoomHandler { } if (!this._tapTime) { - this._tap.touchstart(e, points); - } else if (e.targetTouches.length > 0) { + this._tap.touchstart(e, points, mapTouches); + } else if (mapTouches.length > 0) { this._swipePoint = points[0]; - this._swipeTouch = e.targetTouches[0].identifier; + this._swipeTouch = mapTouches[0].identifier; } } - touchmove(e: TouchEvent, points: Array) { + touchmove(e: TouchEvent, points: Array, mapTouches: Array) { if (!this._tapTime) { - this._tap.touchmove(e, points); + this._tap.touchmove(e, points, mapTouches); } else if (this._swipePoint) { - if (e.targetTouches[0].identifier !== this._swipeTouch) { + if (mapTouches[0].identifier !== this._swipeTouch) { return; } @@ -67,14 +67,14 @@ export default class TapDragZoomHandler { } } - touchend(e: TouchEvent) { + touchend(e: TouchEvent, points: Array, mapTouches: Array) { if (!this._tapTime) { - const point = this._tap.touchend(e); + const point = this._tap.touchend(e, points, mapTouches); if (point) { this._tapTime = e.timeStamp; } } else if (this._swipePoint) { - if (e.targetTouches.length === 0) { + if (mapTouches.length === 0) { this.reset(); } } diff --git a/src/ui/handler/tap_recognizer.js b/src/ui/handler/tap_recognizer.js index 40328e19764..01e9dee629e 100644 --- a/src/ui/handler/tap_recognizer.js +++ b/src/ui/handler/tap_recognizer.js @@ -35,9 +35,9 @@ export class SingleTapRecognizer { this.aborted = false; } - touchstart(e: TouchEvent, points: Array) { + touchstart(e: TouchEvent, points: Array, mapTouches: Array) { - if (this.centroid || e.targetTouches.length > this.numTouches) { + if (this.centroid || mapTouches.length > this.numTouches) { this.aborted = true; } if (this.aborted) { @@ -48,16 +48,16 @@ export class SingleTapRecognizer { this.startTime = e.timeStamp; } - if (e.targetTouches.length === this.numTouches) { + if (mapTouches.length === this.numTouches) { this.centroid = getCentroid(points); - this.touches = indexTouches(e.targetTouches, points); + this.touches = indexTouches(mapTouches, points); } } - touchmove(e: TouchEvent, points: Array) { + touchmove(e: TouchEvent, points: Array, mapTouches: Array) { if (this.aborted || !this.centroid) return; - const newTouches = indexTouches(e.targetTouches, points); + const newTouches = indexTouches(mapTouches, points); for (const id in this.touches) { const prevPos = this.touches[id]; const pos = newTouches[id]; @@ -67,12 +67,12 @@ export class SingleTapRecognizer { } } - touchend(e: TouchEvent) { + touchend(e: TouchEvent, points: Array, mapTouches: Array) { if (!this.centroid || e.timeStamp - this.startTime > MAX_TOUCH_TIME) { this.aborted = true; } - if (e.targetTouches.length === 0) { + if (mapTouches.length === 0) { const centroid = !this.aborted && this.centroid; this.reset(); if (centroid) return centroid; @@ -102,16 +102,16 @@ export class TapRecognizer { this.singleTap.reset(); } - touchstart(e: TouchEvent, points: Array) { - this.singleTap.touchstart(e, points); + touchstart(e: TouchEvent, points: Array, mapTouches: Array) { + this.singleTap.touchstart(e, points, mapTouches); } - touchmove(e: TouchEvent, points: Array) { - this.singleTap.touchmove(e, points); + touchmove(e: TouchEvent, points: Array, mapTouches: Array) { + this.singleTap.touchmove(e, points, mapTouches); } - touchend(e: TouchEvent) { - const tap = this.singleTap.touchend(e); + touchend(e: TouchEvent, points: Array, mapTouches: Array) { + const tap = this.singleTap.touchend(e, points, mapTouches); if (tap) { const soonEnough = e.timeStamp - this.lastTime < MAX_TAP_INTERVAL; const closeEnough = !this.lastTap || this.lastTap.dist(tap) < MAX_DIST; diff --git a/src/ui/handler/tap_zoom.js b/src/ui/handler/tap_zoom.js index 0249c1069e9..1aee59d1e88 100644 --- a/src/ui/handler/tap_zoom.js +++ b/src/ui/handler/tap_zoom.js @@ -31,19 +31,19 @@ export default class TapZoomHandler { this._zoomOut.reset(); } - touchstart(e: TouchEvent, points: Array) { - this._zoomIn.touchstart(e, points); - this._zoomOut.touchstart(e, points); + touchstart(e: TouchEvent, points: Array, mapTouches: Array) { + this._zoomIn.touchstart(e, points, mapTouches); + this._zoomOut.touchstart(e, points, mapTouches); } - touchmove(e: TouchEvent, points: Array) { - this._zoomIn.touchmove(e, points); - this._zoomOut.touchmove(e, points); + touchmove(e: TouchEvent, points: Array, mapTouches: Array) { + this._zoomIn.touchmove(e, points, mapTouches); + this._zoomOut.touchmove(e, points, mapTouches); } - touchend(e: TouchEvent) { - const zoomInPoint = this._zoomIn.touchend(e); - const zoomOutPoint = this._zoomOut.touchend(e); + touchend(e: TouchEvent, points: Array, mapTouches: Array) { + const zoomInPoint = this._zoomIn.touchend(e, points, mapTouches); + const zoomOutPoint = this._zoomOut.touchend(e, points, mapTouches); if (zoomInPoint) { this._active = true; diff --git a/src/ui/handler/touch_pan.js b/src/ui/handler/touch_pan.js index a04a7f7f1d6..be63664b6bd 100644 --- a/src/ui/handler/touch_pan.js +++ b/src/ui/handler/touch_pan.js @@ -24,20 +24,20 @@ export default class TouchPanHandler { this._sum = new Point(0, 0); } - touchstart(e: TouchEvent, points: Array) { - return this._calculateTransform(e, points); + touchstart(e: TouchEvent, points: Array, mapTouches: Array) { + return this._calculateTransform(e, points, mapTouches); } - touchmove(e: TouchEvent, points: Array) { + touchmove(e: TouchEvent, points: Array, mapTouches: Array) { if (!this._active) return; e.preventDefault(); - return this._calculateTransform(e, points); + return this._calculateTransform(e, points, mapTouches); } - touchend(e: TouchEvent, points: Array) { - this._calculateTransform(e, points); + touchend(e: TouchEvent, points: Array, mapTouches: Array) { + this._calculateTransform(e, points, mapTouches); - if (this._active && e.targetTouches.length < this._minTouches) { + if (this._active && mapTouches.length < this._minTouches) { this.reset(); } } @@ -46,10 +46,10 @@ export default class TouchPanHandler { this.reset(); } - _calculateTransform(e: TouchEvent, points: Array) { - if (e.targetTouches.length > 0) this._active = true; + _calculateTransform(e: TouchEvent, points: Array, mapTouches: Array) { + if (mapTouches.length > 0) this._active = true; - const touches = indexTouches(e.targetTouches, points); + const touches = indexTouches(mapTouches, points); const touchPointSum = new Point(0, 0); const touchDeltaSum = new Point(0, 0); diff --git a/src/ui/handler/touch_zoom_rotate.js b/src/ui/handler/touch_zoom_rotate.js index d7b7743b5b9..8e7b910c205 100644 --- a/src/ui/handler/touch_zoom_rotate.js +++ b/src/ui/handler/touch_zoom_rotate.js @@ -24,26 +24,28 @@ class TwoTouchHandler { _start(points: [Point, Point]) {} //eslint-disable-line _move(points: [Point, Point], pinchAround: Point, e: TouchEvent) { return {}; } //eslint-disable-line - touchstart(e: TouchEvent, points: Array) { - if (this._firstTwoTouches || e.targetTouches.length < 2) return; + touchstart(e: TouchEvent, points: Array, mapTouches: Array) { + //console.log(e.target, e.targetTouches.length ? e.targetTouches[0].target : null); + //log('touchstart', points, e.target.innerHTML, e.targetTouches.length ? e.targetTouches[0].target.innerHTML: undefined); + if (this._firstTwoTouches || mapTouches.length < 2) return; this._firstTwoTouches = [ - e.targetTouches[0].identifier, - e.targetTouches[1].identifier + mapTouches[0].identifier, + mapTouches[1].identifier ]; // implemented by child classes this._start([points[0], points[1]]); } - touchmove(e: TouchEvent, points: Array) { + touchmove(e: TouchEvent, points: Array, mapTouches: Array) { if (!this._firstTwoTouches) return; e.preventDefault(); const [idA, idB] = this._firstTwoTouches; - const a = getTouchById(e, points, idA); - const b = getTouchById(e, points, idB); + const a = getTouchById(mapTouches, points, idA); + const b = getTouchById(mapTouches, points, idB); if (!a || !b) return; const pinchAround = this._aroundCenter ? null : a.add(b).div(2); @@ -52,12 +54,12 @@ class TwoTouchHandler { } - touchend(e: TouchEvent, points: Array) { + touchend(e: TouchEvent, points: Array, mapTouches: Array) { if (!this._firstTwoTouches) return; const [idA, idB] = this._firstTwoTouches; - const a = getTouchById(e, points, idA); - const b = getTouchById(e, points, idB); + const a = getTouchById(mapTouches, points, idA); + const b = getTouchById(mapTouches, points, idB); if (a && b) return; if (this._active) DOM.suppressClick(); @@ -88,9 +90,9 @@ class TwoTouchHandler { } } -function getTouchById(e: TouchEvent, points: Array, identifier: number) { - for (let i = 0; i < e.targetTouches.length; i++) { - if (e.targetTouches[i].identifier === identifier) return points[i]; +function getTouchById(mapTouches: Array, points: Array, identifier: number) { + for (let i = 0; i < mapTouches.length; i++) { + if (mapTouches[i].identifier === identifier) return points[i]; } } diff --git a/src/ui/handler_manager.js b/src/ui/handler_manager.js index 1589e1d0f59..42325902491 100644 --- a/src/ui/handler_manager.js +++ b/src/ui/handler_manager.js @@ -49,10 +49,10 @@ export interface Handler { // Handlers can optionally implement these methods. // They are called with dom events whenever those dom evens are received. - +touchstart?: (e: TouchEvent, points: Array) => HandlerResult | void; - +touchmove?: (e: TouchEvent, points: Array) => HandlerResult | void; - +touchend?: (e: TouchEvent, points: Array) => HandlerResult | void; - +touchcancel?: (e: TouchEvent, points: Array) => HandlerResult | void; + +touchstart?: (e: TouchEvent, points: Array, mapTouches: Array) => HandlerResult | void; + +touchmove?: (e: TouchEvent, points: Array, mapTouches: Array) => HandlerResult | void; + +touchend?: (e: TouchEvent, points: Array, mapTouches: Array) => HandlerResult | void; + +touchcancel?: (e: TouchEvent, points: Array, mapTouches: Array) => HandlerResult | void; +mousedown?: (e: MouseEvent, point: Point) => HandlerResult | void; +mousemove?: (e: MouseEvent, point: Point) => HandlerResult | void; +mouseup?: (e: MouseEvent, point: Point) => HandlerResult | void; @@ -273,6 +273,17 @@ class HandlerManager { this.handleEvent(e, `${e.type}Window`); } + _getMapTouches(touches: TouchList) { + const mapTouches = []; + for (const t of touches) { + const target = ((t.target: any): Node); + if (this._el.contains(target)) { + mapTouches.push(t); + } + } + return ((mapTouches: any): TouchList); + } + handleEvent(e: InputEvent | RenderFrameEvent, eventName?: string) { if (e.type === 'blur') { @@ -294,9 +305,8 @@ class HandlerManager { const eventsInProgress = {}; const activeHandlers = {}; - const points = e ? (e.targetTouches ? - DOM.touchPos(this._el, ((e: any): TouchEvent).targetTouches) : - DOM.mousePos(this._el, ((e: any): MouseEvent))) : null; + const mapTouches = e.touches ? this._getMapTouches(((e: any): TouchEvent).touches) : undefined; + const points = mapTouches ? DOM.touchPos(this._el, mapTouches) : DOM.mousePos(this._el, ((e: any): MouseEvent)); for (const {handlerName, handler, allowed} of this._handlers) { if (!handler.isEnabled()) continue; @@ -307,7 +317,7 @@ class HandlerManager { } else { if ((handler: any)[eventName || e.type]) { - data = (handler: any)[eventName || e.type](e, points); + data = (handler: any)[eventName || e.type](e, points, mapTouches); this.mergeHandlerResult(mergedHandlerResult, eventsInProgress, data, handlerName, inputEvent); if (data && data.needsRenderFrame) { this._triggerRenderFrame(); From 265ea8b83bb348f82b7279dc0b5f08f4393e3eea Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 13 May 2020 12:05:15 -0400 Subject: [PATCH 2/2] update tests and add regression test --- test/unit/ui/handler/dblclick_zoom.test.js | 6 +- test/unit/ui/handler/drag_pan.test.js | 20 ++- test/unit/ui/handler/map_event.test.js | 7 +- .../unit/ui/handler/touch_zoom_rotate.test.js | 145 ++++++++++++++++-- test/unit/ui/map.test.js | 2 +- 5 files changed, 150 insertions(+), 30 deletions(-) diff --git a/test/unit/ui/handler/dblclick_zoom.test.js b/test/unit/ui/handler/dblclick_zoom.test.js index a3adb4a63f4..737c7b86cf6 100644 --- a/test/unit/ui/handler/dblclick_zoom.test.js +++ b/test/unit/ui/handler/dblclick_zoom.test.js @@ -12,10 +12,10 @@ function createMap(t) { function simulateDoubleTap(map, delay = 100) { const canvas = map.getCanvas(); return new Promise(resolve => { - simulate.touchstart(canvas, {targetTouches: [{clientX: 0, clientY: 0}]}); + simulate.touchstart(canvas, {touches: [{target: canvas, clientX: 0, clientY: 0}]}); simulate.touchend(canvas); setTimeout(() => { - simulate.touchstart(canvas, {targetTouches: [{clientX: 0, clientY: 0}]}); + simulate.touchstart(canvas, {touches: [{target: canvas, clientX: 0, clientY: 0}]}); simulate.touchend(canvas); map._renderTaskQueue.run(); resolve(); @@ -122,7 +122,7 @@ test('DoubleClickZoomHandler zooms on the second touchend event of a double tap' map.on('zoomstart', zoom); const canvas = map.getCanvas(); - const touchOptions = {targetTouches: [{clientX: 0.5, clientY: 0.5}]}; + const touchOptions = {touches: [{target: canvas, clientX: 0.5, clientY: 0.5}]}; simulate.touchstart(canvas, touchOptions); simulate.touchend(canvas); diff --git a/test/unit/ui/handler/drag_pan.test.js b/test/unit/ui/handler/drag_pan.test.js index 6f364ae3e8e..785efaf488e 100644 --- a/test/unit/ui/handler/drag_pan.test.js +++ b/test/unit/ui/handler/drag_pan.test.js @@ -84,6 +84,7 @@ test('DragPanHandler captures mousemove events during a mouse-triggered drag (re test('DragPanHandler fires dragstart, drag, and dragend events at appropriate times in response to a touch-triggered drag', (t) => { const map = createMap(t); + const target = map.getCanvas(); const dragstart = t.spy(); const drag = t.spy(); @@ -93,13 +94,13 @@ test('DragPanHandler fires dragstart, drag, and dragend events at appropriate ti map.on('drag', drag); map.on('dragend', dragend); - simulate.touchstart(map.getCanvas(), {targetTouches: [{clientX: 0, clientY: 0}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target, clientX: 0, clientY: 0}]}); map._renderTaskQueue.run(); t.equal(dragstart.callCount, 0); t.equal(drag.callCount, 0); t.equal(dragend.callCount, 0); - simulate.touchmove(map.getCanvas(), {targetTouches: [{clientX: 10, clientY: 10}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, clientX: 10, clientY: 10}]}); map._renderTaskQueue.run(); t.equal(dragstart.callCount, 1); t.equal(drag.callCount, 1); @@ -157,14 +158,15 @@ test('DragPanHandler ends a mouse-triggered drag if the window blurs', (t) => { test('DragPanHandler ends a touch-triggered drag if the window blurs', (t) => { const map = createMap(t); + const target = map.getCanvas(); const dragend = t.spy(); map.on('dragend', dragend); - simulate.touchstart(map.getCanvas(), {targetTouches: [{clientX: 0, clientY: 0}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target, clientX: 0, clientY: 0}]}); map._renderTaskQueue.run(); - simulate.touchmove(map.getCanvas(), {targetTouches: [{clientX: 10, clientY: 10}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, clientX: 10, clientY: 10}]}); map._renderTaskQueue.run(); simulate.blur(window); @@ -428,6 +430,7 @@ test('DragPanHandler does not begin a drag if preventDefault is called on the mo test('DragPanHandler does not begin a drag if preventDefault is called on the touchstart event', (t) => { const map = createMap(t); + const target = map.getCanvas(); map.on('touchstart', e => e.preventDefault()); @@ -439,10 +442,10 @@ test('DragPanHandler does not begin a drag if preventDefault is called on the to map.on('drag', drag); map.on('dragend', dragend); - simulate.touchstart(map.getCanvas(), {targetTouches: [{clientX: 0, clientY: 0}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target, clientX: 0, clientY: 0}]}); map._renderTaskQueue.run(); - simulate.touchmove(map.getCanvas(), {targetTouches: [{clientX: 10, clientY: 10}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, clientX: 10, clientY: 10}]}); map._renderTaskQueue.run(); simulate.touchend(map.getCanvas()); @@ -458,6 +461,7 @@ test('DragPanHandler does not begin a drag if preventDefault is called on the to test('DragPanHandler does not begin a drag if preventDefault is called on the touchstart event (delegated)', (t) => { const map = createMap(t); + const target = map.getCanvas(); t.stub(map, 'getLayer') .callsFake(() => true); @@ -476,10 +480,10 @@ test('DragPanHandler does not begin a drag if preventDefault is called on the to map.on('drag', drag); map.on('dragend', dragend); - simulate.touchstart(map.getCanvas(), {targetTouches: [{clientX: 0, clientY: 0}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target, clientX: 0, clientY: 0}]}); map._renderTaskQueue.run(); - simulate.touchmove(map.getCanvas(), {targetTouches: [{clientX: 10, clientY: 10}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, clientX: 10, clientY: 10}]}); map._renderTaskQueue.run(); simulate.touchend(map.getCanvas()); diff --git a/test/unit/ui/handler/map_event.test.js b/test/unit/ui/handler/map_event.test.js index 5e41314192e..bcc2a5b769c 100644 --- a/test/unit/ui/handler/map_event.test.js +++ b/test/unit/ui/handler/map_event.test.js @@ -11,6 +11,7 @@ function createMap(t) { test('MapEvent handler fires touch events with correct values', (t) => { const map = createMap(t); + const target = map.getCanvas(); const touchstart = t.spy(); const touchmove = t.spy(); @@ -20,9 +21,9 @@ test('MapEvent handler fires touch events with correct values', (t) => { map.on('touchmove', touchmove); map.on('touchend', touchend); - const touchesStart = [{identifier: 1, clientX: 0, clientY: 50}]; - const touchesMove = [{identifier: 1, clientX: 0, clientY: 60}]; - const touchesEnd = [{identifier: 1, clientX: 0, clientY: 60}]; + const touchesStart = [{target, identifier: 1, clientX: 0, clientY: 50}]; + const touchesMove = [{target, identifier: 1, clientX: 0, clientY: 60}]; + const touchesEnd = [{target, identifier: 1, clientX: 0, clientY: 60}]; simulate.touchstart(map.getCanvas(), {touches: touchesStart, targetTouches: touchesStart}); t.equal(touchstart.callCount, 1); diff --git a/test/unit/ui/handler/touch_zoom_rotate.test.js b/test/unit/ui/handler/touch_zoom_rotate.test.js index a70ce438c4f..5028aed6c0c 100644 --- a/test/unit/ui/handler/touch_zoom_rotate.test.js +++ b/test/unit/ui/handler/touch_zoom_rotate.test.js @@ -1,6 +1,7 @@ import {test} from '../../../util/test'; import window from '../../../../src/util/window'; import Map from '../../../../src/ui/map'; +import Marker from '../../../../src/ui/marker'; import DOM from '../../../../src/util/dom'; import simulate from '../../../util/simulate_interaction'; @@ -11,6 +12,7 @@ function createMap(t) { test('TouchZoomRotateHandler fires zoomstart, zoom, and zoomend events at appropriate times in response to a pinch-zoom gesture', (t) => { const map = createMap(t); + const target = map.getCanvas(); const zoomstart = t.spy(); const zoom = t.spy(); @@ -22,25 +24,25 @@ test('TouchZoomRotateHandler fires zoomstart, zoom, and zoomend events at approp map.on('zoom', zoom); map.on('zoomend', zoomend); - simulate.touchstart(map.getCanvas(), {targetTouches: [{identifier: 1, clientX: 0, clientY: -50}, {identifier: 2, clientX: 0, clientY: 50}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target, identifier: 1, clientX: 0, clientY: -50}, {target, identifier: 2, clientX: 0, clientY: 50}]}); map._renderTaskQueue.run(); t.equal(zoomstart.callCount, 0); t.equal(zoom.callCount, 0); t.equal(zoomend.callCount, 0); - simulate.touchmove(map.getCanvas(), {targetTouches: [{identifier: 1, clientX: 0, clientY: -100}, {identifier: 2, clientX: 0, clientY: 100}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, identifier: 1, clientX: 0, clientY: -100}, {target, identifier: 2, clientX: 0, clientY: 100}]}); map._renderTaskQueue.run(); t.equal(zoomstart.callCount, 1); t.equal(zoom.callCount, 1); t.equal(zoomend.callCount, 0); - simulate.touchmove(map.getCanvas(), {targetTouches: [{identifier: 1, clientX: 0, clientY: -60}, {identifier: 2, clientX: 0, clientY: 60}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, identifier: 1, clientX: 0, clientY: -60}, {target, identifier: 2, clientX: 0, clientY: 60}]}); map._renderTaskQueue.run(); t.equal(zoomstart.callCount, 1); t.equal(zoom.callCount, 2); t.equal(zoomend.callCount, 0); - simulate.touchend(map.getCanvas(), {targetTouches: []}); + simulate.touchend(map.getCanvas(), {touches: []}); map._renderTaskQueue.run(); // incremented because inertia starts a second zoom @@ -55,6 +57,7 @@ test('TouchZoomRotateHandler fires zoomstart, zoom, and zoomend events at approp test('TouchZoomRotateHandler fires rotatestart, rotate, and rotateend events at appropriate times in response to a pinch-rotate gesture', (t) => { const map = createMap(t); + const target = map.getCanvas(); const rotatestart = t.spy(); const rotate = t.spy(); @@ -64,25 +67,25 @@ test('TouchZoomRotateHandler fires rotatestart, rotate, and rotateend events at map.on('rotate', rotate); map.on('rotateend', rotateend); - simulate.touchstart(map.getCanvas(), {targetTouches: [{identifier: 0, clientX: 0, clientY: -50}, {identifier: 1, clientX: 0, clientY: 50}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target, identifier: 0, clientX: 0, clientY: -50}, {target, identifier: 1, clientX: 0, clientY: 50}]}); map._renderTaskQueue.run(); t.equal(rotatestart.callCount, 0); t.equal(rotate.callCount, 0); t.equal(rotateend.callCount, 0); - simulate.touchmove(map.getCanvas(), {targetTouches: [{identifier: 0, clientX: -50, clientY: 0}, {identifier: 1, clientX: 50, clientY: 0}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, identifier: 0, clientX: -50, clientY: 0}, {target, identifier: 1, clientX: 50, clientY: 0}]}); map._renderTaskQueue.run(); t.equal(rotatestart.callCount, 1); t.equal(rotate.callCount, 1); t.equal(rotateend.callCount, 0); - simulate.touchmove(map.getCanvas(), {targetTouches: [{identifier: 0, clientX: 0, clientY: -50}, {identifier: 1, clientX: 0, clientY: 50}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, identifier: 0, clientX: 0, clientY: -50}, {target, identifier: 1, clientX: 0, clientY: 50}]}); map._renderTaskQueue.run(); t.equal(rotatestart.callCount, 1); t.equal(rotate.callCount, 2); t.equal(rotateend.callCount, 0); - simulate.touchend(map.getCanvas(), {targetTouches: []}); + simulate.touchend(map.getCanvas(), {touches: []}); map._renderTaskQueue.run(); t.equal(rotatestart.callCount, 1); t.equal(rotate.callCount, 2); @@ -94,19 +97,20 @@ test('TouchZoomRotateHandler fires rotatestart, rotate, and rotateend events at test('TouchZoomRotateHandler does not begin a gesture if preventDefault is called on the touchstart event', (t) => { const map = createMap(t); + const target = map.getCanvas(); map.on('touchstart', e => e.preventDefault()); const move = t.spy(); map.on('move', move); - simulate.touchstart(map.getCanvas(), {targetTouches: [{clientX: 0, clientY: 0}, {clientX: 5, clientY: 0}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target, clientX: 0, clientY: 0}, {target, clientX: 5, clientY: 0}]}); map._renderTaskQueue.run(); - simulate.touchmove(map.getCanvas(), {targetTouches: [{clientX: 0, clientY: 0}, {clientX: 0, clientY: 5}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, clientX: 0, clientY: 0}, {target, clientX: 0, clientY: 5}]}); map._renderTaskQueue.run(); - simulate.touchend(map.getCanvas(), {targetTouches: []}); + simulate.touchend(map.getCanvas(), {touches: []}); map._renderTaskQueue.run(); t.equal(move.callCount, 0); @@ -117,6 +121,7 @@ test('TouchZoomRotateHandler does not begin a gesture if preventDefault is calle test('TouchZoomRotateHandler starts zoom immediately when rotation disabled', (t) => { const map = createMap(t); + const target = map.getCanvas(); map.touchZoomRotate.disableRotation(); map.handlers._handlersById.tapZoom.disable(); @@ -128,25 +133,25 @@ test('TouchZoomRotateHandler starts zoom immediately when rotation disabled', (t map.on('zoom', zoom); map.on('zoomend', zoomend); - simulate.touchstart(map.getCanvas(), {targetTouches: [{identifier: 0, clientX: 0, clientY: -5}, {identifier: 2, clientX: 0, clientY: 5}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target, identifier: 0, clientX: 0, clientY: -5}, {target, identifier: 2, clientX: 0, clientY: 5}]}); map._renderTaskQueue.run(); t.equal(zoomstart.callCount, 0); t.equal(zoom.callCount, 0); t.equal(zoomend.callCount, 0); - simulate.touchmove(map.getCanvas(), {targetTouches: [{identifier: 0, clientX: 0, clientY: -5}, {identifier: 2, clientX: 0, clientY: 6}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, identifier: 0, clientX: 0, clientY: -5}, {target, identifier: 2, clientX: 0, clientY: 6}]}); map._renderTaskQueue.run(); t.equal(zoomstart.callCount, 1); t.equal(zoom.callCount, 1); t.equal(zoomend.callCount, 0); - simulate.touchmove(map.getCanvas(), {targetTouches: [{identifier: 0, clientX: 0, clientY: -5}, {identifier: 2, clientX: 0, clientY: 4}]}); + simulate.touchmove(map.getCanvas(), {touches: [{target, identifier: 0, clientX: 0, clientY: -5}, {target, identifier: 2, clientX: 0, clientY: 4}]}); map._renderTaskQueue.run(); t.equal(zoomstart.callCount, 1); t.equal(zoom.callCount, 2); t.equal(zoomend.callCount, 0); - simulate.touchend(map.getCanvas(), {targetTouches: []}); + simulate.touchend(map.getCanvas(), {touches: []}); map._renderTaskQueue.run(); // incremented because inertia starts a second zoom t.equal(zoomstart.callCount, 2); @@ -169,3 +174,113 @@ test('TouchZoomRotateHandler adds css class used for disabling default touch beh t.ok(map.getCanvasContainer().classList.contains(className)); t.end(); }); + +test('TouchZoomRotateHandler zooms when touching two markers on the same map', (t) => { + const map = createMap(t); + + const marker1 = new Marker() + .setLngLat([0, 0]) + .addTo(map); + const marker2 = new Marker() + .setLngLat([0, 0]) + .addTo(map); + const target1 = marker1.getElement(); + const target2 = marker2.getElement(); + + const zoomstart = t.spy(); + const zoom = t.spy(); + const zoomend = t.spy(); + + map.handlers._handlersById.tapZoom.disable(); + map.touchPitch.disable(); + map.on('zoomstart', zoomstart); + map.on('zoom', zoom); + map.on('zoomend', zoomend); + + simulate.touchstart(map.getCanvas(), {touches: [{target: target1, identifier: 1, clientX: 0, clientY: -50}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target: target1, identifier: 1, clientX: 0, clientY: -50}, {target: target2, identifier: 2, clientX: 0, clientY: 50}]}); + map._renderTaskQueue.run(); + t.equal(zoomstart.callCount, 0); + t.equal(zoom.callCount, 0); + t.equal(zoomend.callCount, 0); + + simulate.touchmove(map.getCanvas(), {touches: [{target: target1, identifier: 1, clientX: 0, clientY: -100}, {target: target2, identifier: 2, clientX: 0, clientY: 100}]}); + map._renderTaskQueue.run(); + t.equal(zoomstart.callCount, 1); + t.equal(zoom.callCount, 1); + t.equal(zoomend.callCount, 0); + + simulate.touchmove(map.getCanvas(), {touches: [{target: target1, identifier: 1, clientX: 0, clientY: -60}, {target: target2, identifier: 2, clientX: 0, clientY: 60}]}); + map._renderTaskQueue.run(); + t.equal(zoomstart.callCount, 1); + t.equal(zoom.callCount, 2); + t.equal(zoomend.callCount, 0); + + simulate.touchend(map.getCanvas(), {touches: []}); + map._renderTaskQueue.run(); + + // incremented because inertia starts a second zoom + t.equal(zoomstart.callCount, 2); + map._renderTaskQueue.run(); + t.equal(zoom.callCount, 3); + t.equal(zoomend.callCount, 1); + + map.remove(); + t.end(); +}); + +test('TouchZoomRotateHandler does not zoom when touching an element not on the map', (t) => { + const map = createMap(t); + + const marker1 = new Marker() + .setLngLat([0, 0]) + .addTo(map); + const marker2 = new Marker() + .setLngLat([0, 0]); + + const target1 = marker1.getElement(); // on map + const target2 = marker2.getElement(); // not on map + + const zoomstart = t.spy(); + const zoom = t.spy(); + const zoomend = t.spy(); + + map.handlers._handlersById.tapZoom.disable(); + map.touchPitch.disable(); + map.dragPan.disable(); + map.on('zoomstart', zoomstart); + map.on('zoom', zoom); + map.on('zoomend', zoomend); + + simulate.touchstart(map.getCanvas(), {touches: [{target: target1, identifier: 1, clientX: 0, clientY: -50}]}); + simulate.touchstart(map.getCanvas(), {touches: [{target: target1, identifier: 1, clientX: 0, clientY: -50}, {target: target2, identifier: 2, clientX: 0, clientY: 50}]}); + map._renderTaskQueue.run(); + t.equal(zoomstart.callCount, 0); + t.equal(zoom.callCount, 0); + t.equal(zoomend.callCount, 0); + + simulate.touchmove(map.getCanvas(), {touches: [{target: target1, identifier: 1, clientX: 0, clientY: -100}, {target: target2, identifier: 2, clientX: 0, clientY: 100}]}); + map._renderTaskQueue.run(); + t.equal(zoomstart.callCount, 0); + t.equal(zoom.callCount, 0); + t.equal(zoomend.callCount, 0); + + simulate.touchmove(map.getCanvas(), {touches: [{target: target1, identifier: 1, clientX: 0, clientY: -60}, {target: target2, identifier: 2, clientX: 0, clientY: 60}]}); + map._renderTaskQueue.run(); + t.equal(zoomstart.callCount, 0); + t.equal(zoom.callCount, 0); + t.equal(zoomend.callCount, 0); + + simulate.touchend(map.getCanvas(), {touches: []}); + map._renderTaskQueue.run(); + + // incremented because inertia starts a second zoom + t.equal(zoomstart.callCount, 0); + map._renderTaskQueue.run(); + t.equal(zoom.callCount, 0); + t.equal(zoomend.callCount, 0); + + map.remove(); + t.end(); +}); + diff --git a/test/unit/ui/map.test.js b/test/unit/ui/map.test.js index 6e312951bd7..dedddf15f67 100755 --- a/test/unit/ui/map.test.js +++ b/test/unit/ui/map.test.js @@ -1999,7 +1999,7 @@ test('Map', (t) => { const map = createMap(t, {interactive: true}); map.flyTo({center: [200, 0], duration: 100}); - simulate.touchstart(map.getCanvasContainer(), {targetTouches: [{clientX: 0, clientY: 0}]}); + simulate.touchstart(map.getCanvasContainer(), {touches: [{target: map.getCanvas(), clientX: 0, clientY: 0}]}); t.equal(map.isEasing(), false); map.remove();