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 = {
DATA_RIDE : '[data-ride="carousel"]'
}
+const PointerType = {
+ TOUCH : 'touch',
+ PEN : 'pen'
+}
+
/**
* ------------------------------------------------------------------------
* Class Definition
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()
}
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') {
}
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) {
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 = [
+ '<style>',
+ ' .carousel.pointer-event { -ms-touch-action: pan-x; touch-action: pan-x; }',
+ '</style>'
+ ].join('')
+
QUnit.module('carousel plugin')
QUnit.test('should be defined on jQuery object', function (assert) {
}, 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 =
+ '<div class="carousel" data-interval="false">' +
+ ' <div class="carousel-inner">' +
+ ' <div id="item" class="carousel-item">' +
+ ' <img alt="">' +
+ ' </div>' +
+ ' <div class="carousel-item active">' +
+ ' <img alt="">' +
+ ' </div>' +
+ ' </div>' +
+ '</div>'
+
+ var $carousel = $(carouselHTML)
+ $carousel.appendTo('#qunit-fixture')
+ var $item = $('#item')
+ $carousel.bootstrapCarousel()
+ var carousel = $carousel.data('bs.carousel')
+ var spy = sinon.spy(carousel, 'prev')
+
+ $carousel.one('slid.bs.carousel', function () {
+ assert.ok(true, 'slid event fired')
+ assert.ok($item.hasClass('active'))
+ assert.ok(spy.called)
+ delete document.documentElement.ontouchstart
+ $styles.remove()
+ done()
+ })
+
+ Simulator.gestures.swipe($carousel[0], {
+ deltaX: 300,
+ deltaY: 0
+ })
+ })
+
+ QUnit.test('should allow swiperight and call prev with touch events', function (assert) {
Simulator.setType('touch')
+ clearPointerEvents()
assert.expect(3)
var done = assert.async()
document.documentElement.ontouchstart = $.noop
assert.ok($item.hasClass('active'))
assert.ok(spy.called)
delete document.documentElement.ontouchstart
+ restorePointerEvents()
done()
})
})
})
- QUnit.test('should allow swipeleft and call next', function (assert) {
+ QUnit.test('should allow swipeleft and call next with pointer events', function (assert) {
+ if (!supportPointerEvent) {
+ assert.expect(0)
+ return
+ }
+
+ assert.expect(3)
+ Simulator.setType('pointer')
+
+ var $styles = $(stylesCarousel).appendTo('head')
+ var done = assert.async()
+ document.documentElement.ontouchstart = $.noop
+
+ var carouselHTML =
+ '<div class="carousel" data-interval="false">' +
+ ' <div class="carousel-inner">' +
+ ' <div id="item" class="carousel-item active">' +
+ ' <img alt="">' +
+ ' </div>' +
+ ' <div class="carousel-item">' +
+ ' <img alt="">' +
+ ' </div>' +
+ ' </div>' +
+ '</div>'
+
+ var $carousel = $(carouselHTML)
+ $carousel.appendTo('#qunit-fixture')
+ var $item = $('#item')
+ $carousel.bootstrapCarousel()
+ var carousel = $carousel.data('bs.carousel')
+ var spy = sinon.spy(carousel, 'next')
+
+ $carousel.one('slid.bs.carousel', function () {
+ assert.ok(true, 'slid event fired')
+ assert.ok(!$item.hasClass('active'))
+ assert.ok(spy.called)
+ $styles.remove()
+ done()
+ })
+
+ Simulator.gestures.swipe($carousel[0], {
+ pos: [300, 10],
+ deltaX: -300,
+ deltaY: 0
+ })
+ })
+
+ QUnit.test('should allow swipeleft and call next with touch events', function (assert) {
assert.expect(3)
+ clearPointerEvents()
Simulator.setType('touch')
var done = assert.async()
assert.ok(true, 'slid event fired')
assert.ok(!$item.hasClass('active'))
assert.ok(spy.called)
+ restorePointerEvents()
done()
})
})
})
- QUnit.test('should not allow pinch', function (assert) {
+ QUnit.test('should not allow pinch with touch events', function (assert) {
assert.expect(0)
+ clearPointerEvents()
Simulator.setType('touch')
var done = assert.async()
document.documentElement.ontouchstart = $.noop
deltaY: 0,
touches: 2
}, function () {
+ restorePointerEvents()
done()
})
})
},
{
"path": "./dist/js/bootstrap.js",
- "maxSize": "21 kB"
+ "maxSize": "22 kB"
},
{
"path": "./dist/js/bootstrap.min.js",
position: relative;
}
+.carousel.pointer-event {
+ touch-action: pan-x;
+}
+
.carousel-inner {
position: relative;
width: 100%;