diff --git a/.github/workflows/browserstack.yml b/.github/workflows/browserstack.yml
index 917ccc188..ea0a25704 100644
--- a/.github/workflows/browserstack.yml
+++ b/.github/workflows/browserstack.yml
@@ -4,7 +4,6 @@ on:
push:
env:
- CI: true
FORCE_COLOR: 2
NODE: 14.x
@@ -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 03b3a2b52..989991f0d 100644
--- a/.github/workflows/bundlewatch.yml
+++ b/.github/workflows/bundlewatch.yml
@@ -7,7 +7,6 @@ on:
pull_request:
env:
- CI: true
FORCE_COLOR: 2
NODE: 14.x
@@ -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/css.yml b/.github/workflows/css.yml
index 7fb947005..4463d52f4 100644
--- a/.github/workflows/css.yml
+++ b/.github/workflows/css.yml
@@ -7,7 +7,6 @@ on:
pull_request:
env:
- CI: true
FORCE_COLOR: 2
NODE: 14.x
@@ -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 d2529cbfe..cda6f7748 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -7,7 +7,6 @@ on:
pull_request:
env:
- CI: true
FORCE_COLOR: 2
NODE: 14.x
@@ -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 09c110fc8..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:
@@ -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 30c434984..e7a55083d 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -7,7 +7,6 @@ on:
pull_request:
env:
- CI: true
FORCE_COLOR: 2
NODE: 14.x
@@ -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 9e9cffa40..285d4bcc8 100644
--- a/.github/workflows/node-sass.yml
+++ b/.github/workflows/node-sass.yml
@@ -7,7 +7,6 @@ on:
pull_request:
env:
- CI: true
FORCE_COLOR: 2
NODE: 14.x
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/build/zip-examples.js b/build/zip-examples.js
index c98ed9763..e5eb56f22 100644
--- a/build/zip-examples.js
+++ b/build/zip-examples.js
@@ -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/`)
+sh.rm('-rf', distFolder)
-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/js/src/dropdown.js b/js/src/dropdown.js
index 96024f7c4..73c09c2f4 100644
--- a/js/src/dropdown.js
+++ b/js/src/dropdown.js
@@ -263,7 +263,7 @@ class Dropdown extends BaseComponent {
typeof config.reference.getBoundingClientRect !== 'function'
) {
// Popper virtual elements require a getBoundingClientRect method
- throw new Error(`${NAME}: Option "reference" provided type "object" without a required "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 b1656803d..25013ca3b 100644
--- a/js/src/modal.js
+++ b/js/src/modal.js
@@ -589,7 +589,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/util/index.js b/js/src/util/index.js
index dce4f893f..2f0398003 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/tests/karma.conf.js b/js/tests/karma.conf.js
index 38c7003bb..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')
@@ -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 0286762fa..787a276de 100644
--- a/js/tests/unit/carousel.spec.js
+++ b/js/tests/unit/carousel.spec.js
@@ -1136,11 +1136,9 @@ describe('Carousel', () => {
jQueryMock.fn.carousel = Carousel.jQueryInterface
jQueryMock.elements = [div]
- try {
+ expect(() => {
jQueryMock.fn.carousel.call(jQueryMock, action)
- } catch (error) {
- expect(error.message).toEqual(`No method named "${action}"`)
- }
+ }).toThrowError(TypeError, `No method named "${action}"`)
})
})
diff --git a/js/tests/unit/collapse.spec.js b/js/tests/unit/collapse.spec.js
index 4af21e13f..ff493cf51 100644
--- a/js/tests/unit/collapse.spec.js
+++ b/js/tests/unit/collapse.spec.js
@@ -819,11 +819,9 @@ describe('Collapse', () => {
jQueryMock.fn.collapse = Collapse.jQueryInterface
jQueryMock.elements = [div]
- try {
+ expect(() => {
jQueryMock.fn.collapse.call(jQueryMock, action)
- } catch (error) {
- expect(error.message).toEqual(`No method named "${action}"`)
- }
+ }).toThrowError(TypeError, `No method named "${action}"`)
})
})
diff --git a/js/tests/unit/dropdown.spec.js b/js/tests/unit/dropdown.spec.js
index ba1d0f443..cc4139603 100644
--- a/js/tests/unit/dropdown.spec.js
+++ b/js/tests/unit/dropdown.spec.js
@@ -393,13 +393,13 @@ describe('Dropdown', () => {
expect(() => new Dropdown(btnDropdown, {
reference: {}
- })).toThrow()
+ })).toThrowError(TypeError, 'DROPDOWN: Option "reference" provided type "object" without a required "getBoundingClientRect" method.')
expect(() => new Dropdown(btnDropdown, {
reference: {
getBoundingClientRect: 'not-a-function'
}
- })).toThrow()
+ })).toThrowError(TypeError, 'DROPDOWN: Option "reference" provided type "object" without a required "getBoundingClientRect" method.')
// use onFirstUpdate as Poppers internal update is executed async
const dropdown = new Dropdown(btnDropdown, {
@@ -1557,11 +1557,9 @@ describe('Dropdown', () => {
jQueryMock.fn.dropdown = Dropdown.jQueryInterface
jQueryMock.elements = [div]
- try {
+ expect(() => {
jQueryMock.fn.dropdown.call(jQueryMock, action)
- } catch (error) {
- expect(error.message).toEqual(`No method named "${action}"`)
- }
+ }).toThrowError(TypeError, `No method named "${action}"`)
})
})
diff --git a/js/tests/unit/modal.spec.js b/js/tests/unit/modal.spec.js
index f645e9892..29c90bbf1 100644
--- a/js/tests/unit/modal.spec.js
+++ b/js/tests/unit/modal.spec.js
@@ -870,7 +870,7 @@ describe('Modal', () => {
})
describe('data-api', () => {
- it('should open modal', done => {
+ it('should toggle modal', done => {
fixtureEl.innerHTML = [
' ',
'
'
@@ -885,6 +885,15 @@ describe('Modal', () => {
expect(modalEl.getAttribute('aria-hidden')).toEqual(null)
expect(modalEl.style.display).toEqual('block')
expect(document.querySelector('.modal-backdrop')).toBeDefined()
+ setTimeout(() => trigger.click(), 10)
+ })
+
+ modalEl.addEventListener('hidden.bs.modal', () => {
+ expect(modalEl.getAttribute('aria-modal')).toEqual(null)
+ expect(modalEl.getAttribute('role')).toEqual(null)
+ expect(modalEl.getAttribute('aria-hidden')).toEqual('true')
+ expect(modalEl.style.display).toEqual('none')
+ expect(document.querySelector('.modal-backdrop')).toEqual(null)
done()
})
@@ -1061,11 +1070,9 @@ describe('Modal', () => {
jQueryMock.fn.modal = Modal.jQueryInterface
jQueryMock.elements = [div]
- try {
+ expect(() => {
jQueryMock.fn.modal.call(jQueryMock, action)
- } catch (error) {
- expect(error.message).toEqual(`No method named "${action}"`)
- }
+ }).toThrowError(TypeError, `No method named "${action}"`)
})
it('should call show method', () => {
diff --git a/js/tests/unit/popover.spec.js b/js/tests/unit/popover.spec.js
index 3c04e7ac1..e5c235e2a 100644
--- a/js/tests/unit/popover.spec.js
+++ b/js/tests/unit/popover.spec.js
@@ -206,11 +206,9 @@ describe('Popover', () => {
jQueryMock.fn.popover = Popover.jQueryInterface
jQueryMock.elements = [popoverEl]
- try {
+ expect(() => {
jQueryMock.fn.popover.call(jQueryMock, action)
- } catch (error) {
- expect(error.message).toEqual(`No method named "${action}"`)
- }
+ }).toThrowError(TypeError, `No method named "${action}"`)
})
it('should should call show method', () => {
diff --git a/js/tests/unit/scrollspy.spec.js b/js/tests/unit/scrollspy.spec.js
index eab9183aa..0d175aafa 100644
--- a/js/tests/unit/scrollspy.spec.js
+++ b/js/tests/unit/scrollspy.spec.js
@@ -625,11 +625,9 @@ describe('ScrollSpy', () => {
jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface
jQueryMock.elements = [div]
- try {
+ expect(() => {
jQueryMock.fn.scrollspy.call(jQueryMock, action)
- } catch (error) {
- expect(error.message).toEqual(`No method named "${action}"`)
- }
+ }).toThrowError(TypeError, `No method named "${action}"`)
})
})
diff --git a/js/tests/unit/tab.spec.js b/js/tests/unit/tab.spec.js
index 67a85b2e4..d098ab027 100644
--- a/js/tests/unit/tab.spec.js
+++ b/js/tests/unit/tab.spec.js
@@ -397,11 +397,9 @@ describe('Tab', () => {
jQueryMock.fn.tab = Tab.jQueryInterface
jQueryMock.elements = [div]
- try {
+ expect(() => {
jQueryMock.fn.tab.call(jQueryMock, action)
- } catch (error) {
- expect(error.message).toEqual(`No method named "${action}"`)
- }
+ }).toThrowError(TypeError, `No method named "${action}"`)
})
})
diff --git a/js/tests/unit/toast.spec.js b/js/tests/unit/toast.spec.js
index cc873d1fe..2920eb2ef 100644
--- a/js/tests/unit/toast.spec.js
+++ b/js/tests/unit/toast.spec.js
@@ -368,11 +368,9 @@ describe('Toast', () => {
jQueryMock.fn.toast = Toast.jQueryInterface
jQueryMock.elements = [div]
- try {
+ expect(() => {
jQueryMock.fn.toast.call(jQueryMock, action)
- } catch (error) {
- expect(error.message).toEqual(`No method named "${action}"`)
- }
+ }).toThrowError(TypeError, `No method named "${action}"`)
})
})
diff --git a/js/tests/unit/tooltip.spec.js b/js/tests/unit/tooltip.spec.js
index 8224c3437..213dcc91f 100644
--- a/js/tests/unit/tooltip.spec.js
+++ b/js/tests/unit/tooltip.spec.js
@@ -1207,11 +1207,9 @@ describe('Tooltip', () => {
jQueryMock.fn.tooltip = Tooltip.jQueryInterface
jQueryMock.elements = [div]
- try {
+ expect(() => {
jQueryMock.fn.tooltip.call(jQueryMock, action)
- } catch (error) {
- expect(error.message).toEqual(`No method named "${action}"`)
- }
+ }).toThrowError(TypeError, `No method named "${action}"`)
})
})
})
diff --git a/js/tests/unit/util/index.spec.js b/js/tests/unit/util/index.spec.js
index d4b09bf77..00ab44f1e 100644
--- a/js/tests/unit/util/index.spec.js
+++ b/js/tests/unit/util/index.spec.js
@@ -212,7 +212,7 @@ describe('Util', () => {
expect(() => {
Util.typeCheckConfig(namePlugin, config, defaultType)
- }).toThrow(new Error('COLLAPSE: Option "parent" provided type "number" but expected type "(string|element)".'))
+ }).toThrowError(TypeError, 'COLLAPSE: Option "parent" provided type "number" but expected type "(string|element)".')
})
it('should return null stringified when null is passed', () => {
diff --git a/package-lock.json b/package-lock.json
index e3dd0cee0..a8d8dd4a9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1612,9 +1612,9 @@
"optional": true
},
"autoprefixer": {
- "version": "10.2.0",
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.2.0.tgz",
- "integrity": "sha512-5SdPoK2Q3rxVh4xC8GKdy772KbEESdl2SyJADCw5FMUrd5eNtpOTLo7oCFMEKvK86zQdvsIVTYk93E8dAfRy6g==",
+ "version": "10.2.1",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.2.1.tgz",
+ "integrity": "sha512-dwP0UjyYvROUvtU+boBx8ff5pPWami1NGTrJs9YUsS/oZVbRAcdNHOOuXSA1fc46tgKqe072cVaKD69rvCc3QQ==",
"dev": true,
"requires": {
"browserslist": "^4.16.1",
@@ -1623,21 +1623,6 @@
"fraction.js": "^4.0.13",
"normalize-range": "^0.1.2",
"postcss-value-parser": "^4.1.0"
- },
- "dependencies": {
- "browserslist": {
- "version": "4.16.1",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.1.tgz",
- "integrity": "sha512-UXhDrwqsNcpTYJBTZsbGATDxZbiVDsx6UjpmRUmtnP10pr8wAYr5LgFoEFw9ixriQH2mv/NX2SfGzE/o8GndLA==",
- "dev": true,
- "requires": {
- "caniuse-lite": "^1.0.30001173",
- "colorette": "^1.2.1",
- "electron-to-chromium": "^1.3.634",
- "escalade": "^3.1.1",
- "node-releases": "^1.1.69"
- }
- }
}
},
"axios": {
@@ -2068,16 +2053,16 @@
}
},
"browserslist": {
- "version": "4.16.0",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.0.tgz",
- "integrity": "sha512-/j6k8R0p3nxOC6kx5JGAxsnhc9ixaWJfYc+TNTzxg6+ARaESAvQGV7h0uNOB4t+pLQJZWzcrMxXOxjgsCj3dqQ==",
+ "version": "4.16.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.1.tgz",
+ "integrity": "sha512-UXhDrwqsNcpTYJBTZsbGATDxZbiVDsx6UjpmRUmtnP10pr8wAYr5LgFoEFw9ixriQH2mv/NX2SfGzE/o8GndLA==",
"dev": true,
"requires": {
- "caniuse-lite": "^1.0.30001165",
+ "caniuse-lite": "^1.0.30001173",
"colorette": "^1.2.1",
- "electron-to-chromium": "^1.3.621",
+ "electron-to-chromium": "^1.3.634",
"escalade": "^3.1.1",
- "node-releases": "^1.1.67"
+ "node-releases": "^1.1.69"
}
},
"browserstack": {
@@ -2284,13 +2269,13 @@
}
},
"call-bind": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz",
- "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.1.tgz",
+ "integrity": "sha512-tvAvUwNcRikl3RVF20X9lsYmmepsovzTWeJiXjO0PkJp15uy/6xKFZOQtuiSULwYW+6ToZBprphCgWXC2dSgcQ==",
"dev": true,
"requires": {
"function-bind": "^1.1.1",
- "get-intrinsic": "^1.0.0"
+ "get-intrinsic": "^1.0.2"
}
},
"callsites": {
@@ -2401,14 +2386,14 @@
}
},
"chokidar": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz",
- "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==",
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.0.tgz",
+ "integrity": "sha512-JgQM9JS92ZbFR4P90EvmzNpSGhpPBGBSj10PILeDyYFwp4h2/D9OM03wsJ4zW1fEp4ka2DGrnUeD7FuvQ2aZ2Q==",
"dev": true,
"requires": {
"anymatch": "~3.1.1",
"braces": "~3.0.2",
- "fsevents": "~2.1.2",
+ "fsevents": "~2.3.1",
"glob-parent": "~5.1.0",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
@@ -3336,9 +3321,9 @@
"dev": true
},
"electron-to-chromium": {
- "version": "1.3.634",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.634.tgz",
- "integrity": "sha512-QPrWNYeE/A0xRvl/QP3E0nkaEvYUvH3gM04ZWYtIa6QlSpEetRlRI1xvQ7hiMIySHHEV+mwDSX8Kj4YZY6ZQAw==",
+ "version": "1.3.635",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.635.tgz",
+ "integrity": "sha512-RRriZOLs9CpW6KTLmgBqyUdnY0QNqqWs0HOtuQGGEMizOTNNn1P7sGRBxARnUeLejOsgwjDyRqT3E/CSst02ZQ==",
"dev": true
},
"emoji-regex": {
@@ -4640,9 +4625,9 @@
"dev": true
},
"fsevents": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
- "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz",
+ "integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==",
"dev": true,
"optional": true
},
@@ -4879,9 +4864,9 @@
"dev": true
},
"globby": {
- "version": "11.0.1",
- "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz",
- "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==",
+ "version": "11.0.2",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz",
+ "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==",
"dev": true,
"requires": {
"array-union": "^2.1.0",
@@ -6594,9 +6579,9 @@
"dev": true
},
"meow": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.0.tgz",
- "integrity": "sha512-fNWkgM1UVMey2kf24yLiccxLihc5W+6zVus3/N0b+VfnJgxV99E9u04X6NAiKdg6ED7DAQBX5sy36NM0QJZkWA==",
+ "version": "8.1.2",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz",
+ "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==",
"dev": true,
"requires": {
"@types/minimist": "^1.2.0",
@@ -7849,9 +7834,9 @@
"optional": true
},
"postcss": {
- "version": "8.2.2",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.2.tgz",
- "integrity": "sha512-HM1NDNWLgglJPQQMNwvLxgH2KcrKZklKLi/xXYIOaqQB57p/pDWEJNS83PVICYsn1Dg/9C26TiejNr422/ePaQ==",
+ "version": "8.2.4",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.4.tgz",
+ "integrity": "sha512-kRFftRoExRVXZlwUuay9iC824qmXPcQQVzAjbCCgjpXnkdMCJYBu2gTwAaFBzv8ewND6O8xFb3aELmEkh9zTzg==",
"dev": true,
"requires": {
"colorette": "^1.2.1",
@@ -8854,6 +8839,15 @@
"dev": true,
"requires": {
"fsevents": "~2.1.2"
+ },
+ "dependencies": {
+ "fsevents": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
+ "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
+ "dev": true,
+ "optional": true
+ }
}
},
"rollup-plugin-istanbul": {
@@ -9020,9 +9014,9 @@
"dev": true
},
"sass": {
- "version": "1.32.2",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.2.tgz",
- "integrity": "sha512-u1pUuzqwz3SAgvHSWp1k0mRhX82b2DdlVnP6UIetQPZtYbuJUDaPQhZE12jyjB7vYeOScfz9WPsZJB6Rpk7heA==",
+ "version": "1.32.4",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.4.tgz",
+ "integrity": "sha512-N0BT0PI/t3+gD8jKa83zJJUb7ssfQnRRfqN+GIErokW6U4guBpfYl8qYB+OFLEho+QvnV5ZH1R9qhUC/Z2Ch9w==",
"dev": true,
"requires": {
"chokidar": ">=2.0.0 <4.0.0"
@@ -9478,9 +9472,9 @@
}
},
"socket.io": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.0.tgz",
- "integrity": "sha512-9UPJ1UTvKayuQfVv2IQ3k7tCQC/fboDyIK62i99dAQIyHKaBsNdTpwHLgKJ6guRWxRtC9H+138UwpaGuQO9uWQ==",
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.1.tgz",
+ "integrity": "sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==",
"dev": true,
"requires": {
"debug": "~4.1.0",
diff --git a/package.json b/package.json
index 6a6fc40a0..f70adc436 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,9 @@
"name": "bootstrap",
"description": "The most popular front-end framework for developing responsive, mobile first projects on the web.",
"version": "5.0.0-beta1",
- "version_short": "5.0",
+ "config": {
+ "version_short": "5.0"
+ },
"keywords": [
"css",
"sass",
@@ -32,7 +34,7 @@
"css-prefix": "npm-run-all --parallel css-prefix-*",
"css-prefix-main": "postcss --config build/postcss.config.js --replace \"dist/css/*.css\" \"!dist/css/*.rtl*.css\" \"!dist/css/*.min.css\"",
"css-prefix-examples": "postcss --config build/postcss.config.js --replace \"site/content/**/*.css\"",
- "css-prefix-examples-rtl": "cross-env-shell NODE_ENV=RTL postcss --config build/postcss.config.js --dir \"site/content/docs/$npm_package_version_short/examples/\" --ext \".rtl.css\" --base \"site/content/docs/$npm_package_version_short/examples/\" \"site/content/docs/$npm_package_version_short/examples/{blog,carousel,dashboard,cheatsheet}/*.css\" \"!site/content/docs/$npm_package_version_short/examples/{blog,carousel,dashboard,cheatsheet}/*.rtl.css\"",
+ "css-prefix-examples-rtl": "cross-env-shell NODE_ENV=RTL postcss --config build/postcss.config.js --dir \"site/content/docs/$npm_package_config_version_short/examples/\" --ext \".rtl.css\" --base \"site/content/docs/$npm_package_config_version_short/examples/\" \"site/content/docs/$npm_package_config_version_short/examples/{blog,carousel,dashboard,cheatsheet}/*.css\" \"!site/content/docs/$npm_package_config_version_short/examples/{blog,carousel,dashboard,cheatsheet}/*.rtl.css\"",
"js": "npm-run-all js-compile js-minify",
"js-compile": "npm-run-all --parallel js-compile-*",
"js-compile-standalone": "rollup --environment BUNDLE:false --config build/rollup.config.js --sourcemap",
@@ -49,7 +51,7 @@
"js-test-karma": "karma start js/tests/karma.conf.js",
"js-test-integration-bundle": "rollup --config js/tests/integration/rollup.bundle.js",
"js-test-integration-modularity": "rollup --config js/tests/integration/rollup.bundle-modularity.js",
- "js-test-cloud": "cross-env BROWSER=true npm run js-test-karma",
+ "js-test-cloud": "cross-env BROWSERSTACK=true npm run js-test-karma",
"js-test-jquery": "cross-env JQUERY=true npm run js-test-karma",
"lint": "npm-run-all --parallel js-lint css-lint lockfile-lint",
"docs": "npm-run-all docs-build docs-lint",
@@ -106,7 +108,7 @@
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-node-resolve": "^11.0.1",
"@rollup/plugin-replace": "^2.3.4",
- "autoprefixer": "^10.2.0",
+ "autoprefixer": "^10.2.1",
"bundlewatch": "^0.3.1",
"clean-css-cli": "^4.3.0",
"cross-env": "^7.0.3",
@@ -133,12 +135,12 @@
"lockfile-lint": "^4.3.7",
"nodemon": "^2.0.7",
"npm-run-all": "^4.1.5",
- "postcss": "^8.2.2",
+ "postcss": "^8.2.4",
"postcss-cli": "^8.3.1",
"rollup": "^2.36.1",
"rollup-plugin-istanbul": "^3.0.0",
"rtlcss": "^3.0.0",
- "sass": "^1.32.2",
+ "sass": "^1.32.4",
"shelljs": "^0.8.4",
"stylelint": "^13.8.0",
"stylelint-config-twbs-bootstrap": "^2.1.0",
diff --git a/scss/_close.scss b/scss/_close.scss
index db5d02787..32a0f68cc 100644
--- a/scss/_close.scss
+++ b/scss/_close.scss
@@ -22,7 +22,7 @@
}
&:focus {
- outline: none;
+ outline: 0;
box-shadow: $btn-close-focus-shadow;
opacity: $btn-close-focus-opacity;
}
diff --git a/scss/_reboot.scss b/scss/_reboot.scss
index 58e1a34d0..973b5d16c 100644
--- a/scss/_reboot.scss
+++ b/scss/_reboot.scss
@@ -411,6 +411,15 @@ button {
border-radius: 0;
}
+// Explicitly remove focus outline in Chromium when it shouldn't be
+// visible (e.g. as result of mouse click or touch tap). It already
+// should be doing this automatically, but seems to currently be
+// confused and applies its very visible two-tone outline anyway.
+
+button:focus:not(:focus-visible) {
+ outline: 0;
+}
+
// 1. Remove the margin in Firefox and Safari
input,
diff --git a/scss/_toasts.scss b/scss/_toasts.scss
index 7a1cf7354..5c533d7f5 100644
--- a/scss/_toasts.scss
+++ b/scss/_toasts.scss
@@ -47,4 +47,5 @@
.toast-body {
padding: $toast-padding-x; // apply to both vertical and horizontal
+ word-wrap: break-word;
}
diff --git a/scss/_variables.scss b/scss/_variables.scss
index 3fc03a08e..3d677728a 100644
--- a/scss/_variables.scss
+++ b/scss/_variables.scss
@@ -938,7 +938,7 @@ $dropdown-box-shadow: $box-shadow !default;
$dropdown-link-color: $gray-900 !default;
$dropdown-link-hover-color: shade-color($gray-900, 10%) !default;
-$dropdown-link-hover-bg: $gray-100 !default;
+$dropdown-link-hover-bg: $gray-200 !default;
$dropdown-link-active-color: $component-active-color !default;
$dropdown-link-active-bg: $component-active-bg !default;
diff --git a/scss/forms/_form-range.scss b/scss/forms/_form-range.scss
index b603a7bc3..ae1d841d5 100644
--- a/scss/forms/_form-range.scss
+++ b/scss/forms/_form-range.scss
@@ -12,7 +12,7 @@
appearance: none;
&:focus {
- outline: none;
+ outline: 0;
// Pseudo-elements must be split across multiple rulesets to have an effect.
// No box-shadow() mixin for focus accessibility.
diff --git a/site/content/docs/5.0/components/toasts.md b/site/content/docs/5.0/components/toasts.md
index b6f7e3893..bd7bcae9c 100644
--- a/site/content/docs/5.0/components/toasts.md
+++ b/site/content/docs/5.0/components/toasts.md
@@ -43,7 +43,7 @@ Toasts are as flexible as you need and have very little required markup. At a mi
### Translucent
-Toasts are slightly translucent, too, so they blend over whatever they might appear over. For browsers that support the `backdrop-filter` CSS property, we'll also attempt to blur the elements under a toast.
+Toasts are slightly translucent, too, so they blend over whatever they might appear over.
{{< example class="bg-dark" >}}
@@ -120,7 +120,7 @@ Alternatively, you can also add additional controls and components to toasts.
### Color schemes
-Building on the above example, you can create different toast color schemes with our [color utilities]({{< docsref "/utilities/colors" >}}). Here we've added `.bg-primary` and `.text-white` to the `.toast`, and then added `.text-white` to our close button. For a crisp edge, we remove the default border with `.border-0`.
+Building on the above example, you can create different toast color schemes with our [color utilities]({{< docsref "/utilities/colors" >}}). Here we've added `.bg-primary` and `.text-white` to the `.toast`, and then added `.btn-close-white` to our close button. For a crisp edge, we remove the default border with `.border-0`.
{{< example class="bg-light" >}}
diff --git a/site/content/docs/5.0/extend/approach.md b/site/content/docs/5.0/extend/approach.md
index 552b5f29b..32efbf541 100644
--- a/site/content/docs/5.0/extend/approach.md
+++ b/site/content/docs/5.0/extend/approach.md
@@ -76,3 +76,11 @@ Specifically regarding custom CSS, utilities can help combat increasing file siz
## Flexible HTML
While not always possible, we strive to avoid being overly dogmatic in our HTML requirements for components. Thus, we focus on single classes in our CSS selectors and try to avoid immediate children selectors (`>`). This gives you more flexibility in your implementation and helps keep our CSS simpler and less specific.
+
+## Code conventions
+
+[Code Guide](https://codeguide.co/) (from Bootstrap co-creator, @mdo) documents how we write our HTML and CSS across Bootstrap. It specifices guidelines for general formatting, common sense defaults, property and attribute orders, and more.
+
+We use [Stylelint](https://stylelint.io/) to enforce these standards and more in our Sass/CSS. [Our custom Stylelint config](https://github.com/twbs/stylelint-config-twbs-bootstrap) is open source and available for others to use and extend.
+
+We use [vnu-jar](https://www.npmjs.com/package/vnu-jar) to enforce standard and semantic HTML, as well as detecting common errors.
diff --git a/site/content/docs/5.0/forms/floating-labels.md b/site/content/docs/5.0/forms/floating-labels.md
index 3163ea8e0..2012a4e82 100644
--- a/site/content/docs/5.0/forms/floating-labels.md
+++ b/site/content/docs/5.0/forms/floating-labels.md
@@ -61,7 +61,7 @@ To set a custom height on your `