diff --git a/.bundlewatch.config.json b/.bundlewatch.config.json
index 074066390..da47da480 100644
--- a/.bundlewatch.config.json
+++ b/.bundlewatch.config.json
@@ -18,11 +18,11 @@
},
{
"path": "./dist/css/bootstrap-utilities.css",
- "maxSize": "7 kB"
+ "maxSize": "7.5 kB"
},
{
"path": "./dist/css/bootstrap-utilities.min.css",
- "maxSize": "6.25 kB"
+ "maxSize": "6.75 kB"
},
{
"path": "./dist/css/bootstrap.css",
diff --git a/.eslintrc.json b/.eslintrc.json
index ec6aa589c..d5a54d46b 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -43,15 +43,14 @@
],
"unicorn/consistent-function-scoping": "off",
"unicorn/explicit-length-check": "off",
- "unicorn/import-index": "off",
- "unicorn/no-fn-reference-in-iterator": "off",
+ "unicorn/no-array-callback-reference": "off",
"unicorn/no-for-loop": "off",
"unicorn/no-null": "off",
"unicorn/no-unused-properties": "error",
"unicorn/no-useless-undefined": "off",
- "unicorn/prefer-dataset": "off",
- "unicorn/prefer-node-append": "off",
- "unicorn/prefer-node-remove": "off",
+ "unicorn/prefer-dom-node-append": "off",
+ "unicorn/prefer-dom-node-dataset": "off",
+ "unicorn/prefer-dom-node-remove": "off",
"unicorn/prefer-optional-catch-binding": "off",
"unicorn/prefer-query-selector": "off",
"unicorn/prevent-abbreviations": "off"
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 0913b5b5b..32b30cdb3 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -101,12 +101,12 @@ Example:
Sometimes bugs reported to us are actually caused by bugs in the browser(s) themselves, not bugs in Bootstrap per se.
-| Vendor(s) | Browser(s) | Rendering engine | Bug reporting website(s) | Notes |
-| ------------- | ---------------------------- | ---------------- | --------------------------------------------------------------------- | -------------------------------------------------------- |
-| Mozilla | Firefox | Gecko | https://bugzilla.mozilla.org/enter_bug.cgi | "Core" is normally the right product option to choose. |
-| Apple | Safari | WebKit | https://bugs.webkit.org/enter_bug.cgi?product=WebKit | In Apple's bug reporter, choose "Safari" as the product. |
-| Google, Opera | Chrome, Chromium, Opera v15+ | Blink | https://bugs.chromium.org/p/chromium/issues/list | Click the "New issue" button. |
-| Microsoft | Edge | Blink | https://developer.microsoft.com/en-us/microsoft-edge/ | Go to "Help > Send Feedback" from the browser |
+| Vendor(s) | Browser(s) | Rendering engine | Bug reporting website(s) | Notes |
+| ------------- | ---------------------------- | ---------------- | ------------------------------------------------------ | -------------------------------------------------------- |
+| Mozilla | Firefox | Gecko | https://bugzilla.mozilla.org/enter_bug.cgi | "Core" is normally the right product option to choose. |
+| Apple | Safari | WebKit | https://bugs.webkit.org/enter_bug.cgi?product=WebKit | In Apple's bug reporter, choose "Safari" as the product. |
+| Google, Opera | Chrome, Chromium, Opera v15+ | Blink | https://bugs.chromium.org/p/chromium/issues/list | Click the "New issue" button. |
+| Microsoft | Edge | Blink | https://developer.microsoft.com/en-us/microsoft-edge/ | Go to "Help > Send Feedback" from the browser |
## Feature requests
@@ -134,9 +134,9 @@ Please adhere to the [coding guidelines](#code-guidelines) used throughout the
project (indentation, accurate comments, etc.) and any other requirements
(such as test coverage).
-**Do not edit `bootstrap.css`, or `bootstrap.js`
-directly!** Those files are automatically generated. You should edit the
-source files in [`/bootstrap/scss/`](https://github.com/twbs/bootstrap/tree/main/scss)
+**Do not edit `bootstrap.css` or `bootstrap.js`, and do not commit
+any dist files (`dist/` or `js/dist`).** Those files are automatically generated by our build tools. You should
+edit the source files in [`/bootstrap/scss/`](https://github.com/twbs/bootstrap/tree/main/scss)
and/or [`/bootstrap/js/src/`](https://github.com/twbs/bootstrap/tree/main/js/src) instead.
Similarly, when contributing to Bootstrap's documentation, you should edit the
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 31976021e..29135b400 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -15,3 +15,10 @@ updates:
- v5
versioning-strategy: increase
rebase-strategy: disabled
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: weekly
+ day: tuesday
+ time: "12:00"
+ timezone: Europe/Athens
diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml
index ae1367c89..e2b70e03e 100644
--- a/.github/release-drafter.yml
+++ b/.github/release-drafter.yml
@@ -4,6 +4,9 @@ prerelease: true
exclude-labels:
- 'skip-changelog'
categories:
+ - title: '❗ Breaking Changes'
+ labels:
+ - 'breaking-change'
- title: '🚀 Features'
labels:
- 'new-feature'
@@ -14,6 +17,9 @@ categories:
- 'fix'
- 'bugfix'
- 'bug'
+ - title: '⚡ Performance Improvements'
+ labels:
+ - 'performance'
- title: '🎨 CSS'
labels:
- 'css'
@@ -26,11 +32,19 @@ categories:
- title: '🌎 Accessibility'
labels:
- 'accessibility'
+ - title: '🔧 Utility API'
+ labels:
+ - 'utility API'
+ - 'utilities'
+ - title: '🏭 Tests'
+ labels:
+ - 'tests'
- title: '🧰 Misc'
labels:
- 'build'
- 'meta'
- 'chore'
+ - 'CI'
- title: '📦 Dependencies'
labels:
- 'dependencies'
diff --git a/.github/workflows/browserstack.yml b/.github/workflows/browserstack.yml
index 0b4cfbdaa..a12bffcf7 100644
--- a/.github/workflows/browserstack.yml
+++ b/.github/workflows/browserstack.yml
@@ -4,21 +4,21 @@ on:
push:
env:
- CI: true
FORCE_COLOR: 2
- NODE: 14.x
+ NODE: 14
jobs:
browserstack:
runs-on: ubuntu-latest
if: github.repository == 'twbs/bootstrap' && (!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]'))
+ timeout-minutes: 30
steps:
- name: Clone repository
uses: actions/checkout@v2
- name: Set up Node.js
- uses: actions/setup-node@v1
+ uses: actions/setup-node@v2
with:
node-version: "${{ env.NODE }}"
@@ -28,8 +28,8 @@ jobs:
path: ~/.npm
key: ${{ runner.os }}-node-v${{ env.NODE }}-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}
restore-keys: |
- ${{ runner.OS }}-node-v${{ env.NODE }}-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}
- ${{ runner.OS }}-node-v${{ env.NODE }}-
+ ${{ runner.os }}-node-v${{ env.NODE }}-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}
+ ${{ runner.os }}-node-v${{ env.NODE }}-
- name: Install npm dependencies
run: npm ci
diff --git a/.github/workflows/bundlewatch.yml b/.github/workflows/bundlewatch.yml
index 966dfbf72..b2bd5eed5 100644
--- a/.github/workflows/bundlewatch.yml
+++ b/.github/workflows/bundlewatch.yml
@@ -7,9 +7,8 @@ on:
pull_request:
env:
- CI: true
FORCE_COLOR: 2
- NODE: 14.x
+ NODE: 14
jobs:
bundlewatch:
@@ -20,7 +19,7 @@ jobs:
uses: actions/checkout@v2
- name: Set up Node.js
- uses: actions/setup-node@v1
+ uses: actions/setup-node@v2
with:
node-version: "${{ env.NODE }}"
@@ -30,8 +29,8 @@ jobs:
path: ~/.npm
key: ${{ runner.os }}-node-v${{ env.NODE }}-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}
restore-keys: |
- ${{ runner.OS }}-node-v${{ env.NODE }}-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}
- ${{ runner.OS }}-node-v${{ env.NODE }}-
+ ${{ runner.os }}-node-v${{ env.NODE }}-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}
+ ${{ runner.os }}-node-v${{ env.NODE }}-
- name: Install npm dependencies
run: npm ci
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index f9dccfce0..469a5a4fc 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -22,15 +22,6 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
- with:
- # We must fetch at least the immediate parents so that if this is
- # a pull request then we can checkout the head.
- fetch-depth: 2
-
- # If this run was triggered by a pull request event, then checkout
- # the head of the pull request instead of the merge commit.
- - run: git checkout HEAD^2
- if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
diff --git a/.github/workflows/css.yml b/.github/workflows/css.yml
index a75f018c5..a28059d79 100644
--- a/.github/workflows/css.yml
+++ b/.github/workflows/css.yml
@@ -7,9 +7,8 @@ on:
pull_request:
env:
- CI: true
FORCE_COLOR: 2
- NODE: 14.x
+ NODE: 14
jobs:
css:
@@ -20,7 +19,7 @@ jobs:
uses: actions/checkout@v2
- name: Set up Node.js
- uses: actions/setup-node@v1
+ uses: actions/setup-node@v2
with:
node-version: "${{ env.NODE }}"
@@ -30,8 +29,8 @@ jobs:
path: ~/.npm
key: ${{ runner.os }}-node-v${{ env.NODE }}-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}
restore-keys: |
- ${{ runner.OS }}-node-v${{ env.NODE }}-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}
- ${{ runner.OS }}-node-v${{ env.NODE }}-
+ ${{ runner.os }}-node-v${{ env.NODE }}-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}
+ ${{ runner.os }}-node-v${{ env.NODE }}-
- name: Install npm dependencies
run: npm ci
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 58b55fc7c..150e4d16c 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -7,9 +7,8 @@ on:
pull_request:
env:
- CI: true
FORCE_COLOR: 2
- NODE: 14.x
+ NODE: 14
jobs:
docs:
@@ -20,7 +19,7 @@ jobs:
uses: actions/checkout@v2
- name: Set up Node.js
- uses: actions/setup-node@v1
+ uses: actions/setup-node@v2
with:
node-version: "${{ env.NODE }}"
@@ -32,8 +31,8 @@ jobs:
path: ~/.npm
key: ${{ runner.os }}-node-v${{ env.NODE }}-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}
restore-keys: |
- ${{ runner.OS }}-node-v${{ env.NODE }}-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}
- ${{ runner.OS }}-node-v${{ env.NODE }}-
+ ${{ runner.os }}-node-v${{ env.NODE }}-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}
+ ${{ runner.os }}-node-v${{ env.NODE }}-
- name: Install npm dependencies
run: npm ci
diff --git a/.github/workflows/js.yml b/.github/workflows/js.yml
index 1ec0d6909..c56a2dfdd 100644
--- a/.github/workflows/js.yml
+++ b/.github/workflows/js.yml
@@ -7,7 +7,6 @@ on:
pull_request:
env:
- CI: true
FORCE_COLOR: 2
jobs:
@@ -25,7 +24,7 @@ jobs:
uses: actions/checkout@v2
- name: Set up Node.js
- uses: actions/setup-node@v1
+ uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
@@ -35,8 +34,8 @@ jobs:
path: ~/.npm
key: ${{ runner.os }}-node-v${{ matrix.node }}-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}}
restore-keys: |
- ${{ runner.OS }}-node-v${{ matrix.node }}-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}
- ${{ runner.OS }}-node-v${{ matrix.node }}-
+ ${{ runner.os }}-node-v${{ matrix.node }}-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}
+ ${{ runner.os }}-node-v${{ matrix.node }}-
- name: Install npm dependencies
run: npm ci
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index e850a1f1b..369aaced3 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -7,9 +7,8 @@ on:
pull_request:
env:
- CI: true
FORCE_COLOR: 2
- NODE: 14.x
+ NODE: 14
jobs:
lint:
@@ -20,7 +19,7 @@ jobs:
uses: actions/checkout@v2
- name: Set up Node.js
- uses: actions/setup-node@v1
+ uses: actions/setup-node@v2
with:
node-version: "${{ env.NODE }}"
@@ -30,8 +29,8 @@ jobs:
path: ~/.npm
key: ${{ runner.os }}-node-v${{ env.NODE }}-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}
restore-keys: |
- ${{ runner.OS }}-node-v${{ env.NODE }}-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}
- ${{ runner.OS }}-node-v${{ env.NODE }}-
+ ${{ runner.os }}-node-v${{ env.NODE }}-${{ hashFiles('package.json') }}-${{ hashFiles('package-lock.json') }}
+ ${{ runner.os }}-node-v${{ env.NODE }}-
- name: Install npm dependencies
run: npm ci
diff --git a/.github/workflows/node-sass.yml b/.github/workflows/node-sass.yml
index 2db55f0ff..ee64b2152 100644
--- a/.github/workflows/node-sass.yml
+++ b/.github/workflows/node-sass.yml
@@ -7,9 +7,8 @@ on:
pull_request:
env:
- CI: true
FORCE_COLOR: 2
- NODE: 14.x
+ NODE: 14
jobs:
css:
@@ -20,7 +19,7 @@ jobs:
uses: actions/checkout@v2
- name: Set up Node.js
- uses: actions/setup-node@v1
+ uses: actions/setup-node@v2
with:
node-version: "${{ env.NODE }}"
diff --git a/.gitignore b/.gitignore
index 6208c57d7..0a0f88d15 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,19 +1,8 @@
# Ignore docs files
/_gh_pages/
-# This is the old Jekyll docs dist folder;
-# keeping it here so that when we switch branches it doesn't show up
-/site/docs/**/dist/
-# Jekyll's cache folder; keeping it for the same reason as above
-/site/.jekyll-cache/
# Hugo resources folder
/resources/
-# Ignore ruby/bundler files;
-# keeping them here so that when we switch branches they don't show up
-/.bundle/
-/vendor/
-/.ruby-version
-
# Numerous always-ignore extensions
*.diff
*.err
diff --git a/.stylelintrc b/.stylelintrc
index 1c9ee1811..c068d30b5 100644
--- a/.stylelintrc
+++ b/.stylelintrc
@@ -3,6 +3,10 @@
"stylelint-config-twbs-bootstrap/scss"
],
"rules": {
+ "declaration-property-value-disallowed-list": {
+ "border": "none",
+ "outline": "none"
+ },
"function-disallowed-list": [
"calc",
"lighten",
diff --git a/LICENSE b/LICENSE
index 173a9ebbb..72dda234e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,7 @@
The MIT License (MIT)
-Copyright (c) 2011-2020 Twitter, Inc.
-Copyright (c) 2011-2020 The Bootstrap Authors
+Copyright (c) 2011-2021 Twitter, Inc.
+Copyright (c) 2011-2021 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 8a470260a..f41498691 100644
--- a/README.md
+++ b/README.md
@@ -236,4 +236,4 @@ Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com
## Copyright and license
-Code and documentation copyright 2011–2020 the [Bootstrap Authors](https://github.com/twbs/bootstrap/graphs/contributors) and [Twitter, Inc.](https://twitter.com) Code released under the [MIT License](https://github.com/twbs/bootstrap/blob/main/LICENSE). Docs released under [Creative Commons](https://creativecommons.org/licenses/by/3.0/).
+Code and documentation copyright 2011–2021 the [Bootstrap Authors](https://github.com/twbs/bootstrap/graphs/contributors) and [Twitter, Inc.](https://twitter.com) Code released under the [MIT License](https://github.com/twbs/bootstrap/blob/main/LICENSE). Docs released under [Creative Commons](https://creativecommons.org/licenses/by/3.0/).
diff --git a/build/build-plugins.js b/build/build-plugins.js
index ea7477390..d1930b855 100644
--- a/build/build-plugins.js
+++ b/build/build-plugins.js
@@ -2,8 +2,8 @@
/*!
* Script to build our plugins to use them separately.
- * Copyright 2020 The Bootstrap Authors
- * Copyright 2020 Twitter, Inc.
+ * Copyright 2020-2021 The Bootstrap Authors
+ * Copyright 2020-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
@@ -29,6 +29,7 @@ const bsPlugins = {
Manipulator: path.resolve(__dirname, '../js/src/dom/manipulator.js'),
SelectorEngine: path.resolve(__dirname, '../js/src/dom/selector-engine.js'),
Alert: path.resolve(__dirname, '../js/src/alert.js'),
+ Base: path.resolve(__dirname, '../js/src/base-component.js'),
Button: path.resolve(__dirname, '../js/src/button.js'),
Carousel: path.resolve(__dirname, '../js/src/carousel.js'),
Collapse: path.resolve(__dirname, '../js/src/collapse.js'),
@@ -44,11 +45,13 @@ const bsPlugins = {
const defaultPluginConfig = {
external: [
bsPlugins.Data,
+ bsPlugins.Base,
bsPlugins.EventHandler,
bsPlugins.SelectorEngine
],
globals: {
[bsPlugins.Data]: 'Data',
+ [bsPlugins.Base]: 'Base',
[bsPlugins.EventHandler]: 'EventHandler',
[bsPlugins.SelectorEngine]: 'SelectorEngine'
}
@@ -73,6 +76,7 @@ const getConfigByPluginKey = pluginKey => {
}
if (
+ pluginKey === 'Base' ||
pluginKey === 'Button' ||
pluginKey === 'Carousel' ||
pluginKey === 'Collapse' ||
@@ -112,11 +116,13 @@ const getConfigByPluginKey = pluginKey => {
return {
external: [
bsPlugins.Data,
+ bsPlugins.Base,
bsPlugins.EventHandler,
bsPlugins.Manipulator
],
globals: {
[bsPlugins.Data]: 'Data',
+ [bsPlugins.Base]: 'Base',
[bsPlugins.EventHandler]: 'EventHandler',
[bsPlugins.Manipulator]: 'Manipulator'
}
diff --git a/build/change-version.js b/build/change-version.js
index b8a640fa8..8086ed774 100644
--- a/build/change-version.js
+++ b/build/change-version.js
@@ -2,8 +2,8 @@
/*!
* Script to update version number references in the project.
- * Copyright 2017-2020 The Bootstrap Authors
- * Copyright 2017-2020 Twitter, Inc.
+ * Copyright 2017-2021 The Bootstrap Authors
+ * Copyright 2017-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
diff --git a/build/generate-sri.js b/build/generate-sri.js
index 8a2e59360..221873b8f 100644
--- a/build/generate-sri.js
+++ b/build/generate-sri.js
@@ -5,8 +5,8 @@
* Remember to use the same vendor files as the CDN ones,
* otherwise the hashes won't match!
*
- * Copyright 2017-2020 The Bootstrap Authors
- * Copyright 2017-2020 Twitter, Inc.
+ * Copyright 2017-2021 The Bootstrap Authors
+ * Copyright 2017-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
diff --git a/build/vnu-jar.js b/build/vnu-jar.js
index 90c1a12ab..c23b94d60 100644
--- a/build/vnu-jar.js
+++ b/build/vnu-jar.js
@@ -2,8 +2,8 @@
/*!
* Script to run vnu-jar if Java is available.
- * Copyright 2017-2020 The Bootstrap Authors
- * Copyright 2017-2020 Twitter, Inc.
+ * Copyright 2017-2021 The Bootstrap Authors
+ * Copyright 2017-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
diff --git a/build/zip-examples.js b/build/zip-examples.js
index bf50a0af6..b2f156502 100644
--- a/build/zip-examples.js
+++ b/build/zip-examples.js
@@ -3,7 +3,7 @@
/*!
* Script to create the built examples zip archive;
* requires the `zip` command to be present!
- * Copyright 2020 The Bootstrap Authors
+ * Copyright 2020-2021 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
@@ -12,36 +12,67 @@
const path = require('path')
const sh = require('shelljs')
-const { version, version_short: versionShort } = require('../package.json')
+const pkg = require('../package.json')
-const folderName = `bootstrap-${version}-examples`
+const versionShort = pkg.config.version_short
+const distFolder = `bootstrap-${pkg.version}-examples`
+const rootDocsDir = '_gh_pages'
+const docsDir = `${rootDocsDir}/docs/${versionShort}/`
+
+// these are the files we need in the examples
+const cssFiles = [
+ 'bootstrap.min.css',
+ 'bootstrap.min.css.map',
+ 'bootstrap.rtl.min.css',
+ 'bootstrap.rtl.min.css.map'
+]
+const jsFiles = [
+ 'bootstrap.bundle.min.js',
+ 'bootstrap.bundle.min.js.map'
+]
+const imgFiles = [
+ 'bootstrap-logo.svg',
+ 'bootstrap-logo-white.svg'
+]
sh.config.fatal = true
-if (!sh.test('-d', '_gh_pages')) {
- throw new Error('The "_gh_pages" folder does not exist, did you forget building the docs?')
+if (!sh.test('-d', rootDocsDir)) {
+ throw new Error(`The "${rootDocsDir}" folder does not exist, did you forget building the docs?`)
}
// switch to the root dir
sh.cd(path.join(__dirname, '..'))
-// remove any previously created folder with the same name
-sh.rm('-rf', folderName)
-// create any folders so that `cp` works
-sh.mkdir('-p', folderName)
-sh.mkdir('-p', `${folderName}/assets/brand/`)
+// remove any previously created folder/zip with the same name
+sh.rm('-rf', [distFolder, `${distFolder}.zip`])
-sh.cp('-Rf', `_gh_pages/docs/${versionShort}/examples/*`, folderName)
-sh.cp('-Rf', `_gh_pages/docs/${versionShort}/dist/`, `${folderName}/assets/`)
-// also copy the two brand images we use in the examples
-sh.cp('-f', [
- `_gh_pages/docs/${versionShort}/assets/brand/bootstrap-logo.svg`,
- `_gh_pages/docs/${versionShort}/assets/brand/bootstrap-logo-white.svg`
-], `${folderName}/assets/brand/`)
-sh.rm(`${folderName}/index.html`)
+// create any folders so that `cp` works
+sh.mkdir('-p', [
+ distFolder,
+ `${distFolder}/assets/brand/`,
+ `${distFolder}/assets/dist/css/`,
+ `${distFolder}/assets/dist/js/`
+])
+
+sh.cp('-Rf', `${docsDir}/examples/*`, distFolder)
+
+cssFiles.forEach(file => {
+ sh.cp('-f', `${docsDir}/dist/css/${file}`, `${distFolder}/assets/dist/css/`)
+})
+
+jsFiles.forEach(file => {
+ sh.cp('-f', `${docsDir}/dist/js/${file}`, `${distFolder}/assets/dist/js/`)
+})
+
+imgFiles.forEach(file => {
+ sh.cp('-f', `${docsDir}/assets/brand/${file}`, `${distFolder}/assets/brand/`)
+})
+
+sh.rm(`${distFolder}/index.html`)
// get all examples' HTML files
-sh.find(`${folderName}/**/*.html`).forEach(file => {
+sh.find(`${distFolder}/**/*.html`).forEach(file => {
const fileContents = sh.cat(file)
.toString()
.replace(new RegExp(`"/docs/${versionShort}/`, 'g'), '"../')
@@ -53,7 +84,7 @@ sh.find(`${folderName}/**/*.html`).forEach(file => {
})
// create the zip file
-sh.exec(`zip -r9 "${folderName}.zip" "${folderName}"`, { fatal: true })
+sh.exec(`zip -r9 "${distFolder}.zip" "${distFolder}"`)
// remove the folder we created
-sh.rm('-rf', folderName)
+sh.rm('-rf', distFolder)
diff --git a/config.yml b/config.yml
index 616993e15..a54d5bc6c 100644
--- a/config.yml
+++ b/config.yml
@@ -75,5 +75,5 @@ params:
js_hash: "sha384-pQQkAEnwaBkjpqZ8RU1fF1AKtTcHJwFl3pblpTlHXybJjHpMYo79HY3hIi4NKxyj"
js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js"
js_bundle_hash: "sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW"
- popper: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"
- popper_hash: "sha384-q2kxQ16AaE6UbzuKqyBE9/u/KzioAlnx2maXQHiDX9d4/zp8Ok3f+M7DPm+Ib6IU"
+ popper: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.6.0/dist/umd/popper.min.js"
+ popper_hash: "sha384-KsvD1yqQ1/1+IA7gi3P0tyJcT3vR+NdBTt13hSJ2lnve8agRGXTTyNaBYmCR/Nwi"
diff --git a/js/src/alert.js b/js/src/alert.js
index e498d0324..96eda4c5f 100644
--- a/js/src/alert.js
+++ b/js/src/alert.js
@@ -7,7 +7,6 @@
import {
defineJQueryPlugin,
- TRANSITION_END,
emulateTransitionEnd,
getElementFromSelector,
getTransitionDurationFromElement
@@ -33,9 +32,9 @@ const EVENT_CLOSE = `close${EVENT_KEY}`
const EVENT_CLOSED = `closed${EVENT_KEY}`
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
-const CLASSNAME_ALERT = 'alert'
-const CLASSNAME_FADE = 'fade'
-const CLASSNAME_SHOW = 'show'
+const CLASS_NAME_ALERT = 'alert'
+const CLASS_NAME_FADE = 'fade'
+const CLASS_NAME_SHOW = 'show'
/**
* ------------------------------------------------------------------------
@@ -66,7 +65,7 @@ class Alert extends BaseComponent {
// Private
_getRootElement(element) {
- return getElementFromSelector(element) || element.closest(`.${CLASSNAME_ALERT}`)
+ return getElementFromSelector(element) || element.closest(`.${CLASS_NAME_ALERT}`)
}
_triggerCloseEvent(element) {
@@ -74,16 +73,16 @@ class Alert extends BaseComponent {
}
_removeElement(element) {
- element.classList.remove(CLASSNAME_SHOW)
+ element.classList.remove(CLASS_NAME_SHOW)
- if (!element.classList.contains(CLASSNAME_FADE)) {
+ if (!element.classList.contains(CLASS_NAME_FADE)) {
this._destroyElement(element)
return
}
const transitionDuration = getTransitionDurationFromElement(element)
- EventHandler.one(element, TRANSITION_END, () => this._destroyElement(element))
+ EventHandler.one(element, 'transitionend', () => this._destroyElement(element))
emulateTransitionEnd(element, transitionDuration)
}
diff --git a/js/src/carousel.js b/js/src/carousel.js
index 299aadeda..06a391419 100644
--- a/js/src/carousel.js
+++ b/js/src/carousel.js
@@ -7,11 +7,11 @@
import {
defineJQueryPlugin,
- TRANSITION_END,
emulateTransitionEnd,
getElementFromSelector,
getTransitionDurationFromElement,
isVisible,
+ isRTL,
reflow,
triggerTransitionEnd,
typeCheckConfig
@@ -93,10 +93,8 @@ const SELECTOR_INDICATORS = '.carousel-indicators'
const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'
const SELECTOR_DATA_RIDE = '[data-bs-ride="carousel"]'
-const PointerType = {
- TOUCH: 'touch',
- PEN: 'pen'
-}
+const POINTER_TYPE_TOUCH = 'touch'
+const POINTER_TYPE_PEN = 'pen'
/**
* ------------------------------------------------------------------------
@@ -253,12 +251,20 @@ class Carousel extends BaseComponent {
// swipe left
if (direction > 0) {
- this.prev()
+ if (isRTL) {
+ this.next()
+ } else {
+ this.prev()
+ }
}
// swipe right
if (direction < 0) {
- this.next()
+ if (isRTL) {
+ this.prev()
+ } else {
+ this.next()
+ }
}
}
@@ -279,7 +285,7 @@ class Carousel extends BaseComponent {
_addTouchEventListeners() {
const start = event => {
- if (this._pointerEvent && PointerType[event.pointerType.toUpperCase()]) {
+ if (this._pointerEvent && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)) {
this.touchStartX = event.clientX
} else if (!this._pointerEvent) {
this.touchStartX = event.touches[0].clientX
@@ -296,7 +302,7 @@ class Carousel extends BaseComponent {
}
const end = event => {
- if (this._pointerEvent && PointerType[event.pointerType.toUpperCase()]) {
+ if (this._pointerEvent && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)) {
this.touchDeltaX = event.clientX - this.touchStartX
}
@@ -340,16 +346,20 @@ class Carousel extends BaseComponent {
return
}
- switch (event.key) {
- case ARROW_LEFT_KEY:
- event.preventDefault()
- this.prev()
- break
- case ARROW_RIGHT_KEY:
- event.preventDefault()
+ if (event.key === ARROW_LEFT_KEY) {
+ event.preventDefault()
+ if (isRTL) {
this.next()
- break
- default:
+ } else {
+ this.prev()
+ }
+ } else if (event.key === ARROW_RIGHT_KEY) {
+ event.preventDefault()
+ if (isRTL) {
+ this.prev()
+ } else {
+ this.next()
+ }
}
}
@@ -436,19 +446,9 @@ class Carousel extends BaseComponent {
const nextElementIndex = this._getItemIndex(nextElement)
const isCycling = Boolean(this._interval)
- let directionalClassName
- let orderClassName
- let eventDirectionName
-
- if (direction === DIRECTION_NEXT) {
- directionalClassName = CLASS_NAME_START
- orderClassName = CLASS_NAME_NEXT
- eventDirectionName = DIRECTION_LEFT
- } else {
- directionalClassName = CLASS_NAME_END
- orderClassName = CLASS_NAME_PREV
- eventDirectionName = DIRECTION_RIGHT
- }
+ const directionalClassName = direction === DIRECTION_NEXT ? CLASS_NAME_START : CLASS_NAME_END
+ const orderClassName = direction === DIRECTION_NEXT ? CLASS_NAME_NEXT : CLASS_NAME_PREV
+ const eventDirectionName = direction === DIRECTION_NEXT ? DIRECTION_LEFT : DIRECTION_RIGHT
if (nextElement && nextElement.classList.contains(CLASS_NAME_ACTIVE)) {
this._isSliding = false
@@ -484,7 +484,7 @@ class Carousel extends BaseComponent {
const transitionDuration = getTransitionDurationFromElement(activeElement)
- EventHandler.one(activeElement, TRANSITION_END, () => {
+ EventHandler.one(activeElement, 'transitionend', () => {
nextElement.classList.remove(directionalClassName, orderClassName)
nextElement.classList.add(CLASS_NAME_ACTIVE)
diff --git a/js/src/collapse.js b/js/src/collapse.js
index 590615b72..0d3eeed54 100644
--- a/js/src/collapse.js
+++ b/js/src/collapse.js
@@ -7,7 +7,6 @@
import {
defineJQueryPlugin,
- TRANSITION_END,
emulateTransitionEnd,
getSelectorFromElement,
getElementFromSelector,
@@ -203,7 +202,7 @@ class Collapse extends BaseComponent {
const scrollSize = `scroll${capitalizedDimension}`
const transitionDuration = getTransitionDurationFromElement(this._element)
- EventHandler.one(this._element, TRANSITION_END, complete)
+ EventHandler.one(this._element, 'transitionend', complete)
emulateTransitionEnd(this._element, transitionDuration)
this._element.style[dimension] = `${this._element[scrollSize]}px`
@@ -253,7 +252,7 @@ class Collapse extends BaseComponent {
this._element.style[dimension] = ''
const transitionDuration = getTransitionDurationFromElement(this._element)
- EventHandler.one(this._element, TRANSITION_END, complete)
+ EventHandler.one(this._element, 'transitionend', complete)
emulateTransitionEnd(this._element, transitionDuration)
}
@@ -372,7 +371,7 @@ class Collapse extends BaseComponent {
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
// preventDefault only for elements (which change the URL) not inside the collapsible element
- if (event.target.tagName === 'A') {
+ if (event.target.tagName === 'A' || (event.delegateTarget && event.delegateTarget.tagName === 'A')) {
event.preventDefault()
}
diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js
index 2ee13bafc..e3c685530 100644
--- a/js/src/dom/event-handler.js
+++ b/js/src/dom/event-handler.js
@@ -112,6 +112,7 @@ function bootstrapDelegationHandler(element, selector, fn) {
event.delegateTarget = target
if (handler.oneOff) {
+ // eslint-disable-next-line unicorn/consistent-destructuring
EventHandler.off(element, event.type, fn)
}
diff --git a/js/src/dom/selector-engine.js b/js/src/dom/selector-engine.js
index 727df7518..28287388e 100644
--- a/js/src/dom/selector-engine.js
+++ b/js/src/dom/selector-engine.js
@@ -23,9 +23,8 @@ const SelectorEngine = {
},
children(element, selector) {
- const children = [].concat(...element.children)
-
- return children.filter(child => child.matches(selector))
+ return [].concat(...element.children)
+ .filter(child => child.matches(selector))
},
parents(element, selector) {
diff --git a/js/src/dropdown.js b/js/src/dropdown.js
index 4720ed1ab..bada537c9 100644
--- a/js/src/dropdown.js
+++ b/js/src/dropdown.js
@@ -84,7 +84,7 @@ const DefaultType = {
offset: '(number|string|function)',
flip: 'boolean',
boundary: '(string|element)',
- reference: '(string|element)',
+ reference: '(string|element|object)',
display: 'string',
popperConfig: '(null|object)'
}
@@ -172,6 +172,8 @@ class Dropdown extends BaseComponent {
if (typeof this._config.reference.jquery !== 'undefined') {
referenceElement = this._config.reference[0]
}
+ } else if (typeof this._config.reference === 'object') {
+ referenceElement = this._config.reference
}
this._popper = Popper.createPopper(referenceElement, this._menu, this._getPopperConfig())
@@ -257,6 +259,13 @@ class Dropdown extends BaseComponent {
typeCheckConfig(NAME, config, this.constructor.DefaultType)
+ if (typeof config.reference === 'object' && !isElement(config.reference) &&
+ typeof config.reference.getBoundingClientRect !== 'function'
+ ) {
+ // Popper virtual elements require a getBoundingClientRect method
+ throw new TypeError(`${NAME.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`)
+ }
+
return config
}
@@ -298,6 +307,12 @@ class Dropdown extends BaseComponent {
altBoundary: this._config.flip,
rootBoundary: this._config.boundary
}
+ },
+ {
+ name: 'flip',
+ options: {
+ fallbackPlacements: ['top', 'right', 'bottom', 'left']
+ }
}]
}
diff --git a/js/src/modal.js b/js/src/modal.js
index 20fb215c6..fe1b5a4c6 100644
--- a/js/src/modal.js
+++ b/js/src/modal.js
@@ -7,7 +7,6 @@
import {
defineJQueryPlugin,
- TRANSITION_END,
emulateTransitionEnd,
getElementFromSelector,
getTransitionDurationFromElement,
@@ -184,7 +183,7 @@ class Modal extends BaseComponent {
if (transition) {
const transitionDuration = getTransitionDurationFromElement(this._element)
- EventHandler.one(this._element, TRANSITION_END, event => this._hideModal(event))
+ EventHandler.one(this._element, 'transitionend', event => this._hideModal(event))
emulateTransitionEnd(this._element, transitionDuration)
} else {
this._hideModal()
@@ -272,7 +271,7 @@ class Modal extends BaseComponent {
if (transition) {
const transitionDuration = getTransitionDurationFromElement(this._dialog)
- EventHandler.one(this._dialog, TRANSITION_END, transitionComplete)
+ EventHandler.one(this._dialog, 'transitionend', transitionComplete)
emulateTransitionEnd(this._dialog, transitionDuration)
} else {
transitionComplete()
@@ -377,7 +376,7 @@ class Modal extends BaseComponent {
const backdropTransitionDuration = getTransitionDurationFromElement(this._backdrop)
- EventHandler.one(this._backdrop, TRANSITION_END, callback)
+ EventHandler.one(this._backdrop, 'transitionend', callback)
emulateTransitionEnd(this._backdrop, backdropTransitionDuration)
} else if (!this._isShown && this._backdrop) {
this._backdrop.classList.remove(CLASS_NAME_SHOW)
@@ -389,7 +388,7 @@ class Modal extends BaseComponent {
if (this._element.classList.contains(CLASS_NAME_FADE)) {
const backdropTransitionDuration = getTransitionDurationFromElement(this._backdrop)
- EventHandler.one(this._backdrop, TRANSITION_END, callbackRemove)
+ EventHandler.one(this._backdrop, 'transitionend', callbackRemove)
emulateTransitionEnd(this._backdrop, backdropTransitionDuration)
} else {
callbackRemove()
@@ -413,11 +412,11 @@ class Modal extends BaseComponent {
this._element.classList.add(CLASS_NAME_STATIC)
const modalTransitionDuration = getTransitionDurationFromElement(this._dialog)
- EventHandler.off(this._element, TRANSITION_END)
- EventHandler.one(this._element, TRANSITION_END, () => {
+ EventHandler.off(this._element, 'transitionend')
+ EventHandler.one(this._element, 'transitionend', () => {
this._element.classList.remove(CLASS_NAME_STATIC)
if (!isModalOverflowing) {
- EventHandler.one(this._element, TRANSITION_END, () => {
+ EventHandler.one(this._element, 'transitionend', () => {
this._element.style.overflowY = ''
})
emulateTransitionEnd(this._element, modalTransitionDuration)
@@ -591,7 +590,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (
data = new Modal(target, config)
}
- data.show(this)
+ data.toggle(this)
})
/**
diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js
index a8770ef08..baea49443 100644
--- a/js/src/scrollspy.js
+++ b/js/src/scrollspy.js
@@ -76,7 +76,7 @@ class ScrollSpy extends BaseComponent {
this._activeTarget = null
this._scrollHeight = 0
- EventHandler.on(this._scrollElement, EVENT_SCROLL, event => this._process(event))
+ EventHandler.on(this._scrollElement, EVENT_SCROLL, () => this._process())
this.refresh()
this._process()
diff --git a/js/src/tab.js b/js/src/tab.js
index 3a48986c4..f1b17ac79 100644
--- a/js/src/tab.js
+++ b/js/src/tab.js
@@ -7,7 +7,6 @@
import {
defineJQueryPlugin,
- TRANSITION_END,
emulateTransitionEnd,
getElementFromSelector,
getTransitionDurationFromElement,
@@ -82,13 +81,11 @@ class Tab extends BaseComponent {
previous = previous[previous.length - 1]
}
- let hideEvent = null
-
- if (previous) {
- hideEvent = EventHandler.trigger(previous, EVENT_HIDE, {
+ const hideEvent = previous ?
+ EventHandler.trigger(previous, EVENT_HIDE, {
relatedTarget: this._element
- })
- }
+ }) :
+ null
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {
relatedTarget: previous
@@ -132,7 +129,7 @@ class Tab extends BaseComponent {
const transitionDuration = getTransitionDurationFromElement(active)
active.classList.remove(CLASS_NAME_SHOW)
- EventHandler.one(active, TRANSITION_END, complete)
+ EventHandler.one(active, 'transitionend', complete)
emulateTransitionEnd(active, transitionDuration)
} else {
complete()
diff --git a/js/src/toast.js b/js/src/toast.js
index bda152af6..e277cdc06 100644
--- a/js/src/toast.js
+++ b/js/src/toast.js
@@ -7,7 +7,6 @@
import {
defineJQueryPlugin,
- TRANSITION_END,
emulateTransitionEnd,
getTransitionDurationFromElement,
reflow,
@@ -116,7 +115,7 @@ class Toast extends BaseComponent {
if (this._config.animation) {
const transitionDuration = getTransitionDurationFromElement(this._element)
- EventHandler.one(this._element, TRANSITION_END, complete)
+ EventHandler.one(this._element, 'transitionend', complete)
emulateTransitionEnd(this._element, transitionDuration)
} else {
complete()
@@ -143,7 +142,7 @@ class Toast extends BaseComponent {
if (this._config.animation) {
const transitionDuration = getTransitionDurationFromElement(this._element)
- EventHandler.one(this._element, TRANSITION_END, complete)
+ EventHandler.one(this._element, 'transitionend', complete)
emulateTransitionEnd(this._element, transitionDuration)
} else {
complete()
diff --git a/js/src/tooltip.js b/js/src/tooltip.js
index df0f0c19c..63a30cf2f 100644
--- a/js/src/tooltip.js
+++ b/js/src/tooltip.js
@@ -9,7 +9,6 @@ import * as Popper from '@popperjs/core'
import {
defineJQueryPlugin,
- TRANSITION_END,
emulateTransitionEnd,
findShadowRoot,
getTransitionDurationFromElement,
@@ -52,7 +51,7 @@ const DefaultType = {
selector: '(string|boolean)',
placement: '(string|function)',
container: '(string|element|boolean)',
- fallbackPlacements: '(null|array)',
+ fallbackPlacements: 'array',
boundary: '(string|element)',
customClass: '(string|function)',
sanitize: 'boolean',
@@ -82,7 +81,7 @@ const Default = {
selector: false,
placement: 'top',
container: false,
- fallbackPlacements: null,
+ fallbackPlacements: ['top', 'right', 'bottom', 'left'],
boundary: 'clippingParents',
customClass: '',
sanitize: true,
@@ -223,7 +222,7 @@ class Tooltip extends BaseComponent {
EventHandler.off(this._element, this.constructor.EVENT_KEY)
EventHandler.off(this._element.closest(`.${CLASS_NAME_MODAL}`), 'hide.bs.modal', this._hideModalHandler)
- if (this.tip) {
+ if (this.tip && this.tip.parentNode) {
this.tip.parentNode.removeChild(this.tip)
}
@@ -317,7 +316,7 @@ class Tooltip extends BaseComponent {
if (this.tip.classList.contains(CLASS_NAME_FADE)) {
const transitionDuration = getTransitionDurationFromElement(this.tip)
- EventHandler.one(this.tip, TRANSITION_END, complete)
+ EventHandler.one(this.tip, 'transitionend', complete)
emulateTransitionEnd(this.tip, transitionDuration)
} else {
complete()
@@ -367,7 +366,7 @@ class Tooltip extends BaseComponent {
if (this.tip.classList.contains(CLASS_NAME_FADE)) {
const transitionDuration = getTransitionDurationFromElement(tip)
- EventHandler.one(tip, TRANSITION_END, complete)
+ EventHandler.one(tip, 'transitionend', complete)
emulateTransitionEnd(tip, transitionDuration)
} else {
complete()
@@ -467,21 +466,16 @@ class Tooltip extends BaseComponent {
// Private
_getPopperConfig(attachment) {
- const flipModifier = {
- name: 'flip',
- options: {
- altBoundary: true
- }
- }
-
- if (this.config.fallbackPlacements) {
- flipModifier.options.fallbackPlacements = this.config.fallbackPlacements
- }
-
const defaultBsConfig = {
placement: attachment,
modifiers: [
- flipModifier,
+ {
+ name: 'flip',
+ options: {
+ altBoundary: true,
+ fallbackPlacements: this.config.fallbackPlacements
+ }
+ },
{
name: 'preventOverflow',
options: {
diff --git a/js/src/util/index.js b/js/src/util/index.js
index 1bf9fe954..22d0a578b 100644
--- a/js/src/util/index.js
+++ b/js/src/util/index.js
@@ -111,15 +111,14 @@ const typeCheckConfig = (componentName, config, configTypes) => {
Object.keys(configTypes).forEach(property => {
const expectedTypes = configTypes[property]
const value = config[property]
- const valueType = value && isElement(value) ?
- 'element' :
- toType(value)
+ const valueType = value && isElement(value) ? 'element' : toType(value)
if (!new RegExp(expectedTypes).test(valueType)) {
- throw new Error(
+ throw new TypeError(
`${componentName.toUpperCase()}: ` +
`Option "${property}" provided type "${valueType}" ` +
- `but expected type "${expectedTypes}".`)
+ `but expected type "${expectedTypes}".`
+ )
}
})
}
@@ -205,7 +204,6 @@ const defineJQueryPlugin = (name, plugin) => {
}
export {
- TRANSITION_END,
getUID,
getSelectorFromElement,
getElementFromSelector,
diff --git a/js/src/util/sanitizer.js b/js/src/util/sanitizer.js
index d908dd661..ce3d455a6 100644
--- a/js/src/util/sanitizer.js
+++ b/js/src/util/sanitizer.js
@@ -37,7 +37,7 @@ const allowedAttribute = (attr, allowedAttributeList) => {
if (allowedAttributeList.includes(attrName)) {
if (uriAttrs.has(attrName)) {
- return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN))
+ return Boolean(SAFE_URL_PATTERN.test(attr.nodeValue) || DATA_URL_PATTERN.test(attr.nodeValue))
}
return true
@@ -47,7 +47,7 @@ const allowedAttribute = (attr, allowedAttributeList) => {
// Check if a regular expression validates the attribute.
for (let i = 0, len = regExp.length; i < len; i++) {
- if (attrName.match(regExp[i])) {
+ if (regExp[i].test(attrName)) {
return true
}
}
diff --git a/js/tests/browsers.js b/js/tests/browsers.js
index 8df94cc53..2e1bd3a80 100644
--- a/js/tests/browsers.js
+++ b/js/tests/browsers.js
@@ -5,21 +5,21 @@ const browsers = {
safariMac: {
base: 'BrowserStack',
os: 'OS X',
- os_version: 'High Sierra',
+ os_version: 'Catalina',
browser: 'Safari',
browser_version: 'latest'
},
chromeMac: {
base: 'BrowserStack',
os: 'OS X',
- os_version: 'High Sierra',
+ os_version: 'Catalina',
browser: 'Chrome',
browser_version: 'latest'
},
firefoxMac: {
base: 'BrowserStack',
os: 'OS X',
- os_version: 'High Sierra',
+ os_version: 'Catalina',
browser: 'Firefox',
browser_version: 'latest'
},
diff --git a/js/tests/karma.conf.js b/js/tests/karma.conf.js
index d0dd8bdd9..cd4946525 100644
--- a/js/tests/karma.conf.js
+++ b/js/tests/karma.conf.js
@@ -1,5 +1,7 @@
/* eslint-env node */
+'use strict'
+
const path = require('path')
const ip = require('ip')
const { babel } = require('@rollup/plugin-babel')
@@ -12,10 +14,11 @@ const {
browsersKeys
} = require('./browsers')
-const { env } = process
-const browserStack = env.BROWSER === 'true'
-const debug = env.DEBUG === 'true'
-const jQueryTest = env.JQUERY === 'true'
+const ENV = process.env
+const BROWSERSTACK = Boolean(ENV.BROWSERSTACK)
+const DEBUG = Boolean(ENV.DEBUG)
+const JQUERY_TEST = Boolean(ENV.JQUERY)
+
const frameworks = [
'jasmine'
]
@@ -30,16 +33,16 @@ const reporters = ['dots']
const detectBrowsers = {
usePhantomJS: false,
postDetection(availableBrowser) {
- if (env.CI === true || availableBrowser.includes('Chrome')) {
- return debug ? ['Chrome'] : ['ChromeHeadless']
+ if (ENV.CI === true || availableBrowser.includes('Chrome')) {
+ return DEBUG ? ['Chrome'] : ['ChromeHeadless']
}
if (availableBrowser.includes('Chromium')) {
- return debug ? ['Chromium'] : ['ChromiumHeadless']
+ return DEBUG ? ['Chromium'] : ['ChromiumHeadless']
}
if (availableBrowser.includes('Firefox')) {
- return debug ? ['Firefox'] : ['FirefoxHeadless']
+ return DEBUG ? ['Firefox'] : ['FirefoxHeadless']
}
throw new Error('Please install Chrome, Chromium or Firefox')
@@ -59,7 +62,7 @@ const conf = {
colors: true,
autoWatch: false,
singleRun: true,
- concurrency: Infinity,
+ concurrency: Number.POSITIVE_INFINITY,
client: {
clearContext: false
},
@@ -67,7 +70,7 @@ const conf = {
'node_modules/hammer-simulator/index.js',
{
pattern: 'js/tests/unit/**/!(jquery).spec.js',
- watched: !browserStack
+ watched: !BROWSERSTACK
}
],
preprocessors: {
@@ -101,11 +104,11 @@ const conf = {
}
}
-if (browserStack) {
+if (BROWSERSTACK) {
conf.hostname = ip.address()
conf.browserStack = {
- username: env.BROWSER_STACK_USERNAME,
- accessKey: env.BROWSER_STACK_ACCESS_KEY,
+ username: ENV.BROWSER_STACK_USERNAME,
+ accessKey: ENV.BROWSER_STACK_ACCESS_KEY,
build: `bootstrap-${new Date().toISOString()}`,
project: 'Bootstrap',
retryLimit: 2
@@ -114,7 +117,7 @@ if (browserStack) {
conf.customLaunchers = browsers
conf.browsers = browsersKeys
reporters.push('BrowserStack', 'kjhtml')
-} else if (jQueryTest) {
+} else if (JQUERY_TEST) {
frameworks.push('detectBrowsers')
plugins.push(
'karma-chrome-launcher',
@@ -155,7 +158,7 @@ if (browserStack) {
}
}
- if (debug) {
+ if (DEBUG) {
conf.hostname = ip.address()
plugins.push('karma-jasmine-html-reporter')
reporters.push('kjhtml')
@@ -169,7 +172,6 @@ conf.plugins = plugins
conf.reporters = reporters
module.exports = karmaConfig => {
- // possible values: karmaConfig.LOG_DISABLE || karmaConfig.LOG_ERROR || karmaConfig.LOG_WARN || karmaConfig.LOG_INFO || karmaConfig.LOG_DEBUG
- conf.logLevel = karmaConfig.LOG_ERROR || karmaConfig.LOG_WARN
+ conf.logLevel = karmaConfig.LOG_ERROR
karmaConfig.set(conf)
}
diff --git a/js/tests/unit/carousel.spec.js b/js/tests/unit/carousel.spec.js
index 07b8fc311..787a276de 100644
--- a/js/tests/unit/carousel.spec.js
+++ b/js/tests/unit/carousel.spec.js
@@ -905,7 +905,7 @@ describe('Carousel', () => {
})
describe('to', () => {
- it('should go directement to the provided index', done => {
+ it('should go directly to the provided index', done => {
fixtureEl.innerHTML = [
'
@@ -206,6 +206,34 @@ Add `data-bs-interval=""` to a `.carousel-item` to change the amount of time to
{{< /example >}}
+### Disable touch swiping
+
+Carousels support swiping left/right on touchscreen devices to move between slides. This can be disabled using the `data-bs-touch` attribute. The example below also does not include the `data-bs-ride` attribute and has `data-bs-interval="false"` so it doesn't autoplay.
+
+{{< example >}}
+
+{{< /example >}}
+
## Dark variant
Add `.carousel-dark` to the `.carousel` for darker controls, indicators, and captions. Controls have been inverted from their default white fill with the `filter` CSS property. Captions and controls have additional Sass variables that customize the `color` and `background-color`.
@@ -222,21 +250,21 @@ Add `.carousel-dark` to the `.carousel` for darker controls, indicators, and cap
{{< placeholder width="800" height="400" class="bd-placeholder-img-lg d-block w-100" color="#aaa" background="#f5f5f5" text="First slide" >}}
Nulla vitae elit libero, a pharetra augue mollis interdum. Some representative placeholder content for the first slide. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Some representative placeholder content for the second slide. Praesent commodo cursus magna, vel scelerisque nisl consectetur. Some representative placeholder content for the third slide.First slide label
- Second slide label
- Third slide label
-
interval5000false, carousel will not automatically cycle.keyboardpause"hover"If set to "hover", pauses the cycling of the carousel on mouseenter and resumes the cycling of the carousel on mouseleave. If set to false, hovering over the carousel won't pause it.
On touch-enabled devices, when set to "hover", cycling will pause on touchend (once the user finished interacting with the carousel) for two intervals, before automatically resuming. Note that this is in addition to the above mouse behavior.
'hover'If set to 'hover', pauses the cycling of the carousel on mouseenter and resumes the cycling of the carousel on mouseleave. If set to false, hovering over the carousel won't pause it.
On touch-enabled devices, when set to 'hover', cycling will pause on touchend (once the user finished interacting with the carousel) for two intervals, before automatically resuming. Note that this is in addition to the above mouse behavior.
slideridefalse'carousel', autoplays the carousel on load.wrap@@ -35,7 +35,7 @@ You can use a link with the `href` attribute, or a button with the `data-bs-targ
reference'toggle''toggle', 'parent', or an HTMLElement reference. For more information refer to Popper's constructor docs.'toggle', 'parent', an HTMLElement reference or an object providing getBoundingClientRect. For more information refer to Popper's constructor docs and virtual element docs.displaypopperConfignull