diff --git a/js/src/carousel.js b/js/src/carousel.js index 5b6209460a..f0ad83bb0b 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -56,22 +56,28 @@ const Event = { KEYDOWN : `keydown${EVENT_KEY}`, MOUSEENTER : `mouseenter${EVENT_KEY}`, MOUSELEAVE : `mouseleave${EVENT_KEY}`, - TOUCHEND : `touchend${EVENT_KEY}`, TOUCHSTART : `touchstart${EVENT_KEY}`, TOUCHMOVE : `touchmove${EVENT_KEY}`, + TOUCHEND : `touchend${EVENT_KEY}`, + POINTERDOWN : `pointerdown${EVENT_KEY}`, + POINTERMOVE : `pointermove${EVENT_KEY}`, + POINTERUP : `pointerup${EVENT_KEY}`, + POINTERLEAVE : `pointerleave${EVENT_KEY}`, + POINTERCANCEL : `pointercancel${EVENT_KEY}`, LOAD_DATA_API : `load${EVENT_KEY}${DATA_API_KEY}`, CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}` } const ClassName = { - CAROUSEL : 'carousel', - ACTIVE : 'active', - SLIDE : 'slide', - RIGHT : 'carousel-item-right', - LEFT : 'carousel-item-left', - NEXT : 'carousel-item-next', - PREV : 'carousel-item-prev', - ITEM : 'carousel-item' + CAROUSEL : 'carousel', + ACTIVE : 'active', + SLIDE : 'slide', + RIGHT : 'carousel-item-right', + LEFT : 'carousel-item-left', + NEXT : 'carousel-item-next', + PREV : 'carousel-item-prev', + ITEM : 'carousel-item', + POINTER_EVENT : 'pointer-event' } const Selector = { @@ -84,6 +90,11 @@ const Selector = { DATA_RIDE : '[data-ride="carousel"]' } +const PointerType = { + TOUCH : 'touch', + PEN : 'pen' +} + /** * ------------------------------------------------------------------------ * Class Definition @@ -103,7 +114,8 @@ class Carousel { this._config = this._getConfig(config) this._element = element this._indicatorsElement = this._element.querySelector(Selector.INDICATORS) - this._touchSupported = 'ontouchstart' in document.documentElement + this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0 + this._pointerEvent = Boolean(window.PointerEvent || window.MSPointerEvent) this._addEventListeners() } @@ -265,22 +277,35 @@ class Carousel { return } - $(this._element).on(Event.TOUCHSTART, (event) => { - this.touchStartX = event.originalEvent.touches[0].pageX - }) + const start = (event) => { + event.preventDefault() + const originEvent = event.originalEvent - $(this._element).on(Event.TOUCHMOVE, (event) => { + if (this._pointerEvent && (originEvent.pointerType === PointerType.TOUCH || originEvent.pointerType === PointerType.PEN)) { + this.touchStartX = originEvent.clientX + } else { + this.touchStartX = originEvent.touches[0].pageX + } + } + + const move = (event) => { event.preventDefault() // ensure swiping with one touch and not pinching - if (event.originalEvent.touches.length > 1) { + if (event.originalEvent.touches && event.originalEvent.touches.length > 1) { return } - this.touchDeltaX = event.originalEvent.touches[0].pageX - this.touchStartX - }) + if (!this._pointerEvent) { + this.touchDeltaX = event.originalEvent.touches[0].pageX - this.touchStartX + } + } + + const end = (event) => { + if (this._pointerEvent) { + this.touchDeltaX = event.originalEvent.clientX - this.touchStartX + } - $(this._element).on(Event.TOUCHEND, () => { this._handleSwipe() if (this._config.pause === 'hover') { @@ -298,7 +323,21 @@ class Carousel { } this.touchTimeout = setTimeout((event) => this.cycle(event), TOUCHEVENT_COMPAT_WAIT + this._config.interval) } - }) + } + + if (this._pointerEvent) { + $(this._element).on(Event.POINTERDOWN, (event) => start(event)) + $(this._element).on(Event.POINTERMOVE, (event) => move(event)) + $(this._element).on(Event.POINTERUP, (event) => end(event)) + $(this._element).on(Event.POINTERLEAVE, (event) => end(event)) + $(this._element).on(Event.POINTERCANCEL, (event) => end(event)) + + this._element.classList.add(ClassName.POINTER_EVENT) + } else { + $(this._element).on(Event.TOUCHSTART, (event) => start(event)) + $(this._element).on(Event.TOUCHMOVE, (event) => move(event)) + $(this._element).on(Event.TOUCHEND, (event) => end(event)) + } } _keydown(event) { diff --git a/js/tests/unit/carousel.js b/js/tests/unit/carousel.js index e416ab20e4..68c28aec5e 100644 --- a/js/tests/unit/carousel.js +++ b/js/tests/unit/carousel.js @@ -3,6 +3,26 @@ $(function () { window.Carousel = typeof bootstrap !== 'undefined' ? bootstrap.Carousel : Carousel + var originWinPointerEvent = window.PointerEvent + var originMsPointerEvent = window.MSPointerEvent + var supportPointerEvent = Boolean(window.PointerEvent || window.MSPointerEvent) + + function clearPointerEvents() { + window.PointerEvent = null + window.MSPointerEvent = null + } + + function restorePointerEvents() { + window.PointerEvent = originWinPointerEvent + window.MSPointerEvent = originMsPointerEvent + } + + var stylesCarousel = [ + '' + ].join('') + QUnit.module('carousel plugin') QUnit.test('should be defined on jQuery object', function (assert) { @@ -1006,8 +1026,55 @@ $(function () { }, 80) }) - QUnit.test('should allow swiperight and call prev', function (assert) { + QUnit.test('should allow swiperight and call prev with pointer events', function (assert) { + if (!supportPointerEvent) { + assert.expect(0) + return + } + + Simulator.setType('pointer') + assert.expect(3) + var $styles = $(stylesCarousel).appendTo('head') + var done = assert.async() + document.documentElement.ontouchstart = $.noop + + var carouselHTML = + '