]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Add touch support in our carousel with HammerJS.
authorJohann-S <johann.servoire@gmail.com>
Sat, 3 Mar 2018 21:04:11 +0000 (23:04 +0200)
committerXhmikosR <xhmikosr@gmail.com>
Sat, 20 Oct 2018 12:32:09 +0000 (15:32 +0300)
17 files changed:
README.md
_config.yml
build/build-plugins.js
build/generate-sri.js
build/rollup.config.js
js/src/carousel.js
js/tests/index.html
js/tests/karma.conf.js
js/tests/unit/.eslintrc.json
js/tests/unit/carousel.js
js/tests/visual/carousel.html
package-lock.json
package.json
site/_includes/scripts.html
site/docs/4.1/components/carousel.md
site/docs/4.1/getting-started/contents.md
site/docs/4.1/getting-started/introduction.md

index 8694b686f14e44e3c78bd704bb11e979b6c36778..17680e642dec62bad286493cf6f673c1088951ef 100644 (file)
--- a/README.md
+++ b/README.md
@@ -99,7 +99,7 @@ bootstrap/
         └── bootstrap.min.js.map
 ```
 
-We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [source maps](https://developers.google.com/web/tools/chrome-devtools/debug/readability/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/), but not [jQuery](https://jquery.com/).
+We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [source maps](https://developers.google.com/web/tools/chrome-devtools/debug/readability/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/) and [HammerJS](https://hammerjs.github.io/), but not [jQuery](https://jquery.com/).
 
 
 ## Bugs and feature requests
index e073dce9b9df4009d6b67d3d12be93c014d7535e..7fab354119bbf93af72d96b8f5f2756c7fb5df58 100644 (file)
@@ -58,6 +58,8 @@ cdn:
   jquery_hash:      "sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
   popper:           "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.4/umd/popper.min.js"
   popper_hash:      "sha384-GM0Y80ecpwKxF1D5XCrGanKusGDy9WW0O2sSM84neB4iFhvKp3fwnoIRnPsQcN1R"
+  hammer:           "https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"
+  hammer_hash:      "sha384-Cs3dgUx6+jDxxuqHvVH8Onpyj2LF1gKZurLDlhqzuJmUqVYMJ0THTWpxK5Z086Zm"
 
 toc:
   min_level:        2
index 1de65b426dc00a3ae56fd23d86cad56a08699dbe..299f502d9d943548104d53c87005231ab8823f29 100644 (file)
@@ -5,10 +5,10 @@
  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
  */
 
-const path    = require('path')
-const rollup  = require('rollup')
-const babel   = require('rollup-plugin-babel')
-const banner  = require('./banner.js')
+const path   = require('path')
+const rollup = require('rollup')
+const babel  = require('rollup-plugin-babel')
+const banner = require('./banner.js')
 
 const TEST    = process.env.NODE_ENV === 'test'
 const plugins = [
@@ -41,8 +41,9 @@ const rootPath = TEST ? '../js/coverage/dist/' : '../js/dist/'
 function build(plugin) {
   console.log(`Building ${plugin} plugin...`)
 
-  const external = ['jquery', 'popper.js']
+  const external = ['hammerjs', 'jquery', 'popper.js']
   const globals = {
+    hammerjs: 'Hammer',
     jquery: 'jQuery', // Ensure we use jQuery which is always available even in noConflict mode
     'popper.js': 'Popper'
   }
index 6929097703eca5a608859c7074919cf9645b65c2..13b90db1ceb26a77ea414b714f6d8d283535fd13 100644 (file)
@@ -42,6 +42,10 @@ const files = [
   {
     file: 'node_modules/popper.js/dist/umd/popper.min.js',
     configPropertyName: 'popper_hash'
+  },
+  {
+    file: 'node_modules/hammerjs/hammer.min.js',
+    configPropertyName: 'hammer_hash'
   }
 ]
 
index c8acf7a9e92b9d94d80cf98fd4d0b92a63458218..72e3951fa625b5f42a64697dfe437ea6624735fa 100644 (file)
@@ -1,12 +1,13 @@
-const path    = require('path')
-const babel   = require('rollup-plugin-babel')
-const resolve = require('rollup-plugin-node-resolve')
-const banner  = require('./banner.js')
+const path     = require('path')
+const babel    = require('rollup-plugin-babel')
+const resolve  = require('rollup-plugin-node-resolve')
+const commonjs = require('rollup-plugin-commonjs')
+const banner   = require('./banner.js')
 
 const BUNDLE  = process.env.BUNDLE === 'true'
 
-let fileDest  = 'bootstrap.js'
-const external = ['jquery', 'popper.js']
+let fileDest   = 'bootstrap.js'
+const external = ['jquery', 'hammerjs', 'popper.js']
 const plugins = [
   babel({
     exclude: 'node_modules/**', // Only transpile our source code
@@ -21,15 +22,22 @@ const plugins = [
 ]
 const globals = {
   jquery: 'jQuery', // Ensure we use jQuery which is always available even in noConflict mode
+  hammerjs: 'Hammer',
   'popper.js': 'Popper'
 }
 
 if (BUNDLE) {
   fileDest = 'bootstrap.bundle.js'
-  // Remove last entry in external array to bundle Popper
-  external.pop()
+  // We just keep jQuery as external
+  external.length = 1
   delete globals['popper.js']
-  plugins.push(resolve())
+  delete globals.hammerjs
+  plugins.push(
+    commonjs({
+      include: 'node_modules/**'
+    }),
+    resolve()
+  )
 }
 
 module.exports = {
index fcc78af6f104ef57fca1691fbbb899f438eb9a40..b2765ac5a8be0ea52b26062c0c54b8e77386e4f8 100644 (file)
@@ -1,4 +1,5 @@
 import $ from 'jquery'
+import Hammer from 'hammerjs'
 import Util from './util'
 
 /**
@@ -23,13 +24,15 @@ const JQUERY_NO_CONFLICT     = $.fn[NAME]
 const ARROW_LEFT_KEYCODE     = 37 // KeyboardEvent.which value for left arrow key
 const ARROW_RIGHT_KEYCODE    = 39 // KeyboardEvent.which value for right arrow key
 const TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch
+const HAMMER_ENABLED         = typeof Hammer !== 'undefined'
 
 const Default = {
   interval : 5000,
   keyboard : true,
   slide    : false,
   pause    : 'hover',
-  wrap     : true
+  wrap     : true,
+  touch    : true
 }
 
 const DefaultType = {
@@ -37,7 +40,8 @@ const DefaultType = {
   keyboard : 'boolean',
   slide    : '(boolean|string)',
   pause    : '(string|boolean)',
-  wrap     : 'boolean'
+  wrap     : 'boolean',
+  touch    : 'boolean'
 }
 
 const Direction = {
@@ -55,7 +59,9 @@ const Event = {
   MOUSELEAVE     : `mouseleave${EVENT_KEY}`,
   TOUCHEND       : `touchend${EVENT_KEY}`,
   LOAD_DATA_API  : `load${EVENT_KEY}${DATA_API_KEY}`,
-  CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`
+  CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`,
+  SWIPELEFT      : 'swipeleft',
+  SWIPERIGHT     : 'swiperight'
 }
 
 const ClassName = {
@@ -84,21 +90,30 @@ const Selector = {
  * Class Definition
  * ------------------------------------------------------------------------
  */
-
 class Carousel {
   constructor(element, config) {
-    this._items              = null
-    this._interval           = null
-    this._activeElement      = null
-
-    this._isPaused           = false
-    this._isSliding          = false
-
-    this.touchTimeout        = null
-
-    this._config             = this._getConfig(config)
-    this._element            = $(element)[0]
-    this._indicatorsElement  = this._element.querySelector(Selector.INDICATORS)
+    this._items         = null
+    this._interval      = null
+    this._activeElement = null
+    this._isPaused      = false
+    this._isSliding     = false
+    this.touchTimeout   = null
+    this.hammer         = null
+
+    this._config            = this._getConfig(config)
+    this._element           = element
+    this._indicatorsElement = this._element.querySelector(Selector.INDICATORS)
+    this._touchSupported    = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0
+
+    if (HAMMER_ENABLED && this._touchSupported && this._config.touch) {
+      this.hammer = new Hammer(this._element, {
+        recognizers: [[
+          Hammer.Swipe, {
+            direction: Hammer.DIRECTION_HORIZONTAL
+          }
+        ]]
+      })
+    }
 
     this._addEventListeners()
   }
@@ -226,11 +241,16 @@ class Carousel {
         .on(Event.KEYDOWN, (event) => this._keydown(event))
     }
 
+    if (this.hammer) {
+      this.hammer.on(Event.SWIPELEFT, () => this.next())
+      this.hammer.on(Event.SWIPERIGHT, () => this.prev())
+    }
+
     if (this._config.pause === 'hover') {
       $(this._element)
         .on(Event.MOUSEENTER, (event) => this.pause(event))
         .on(Event.MOUSELEAVE, (event) => this.cycle(event))
-      if ('ontouchstart' in document.documentElement) {
+      if (this._touchSupported) {
         // If it's a touch-enabled device, mouseenter/leave are fired as
         // part of the mouse compatibility events on first tap - the carousel
         // would stop cycling until user tapped out of it;
index ce4a0e3081a361a3113fb848593469262d83a958..201e15f2a81ae221bde24a993c75fb856899572e 100644 (file)
@@ -20,6 +20,7 @@
       }())
     </script>
     <script src="../../node_modules/popper.js/dist/umd/popper.min.js"></script>
+    <script src="../../node_modules/hammerjs/hammer.min.js"></script>
 
     <!-- QUnit -->
     <link rel="stylesheet" href="../../node_modules/qunit/qunit/qunit.css" media="screen">
@@ -28,6 +29,9 @@
     <!-- Sinon -->
     <script src="../../node_modules/sinon/pkg/sinon-no-sourcemaps.js"></script>
 
+    <!-- Hammer simulator -->
+    <script src="../../node_modules/hammer-simulator/index.js"></script>
+
     <script>
       // Disable jQuery event aliases to ensure we don't accidentally use any of them
       [
index 070606a521a73ba1b53c26cbe54600e0beea5afc..c3c64e8850d30357dce040a96cbdd3d2cb7d7fcf 100644 (file)
@@ -12,16 +12,16 @@ const jqueryFile = process.env.USE_OLD_JQUERY ? 'https://code.jquery.com/jquery-
 const bundle = process.env.BUNDLE === 'true'
 const browserStack = process.env.BROWSER === 'true'
 
-const frameworks = [
-  'qunit',
-  'sinon'
-]
-
 const plugins = [
   'karma-qunit',
   'karma-sinon'
 ]
 
+const frameworks = [
+  'qunit',
+  'sinon'
+]
+
 const reporters = ['dots']
 
 const detectBrowsers = {
@@ -46,7 +46,12 @@ const customLaunchers = {
   }
 }
 
-let files = ['node_modules/popper.js/dist/umd/popper.min.js']
+let files = [
+  'node_modules/popper.js/dist/umd/popper.min.js',
+  'node_modules/hammerjs/hammer.min.js',
+  'node_modules/hammer-simulator/index.js'
+]
+
 const conf = {
   basePath: '../..',
   port: 9876,
index 0896f406d0f5ec66bfe798df1b9e5dbc519d496b..a7fa64af0edfb8207cc0a22757c01bf78a4ab58b 100644 (file)
@@ -9,7 +9,9 @@
     "sinon": false,
     "Util": false,
     "Alert": false,
-    "Button": false
+    "Button": false,
+    "Carousel": false,
+    "Simulator": false
   },
   "parserOptions": {
     "ecmaVersion": 5,
index 20b12f4b069b8d91f02774c1b8bd1ad8245dd71c..0bc52219e4ed3e2b67b2abcb7a018b659aa71e31 100644 (file)
@@ -1,6 +1,10 @@
 $(function () {
   'use strict'
 
+  window.Carousel = typeof bootstrap !== 'undefined' ? bootstrap.Carousel : Carousel
+
+  var touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0
+
   QUnit.module('carousel plugin')
 
   QUnit.test('should be defined on jQuery object', function (assert) {
@@ -25,6 +29,20 @@ $(function () {
     assert.strictEqual(typeof $.fn.carousel, 'undefined', 'carousel was set back to undefined (orig value)')
   })
 
+  QUnit.test('should return version', function (assert) {
+    assert.expect(1)
+
+    assert.strictEqual(typeof Carousel.VERSION, 'string')
+  })
+
+  QUnit.test('should return default parameters', function (assert) {
+    assert.expect(1)
+
+    var defaultConfig = Carousel.Default
+
+    assert.strictEqual(defaultConfig.touch, true)
+  })
+
   QUnit.test('should throw explicit error on undefined method', function (assert) {
     assert.expect(1)
     var $el = $('<div/>')
@@ -989,4 +1007,113 @@ $(function () {
       }, 80)
     }, 80)
   })
+
+  QUnit.test('should allow swiperight and call prev', function (assert) {
+    if (!touchSupported) {
+      assert.expect(0)
+
+      return
+    }
+
+    assert.expect(2)
+    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()
+
+    $carousel.one('slid.bs.carousel', function () {
+      assert.ok(true, 'slid event fired')
+      assert.ok($item.hasClass('active'))
+      delete document.documentElement.ontouchstart
+      done()
+    })
+
+    Simulator.gestures.swipe($carousel[0], {
+      deltaX: 300,
+      deltaY: 0
+    })
+  })
+
+  QUnit.test('should not use HammerJS when touch option is false', function (assert) {
+    assert.expect(1)
+
+    var $carousel = $('<div></div>').appendTo('#qunit-fixture')
+    $carousel.bootstrapCarousel({
+      touch: false
+    })
+
+    var carousel = $carousel.data('bs.carousel')
+
+    assert.strictEqual(carousel.hammer, null)
+  })
+
+  QUnit.test('should use HammerJS when touch option is true', function (assert) {
+    assert.expect(1)
+
+    document.documentElement.ontouchstart = $.noop
+
+    var $carousel = $('<div></div>').appendTo('#qunit-fixture')
+    $carousel.bootstrapCarousel()
+
+    var carousel = $carousel.data('bs.carousel')
+
+    assert.ok(carousel.hammer !== null)
+  })
+
+  QUnit.test('should allow swipeleft and call next', function (assert) {
+    if (!touchSupported) {
+      assert.expect(0)
+
+      return
+    }
+
+    assert.expect(2)
+    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()
+
+    $carousel.one('slid.bs.carousel', function () {
+      assert.ok(true, 'slid event fired')
+      assert.ok(!$item.hasClass('active'))
+      delete document.documentElement.ontouchstart
+      done()
+    })
+
+    Simulator.gestures.swipe($carousel[0], {
+      pos: [300, 10],
+      deltaX: -300,
+      deltaY: 0
+    })
+  })
 })
index 630f870cf4e063909f70b8cb829e6358272df631..cd917caa627e2eb89b1a7456df3357f535a61b18 100644 (file)
@@ -46,6 +46,7 @@
     </div>
 
     <script src="../../../site/docs/4.1/assets/js/vendor/jquery-slim.min.js"></script>
+    <script src="../../../node_modules/hammerjs/hammer.min.js"></script>
     <script src="../../dist/util.js"></script>
     <script src="../../dist/carousel.js"></script>
     <script>
index e6356551a081daa0b8abd474e019addf7b914faf..630c36c6b7fc904801c02a049d0b0661efff5ca1 100644 (file)
         "pify": "^3.0.0"
       }
     },
+    "hammer-simulator": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/hammer-simulator/-/hammer-simulator-0.0.1.tgz",
+      "integrity": "sha1-7tO85CtDMgF1o/T4NP4gjl+iSho=",
+      "dev": true
+    },
+    "hammerjs": {
+      "version": "2.0.8",
+      "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
+      "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=",
+      "dev": true
+    },
     "handlebars": {
       "version": "4.0.12",
       "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz",
         "yallist": "^2.1.2"
       }
     },
+    "magic-string": {
+      "version": "0.22.5",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz",
+      "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==",
+      "dev": true,
+      "requires": {
+        "vlq": "^0.2.2"
+      }
+    },
     "make-dir": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
         "rollup-pluginutils": "^2.3.0"
       }
     },
+    "rollup-plugin-commonjs": {
+      "version": "9.1.6",
+      "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.1.6.tgz",
+      "integrity": "sha512-J7GOJm9uzEeLqkVxYSgjyoieh34hATWpa9G2M1ilGzWOLYGfQx5IDQ9ewG8QUj/Z2dzgV+d0/AyloAzElkABAA==",
+      "dev": true,
+      "requires": {
+        "estree-walker": "^0.5.1",
+        "magic-string": "^0.22.4",
+        "resolve": "^1.5.0",
+        "rollup-pluginutils": "^2.0.1"
+      }
+    },
     "rollup-plugin-node-resolve": {
       "version": "3.4.0",
       "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.4.0.tgz",
         "unist-util-stringify-position": "^1.1.1"
       }
     },
+    "vlq": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz",
+      "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==",
+      "dev": true
+    },
     "vnu-jar": {
       "version": "18.8.29",
       "resolved": "https://registry.npmjs.org/vnu-jar/-/vnu-jar-18.8.29.tgz",
index fbb238adcd9a035f22c36ba172e4ed79a6d1d488..2f2f76bc586d31f5d6d77eb491fe941a7158b696 100644 (file)
@@ -95,6 +95,7 @@
   "license": "MIT",
   "dependencies": {},
   "peerDependencies": {
+    "hammerjs": "^2.0.8",
     "jquery": "1.9.1 - 3",
     "popper.js": "^1.14.4"
   },
     "eslint": "^5.7.0",
     "find-unused-sass-variables": "^0.2.1",
     "glob": "^7.1.3",
+    "hammer-simulator": "0.0.1",
+    "hammerjs": "^2.0.8",
     "htmllint-cli": "^0.0.7",
     "http-server": "^0.11.1",
     "ip": "^1.1.5",
     "qunit": "^2.7.0",
     "rollup": "^0.66.6",
     "rollup-plugin-babel": "^4.0.3",
+    "rollup-plugin-commonjs": "^9.1.6",
     "rollup-plugin-node-resolve": "^3.4.0",
     "shelljs": "^0.8.2",
     "shx": "^0.3.2",
     },
     {
       "path": "./dist/js/bootstrap.bundle.js",
-      "maxSize": "45 kB"
+      "maxSize": "61 kB"
     },
     {
       "path": "./dist/js/bootstrap.bundle.min.js",
-      "maxSize": "25 kB"
+      "maxSize": "28 kB"
     },
     {
       "path": "./dist/js/bootstrap.js",
index 74d6f9ef2e1eb317bf49201544bb7c8b2405ce4d..dfbf3f2509a24ee7ff55abb15f74e487e82f1813 100644 (file)
@@ -1,5 +1,6 @@
 <script src="{{ site.cdn.jquery }}" integrity="{{ site.cdn.jquery_hash }}" crossorigin="anonymous"></script>
 <script>window.jQuery || document.write('<script src="{{ site.baseurl }}/assets/js/vendor/jquery-slim.min.js"><\/script>')</script>
+<script src="{{ site.cdn.hammer }}"{% if site.github %} integrity="{{ site.cdn.hammer_hash }}" crossorigin="anonymous"{% endif %}></script>
 
 {%- if jekyll.environment == "production" -%}
   <script src="{{ site.baseurl }}/docs/{{ site.docs_version }}/dist/js/bootstrap.bundle.min.js" integrity="{{ site.cdn.js_bundle_hash }}" crossorigin="anonymous"></script>
index 543b06430a6a789b5057bfe316b52a14239c72be..6bfb352069fbd2533c821e6d748426e45aaa74bb 100644 (file)
@@ -12,6 +12,8 @@ The carousel is a slideshow for cycling through a series of content, built with
 
 In browsers where the [Page Visibility API](https://www.w3.org/TR/page-visibility/) is supported, the carousel will avoid sliding when the webpage is not visible to the user (such as when the browser tab is inactive, the browser window is minimized, etc.).
 
+The carousel supports swipe gestures (left and right) using [HammerJS]({{ site.cdn.hammer }}). For this to function correctly you need to include HammerJS before Bootstrap or use `bootstrap.bundle.min.js` / `bootstrap.bundle.js` which contains HammerJS.
+
 Please be aware that nested carousels are not supported, and carousels are generally not compliant with accessibility standards.
 
 Lastly, if you're building our JavaScript from source, it [requires `util.js`]({{ site.baseurl }}/docs/{{ site.docs_version }}/getting-started/javascript/#util).
@@ -281,6 +283,12 @@ Options can be passed via data attributes or JavaScript. For data attributes, ap
       <td>true</td>
       <td>Whether the carousel should cycle continuously or have hard stops.</td>
     </tr>
+    <tr>
+      <td>touch</td>
+      <td>boolean</td>
+      <td>true</td>
+      <td>Whether the carousel should handle touch event and allow swipe left/right.</td>
+    </tr>
   </tbody>
 </table>
 
index fd38e2fba5e9682e1af354a98da7a1495d5471c0..0c210d0438aa41db019164bc527d5df4b3da4b87 100644 (file)
@@ -38,7 +38,7 @@ bootstrap/
     └── bootstrap.min.js.map
 {% endhighlight %}
 
-This is the most basic form of Bootstrap: precompiled files for quick drop-in usage in nearly any web project. We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [source maps](https://developers.google.com/web/tools/chrome-devtools/javascript/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/), but not [jQuery](https://jquery.com/).
+This is the most basic form of Bootstrap: precompiled files for quick drop-in usage in nearly any web project. We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [source maps](https://developers.google.com/web/tools/chrome-devtools/javascript/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/) and [HammerJS](https://hammerjs.github.io/), but not [jQuery](https://jquery.com/).
 
 ## CSS files
 
index 55ced2f9c31823742ad00474d326af94049334fc..0ad7973010501ec6dc7381c21b1e9720a6fe10ba 100644 (file)
@@ -37,7 +37,7 @@ We use [jQuery's slim build](https://blog.jquery.com/2016/06/09/jquery-3-0-final
 
 Curious which components explicitly require jQuery, our JS, and Popper.js? Click the show components link below. If you're at all unsure about the general page structure, keep reading for an example page template.
 
-Our `bootstrap.bundle.js` and `bootstrap.bundle.min.js` include [Popper](https://popper.js.org/), but not [jQuery](https://jquery.com/). For more information about what's included in Bootstrap, please see our [contents]({{ site.baseurl }}/docs/{{ site.docs_version }}/getting-started/contents/#precompiled-bootstrap) section.
+Our `bootstrap.bundle.js` and `bootstrap.bundle.min.js` include [Popper](https://popper.js.org/) and [HammerJS](https://hammerjs.github.io/), but not [jQuery](https://jquery.com/). For more information about what's included in Bootstrap, please see our [contents]({{ site.baseurl }}/docs/{{ site.docs_version }}/getting-started/contents/#precompiled-bootstrap) section.
 
 <details>
 <summary class="text-primary mb-3">Show components requiring JavaScript</summary>