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/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 00831721d..a12bffcf7 100644 --- a/.github/workflows/browserstack.yml +++ b/.github/workflows/browserstack.yml @@ -4,9 +4,8 @@ on: push: env: - CI: true FORCE_COLOR: 2 - NODE: 14.x + NODE: 14 jobs: browserstack: @@ -19,7 +18,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 }}" @@ -29,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/carousel.js b/js/src/carousel.js index a266ec10f..06a391419 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -11,6 +11,7 @@ import { getElementFromSelector, getTransitionDurationFromElement, isVisible, + isRTL, reflow, triggerTransitionEnd, typeCheckConfig @@ -250,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() + } } } @@ -339,10 +348,18 @@ class Carousel extends BaseComponent { if (event.key === ARROW_LEFT_KEY) { event.preventDefault() - this.prev() + if (isRTL) { + this.next() + } else { + this.prev() + } } else if (event.key === ARROW_RIGHT_KEY) { event.preventDefault() - this.next() + if (isRTL) { + this.prev() + } else { + this.next() + } } } diff --git a/js/src/collapse.js b/js/src/collapse.js index 90bab0ec9..0d3eeed54 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -371,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/dropdown.js b/js/src/dropdown.js index 4d65008f8..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 } diff --git a/js/src/modal.js b/js/src/modal.js index 87c22943a..fe1b5a4c6 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -590,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/tooltip.js b/js/src/tooltip.js index 103524b8b..63a30cf2f 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -51,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', @@ -81,7 +81,7 @@ const Default = { selector: false, placement: 'top', container: false, - fallbackPlacements: null, + fallbackPlacements: ['top', 'right', 'bottom', 'left'], boundary: 'clippingParents', customClass: '', sanitize: true, @@ -466,22 +466,16 @@ class Tooltip extends BaseComponent { // Private _getPopperConfig(attachment) { - const flipModifier = { - name: 'flip', - options: { - altBoundary: true, - fallbackPlacements: ['top', 'right', 'bottom', 'left'] - } - } - - 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 9ccad1cbb..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}".` + ) } }) } 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 = [ '