]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
use pointer events if available
authorJohann-S <johann.servoire@gmail.com>
Sun, 14 Oct 2018 21:10:13 +0000 (23:10 +0200)
committerXhmikosR <xhmikosr@gmail.com>
Sat, 20 Oct 2018 12:32:09 +0000 (15:32 +0300)
js/src/carousel.js
js/tests/unit/carousel.js
package.json
scss/_carousel.scss

index 5b6209460a4e0080cfde6ba128ab824e1c7b3556..f0ad83bb0b49915ad633d5ac20d279ca605bf29b 100644 (file)
@@ -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) {
index e416ab20e4279899738532badc301c8926be4c39..68c28aec5e983e4b77935102fc871bef273e2681 100644 (file)
@@ -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 = [
+    '<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) {
@@ -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 =
+        '<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
@@ -1036,6 +1103,7 @@ $(function () {
       assert.ok($item.hasClass('active'))
       assert.ok(spy.called)
       delete document.documentElement.ontouchstart
+      restorePointerEvents()
       done()
     })
 
@@ -1045,8 +1113,56 @@ $(function () {
     })
   })
 
-  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()
@@ -1075,6 +1191,7 @@ $(function () {
       assert.ok(true, 'slid event fired')
       assert.ok(!$item.hasClass('active'))
       assert.ok(spy.called)
+      restorePointerEvents()
       done()
     })
 
@@ -1085,8 +1202,9 @@ $(function () {
     })
   })
 
-  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
@@ -1102,6 +1220,7 @@ $(function () {
       deltaY: 0,
       touches: 2
     }, function () {
+      restorePointerEvents()
       done()
     })
   })
index c7302c490dc82d8efc5b3d80a97e04cceb847a80..39e883d18be051bc98a42a1dd2350ddb81b0736c 100644 (file)
     },
     {
       "path": "./dist/js/bootstrap.js",
-      "maxSize": "21 kB"
+      "maxSize": "22 kB"
     },
     {
       "path": "./dist/js/bootstrap.min.js",
index 97e792ea722ddea31c8161846d70649cbee013b1..7d3728b40ded15edb0904515cac1f31c7abd37ba 100644 (file)
   position: relative;
 }
 
+.carousel.pointer-event {
+  touch-action: pan-x;
+}
+
 .carousel-inner {
   position: relative;
   width: 100%;