diff --git a/src/base/events/events.js b/src/base/events/events.js index ff9387b3..4db219c1 100644 --- a/src/base/events/events.js +++ b/src/base/events/events.js @@ -757,6 +757,8 @@ Events.CONTAINER_PAUSE = 'container:pause' Events.CONTAINER_ENDED = 'container:ended' Events.CONTAINER_CLICK = 'container:click' Events.CONTAINER_DBLCLICK = 'container:dblclick' +Events.CONTAINER_TAP = 'container:tap' +Events.CONTAINER_DBLTAP = 'container:dbltap' Events.CONTAINER_CONTEXTMENU = 'container:contextmenu' Events.CONTAINER_MOUSE_ENTER = 'container:mouseenter' Events.CONTAINER_MOUSE_LEAVE = 'container:mouseleave' diff --git a/src/components/container/container.js b/src/components/container/container.js index 604ea4f7..213241a8 100644 --- a/src/components/container/container.js +++ b/src/components/container/container.js @@ -10,7 +10,6 @@ import Events from '@/base/events' import UIObject from '@/base/ui_object' import ErrorMixin from '@/base/error_mixin' import Styler from '@/base/styler' -import { DoubleEventHandler } from '@/utils' import ContainerStyle from './public/style.scss' @@ -37,7 +36,7 @@ export default class Container extends UIObject { return { 'click': 'clicked', 'dblclick': 'dblClicked', - 'touchend': 'dblTap', + 'touchend': 'tapped', 'contextmenu': 'onContextMenu', 'mouseenter': 'mouseEnter', 'mouseleave': 'mouseLeave', @@ -144,9 +143,9 @@ export default class Container extends UIObject { this.isReady = false this.mediaControlDisabled = false this.plugins = [this.playback] - this.dblTapHandler = new DoubleEventHandler(500) this.clickTimer = null this.clickDelay = 200 // FIXME: could be a player option + this.tapTimer = null this.actionsMetadata = {} this.bindEvents() } @@ -378,12 +377,13 @@ export default class Container extends UIObject { this.currentTime = 0 } - clicked() { + clicked(evt) { if (!this.options.chromeless || this.options.allowUserInteraction) { // The event is delayed because it can be canceled by a double-click event // An example of use is to prevent playback from pausing when switching to full screen this.clickTimer = setTimeout(() => { - this.clickTimer && this.trigger(Events.CONTAINER_CLICK, this, this.name) + this.clickTimer && this.trigger(Events.CONTAINER_CLICK, this, this.name, evt) + this.clickTimer = null }, this.clickDelay) } } @@ -393,20 +393,37 @@ export default class Container extends UIObject { this.clickTimer = null } - dblClicked() { + dblClicked(evt) { if (!this.options.chromeless || this.options.allowUserInteraction) { this.cancelClicked() - this.trigger(Events.CONTAINER_DBLCLICK, this, this.name) + this.trigger(Events.CONTAINER_DBLCLICK, this, this.name, evt) } } - dblTap(evt) { - if (!this.options.chromeless || this.options.allowUserInteraction) { - this.dblTapHandler.handle(evt, () => { - this.cancelClicked() - this.trigger(Events.CONTAINER_DBLCLICK, this, this.name) - }) + tapped(evt) { + if (this.options.chromeless || !this.options.allowUserInteraction) return + + if (this.tapTimer) { + this.cancelTapped() + this.cancelClicked() + this.trigger(Events.CONTAINER_DBLTAP, this, this.name, evt) + // Do not let the browser emit a click event afterward + evt.preventDefault() + return } + + // The event is delayed because it can be canceled by a double tap event + this.tapTimer = setTimeout(() => { + if (this.tapTimer) { + this.trigger(Events.CONTAINER_TAP, this, this.name, evt) + this.tapTimer = null + } + }, this.clickDelay) + } + + cancelTapped() { + clearTimeout(this.tapTimer) + this.tapTimer = null } onContextMenu(event) { diff --git a/src/components/container/container.test.js b/src/components/container/container.test.js index 06c499d8..094d9ef5 100644 --- a/src/components/container/container.test.js +++ b/src/components/container/container.test.js @@ -315,4 +315,34 @@ describe('Container', function() { expect(this.container.trigger).not.toHaveBeenCalled() }) }) + + describe('triggers a CONTAINER_TAP or CONTAINER_DBLTAP depending on the value of the tapTimer', () => { + jest.useFakeTimers() + + test('triggers CONTAINER_TAP on touchend', () => { + this.container.options.allowUserInteraction = true + const evt = new TouchEvent('touchend') + const spy = jest.spyOn(this.container, 'trigger') + this.container.tapped(evt) + + jest.advanceTimersByTime(this.container.clickDelay) + + expect(spy).toHaveBeenCalledWith(Events.CONTAINER_TAP, expect.anything(Object), this.container.name, evt) + }) + + test('triggers CONTAINER_DBLTAP on double touchend', () => { + this.container.options.allowUserInteraction = true + const evt = new Event('touchend') + const spy = jest.spyOn(this.container, 'trigger') + this.container.tapped(evt) + + setTimeout(() => { + this.container.tapped(evt) + }, this.container.clickDelay / 2) + + jest.runOnlyPendingTimers() + expect(spy).not.toHaveBeenCalledWith(Events.CONTAINER_TAP) + expect(spy).toHaveBeenCalledWith(Events.CONTAINER_DBLTAP, expect.anything(Object), this.container.name, evt) + }) + }) }) diff --git a/src/utils/utils.js b/src/utils/utils.js index 0ce03542..62e264a9 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -309,26 +309,6 @@ export class DomRecycler { DomRecycler.options = { recycleVideo: false } -export class DoubleEventHandler { - constructor(delay = 500) { - this.delay = delay - this.lastTime = 0 - } - - handle(event, cb, prevented = true) { - // Based on http://jsfiddle.net/brettwp/J4djY/ - let currentTime = new Date().getTime() - let diffTime = currentTime - this.lastTime - - if (diffTime < this.delay && diffTime > 0) { - cb() - prevented && event.preventDefault() - } - - this.lastTime = currentTime - } -} - export default { Config, Fullscreen, @@ -349,5 +329,4 @@ export default { listContainsIgnoreCase, canAutoPlayMedia, Media, - DoubleEventHandler, } diff --git a/src/utils/utils.test.js b/src/utils/utils.test.js index 6e73c797..3f3dc069 100644 --- a/src/utils/utils.test.js +++ b/src/utils/utils.test.js @@ -412,19 +412,4 @@ describe('Utils', () => { expect(video1).toEqual(video2) }) }) - - describe('DoubleEventHandler module', () => { - test('handle double event', (done) => { - const handler = new utils.DoubleEventHandler() - const spy = jest.fn() - const evt = new Event('touchend') - - handler.handle(evt, spy) - setTimeout(() => { - handler.handle(evt, spy) - expect(spy).toHaveBeenCalledTimes(1) - done() - }, 500/2) - }) - }) })