Merge branch 'main' into patrickhlauke-carousel

This commit is contained in:
Patrick H. Lauke 2021-01-13 23:20:05 +00:00 committed by GitHub
commit 7e1855d23d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 240 additions and 141 deletions

View file

@ -43,7 +43,6 @@
],
"unicorn/consistent-function-scoping": "off",
"unicorn/explicit-length-check": "off",
"unicorn/import-index": "off",
"unicorn/no-array-callback-reference": "off",
"unicorn/no-for-loop": "off",
"unicorn/no-null": "off",

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -7,7 +7,6 @@ on:
pull_request:
env:
CI: true
FORCE_COLOR: 2
NODE: 14.x

11
.gitignore vendored
View file

@ -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

View file

@ -3,6 +3,10 @@
"stylelint-config-twbs-bootstrap/scss"
],
"rules": {
"declaration-property-value-disallowed-list": {
"border": "none",
"outline": "none"
},
"function-disallowed-list": [
"calc",
"lighten",

View file

@ -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'
}

View file

@ -15,34 +15,64 @@ const sh = require('shelljs')
const pkg = require('../package.json')
const versionShort = pkg.config.version_short
const folderName = `bootstrap-${pkg.version}-examples`
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'), '"../')
@ -54,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)

View file

@ -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)
}

View file

@ -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

View file

@ -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)
})
/**

View file

@ -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}".`
)
}
})
}

View file

@ -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
}
}

View file

@ -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)
}

View file

@ -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}"`)
})
})

View file

@ -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}"`)
})
})

View file

@ -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}"`)
})
})

View file

@ -870,7 +870,7 @@ describe('Modal', () => {
})
describe('data-api', () => {
it('should open modal', done => {
it('should toggle modal', done => {
fixtureEl.innerHTML = [
'<button type="button" data-bs-toggle="modal" data-bs-target="#exampleModal"></button>',
'<div id="exampleModal" class="modal"><div class="modal-dialog"></div></div>'
@ -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', () => {

View file

@ -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', () => {

View file

@ -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}"`)
})
})

View file

@ -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}"`)
})
})

View file

@ -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}"`)
})
})

View file

@ -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}"`)
})
})
})

View file

@ -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', () => {

16
package-lock.json generated
View file

@ -3802,17 +3802,17 @@
}
},
"eslint-plugin-unicorn": {
"version": "25.0.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-25.0.1.tgz",
"integrity": "sha512-MEyEWoyou/qhJH6rEER9YHACtCsQT+eewc6Fdxbi2eiTvsGrBR8JZMA6qaeof3oMQeRxOpaERoBKzU7R5c4A/w==",
"version": "26.0.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-26.0.1.tgz",
"integrity": "sha512-SWgF9sIVY74zqkkSN2dclSCqRfocWSUGD0haC0NX2oRfmdp9p8dQvJYkYSQePaCyssPUE/pqpsIEEZNTh8crUA==",
"dev": true,
"requires": {
"ci-info": "^2.0.0",
"clean-regexp": "^1.0.0",
"eslint-ast-utils": "^1.1.0",
"eslint-template-visitor": "^2.2.1",
"eslint-template-visitor": "^2.2.2",
"eslint-utils": "^2.1.0",
"import-modules": "^2.0.0",
"import-modules": "^2.1.0",
"lodash": "^4.17.20",
"pluralize": "^8.0.0",
"read-pkg-up": "^7.0.1",
@ -9014,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"

View file

@ -51,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",
@ -115,7 +115,7 @@
"eslint": "^7.17.0",
"eslint-config-xo": "^0.34.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-unicorn": "^25.0.1",
"eslint-plugin-unicorn": "^26.0.1",
"find-unused-sass-variables": "^3.1.0",
"glob": "^7.1.6",
"hammer-simulator": "0.0.1",
@ -140,7 +140,7 @@
"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",

View file

@ -22,7 +22,7 @@
}
&:focus {
outline: none;
outline: 0;
box-shadow: $btn-close-focus-shadow;
opacity: $btn-close-focus-opacity;
}

View file

@ -47,4 +47,5 @@
.toast-body {
padding: $toast-padding-x; // apply to both vertical and horizontal
word-wrap: break-word;
}

View file

@ -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;

View file

@ -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.

View file

@ -1,7 +1,7 @@
.bd-subnavbar {
// The position and z-index are needed for the dropdown to stay on top of the content
position: relative;
z-index: $bd-navbar-zindex;
z-index: $zindex-sticky;
background-color: rgba($white, .95);
box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .05), inset 0 -1px 0 rgba(0, 0, 0, .15);

View file

@ -11,6 +11,3 @@ $bd-warning: #f0ad4e;
$bd-danger: #d9534f;
$dropdown-active-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'><path fill='#292b2c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/></svg>");
$sidebar-collapse-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path fill='none' stroke='rgba(0,0,0,.5)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/></svg>");
// Higher than core's $zindex-tooltip, which is 1070
$bd-navbar-zindex: 1080;

View file

@ -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" >}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
@ -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" >}}
<div class="toast d-flex align-items-center text-white bg-primary border-0" role="alert" aria-live="assertive" aria-atomic="true">

View file

@ -34,3 +34,18 @@ While we cannot go into details here on how to use every package manager, we can
For those who want to use the distribution files, review the [getting started page]({{< docsref "/getting-started/introduction" >}}) for how to include those files and an example HTML page. From there, consult the docs for the layout, components, and behaviors you'd like to use.
As you familiarize yourself with Bootstrap, continue exploring this section for more details on how to utilize our global options, making use of and changing our color system, how we build our components, how to use our growing list of CSS custom properties, and how to optimize your code when building with Bootstrap.
## CSPs and embedded SVGs
Several Bootstrap components include embedded SVGs in our CSS to style components consistently and easily across browsers and devices. **For organizations with more strict <abbr title="Content Security Policy">CSP</abbr> configurations**, we've documented all instances of our embedded SVGs (all of which are applied via `background-image`) so you can more thoroughly review your options.
- [Accordion]({{< docsref "/components/accordion" >}})
- [Close button]({{< docsref "/components/close-button" >}}) (used in alerts and modals)
- [Form checkboxes and radio buttons]({{< docsref "/forms/checks-radios" >}})
- [Form switches]({{< docsref "/forms/checks-radios#switches" >}})
- [Form validation icons]({{< docsref "/forms/validation#server-side" >}})
- [Select menus]({{< docsref "/forms/select" >}})
- [Carousel controls]({{< docsref "/components/carousel#with-controls" >}})
- [Navbar toggle buttons]({{< docsref "/components/navbar#responsive-behaviors" >}})
Based on [community conversation](https://github.com/twbs/bootstrap/issues/25394), some options for addressing this in your own codebase include replacing the URLs with locally hosted assets, removing the images and using inline images (not possible in all components), and modifying your CSP. Our recommendation is to carefully review your own security policies and decide on a best path forward, if necessary.

View file

@ -6,7 +6,7 @@ group: customize
toc: true
---
Utilize our source Sass files to take advantage of variables, maps, mixins, and more. In our build we've increased the Sass rounding precision to 6 (by default it's 5) to prevent issues with browser rounding.
Utilize our source Sass files to take advantage of variables, maps, mixins, and more.
## File structure

View file

@ -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.

View file

@ -61,7 +61,7 @@ To set a custom height on your `<textarea>`, do not use the `rows` attribute. In
## Selects
Other than `.form-control`, floating labels are only available on `.form-select`s. They work in the same way, but unlike `<input>`s, they'll always show the `<label>` in its floated state.
Other than `.form-control`, floating labels are only available on `.form-select`s. They work in the same way, but unlike `<input>`s, they'll always show the `<label>` in its floated state. **Selects with `size` and `multiple` are not supported.**
{{< example >}}
<div class="form-floating">

View file

@ -65,6 +65,12 @@ Our [package.json]({{< param repo >}}/blob/v{{< param current_version >}}/packag
</tbody>
</table>
## Sass
Bootstrap uses [Dart Sass](https://sass-lang.com/dart-sass) for compiling our Sass source files into CSS files (included in our build process), and we recommend you do the same if you're compiling Sass using your own asset pipeline. We previously used Node Sass for Bootstrap v4, but LibSass and packages built on top of it, including Node Sass, are now [deprecated](https://sass-lang.com/blog/libsass-is-deprecated).
Dart Sass uses a rounding precision of 10 and for efficiency reasons does not allow adjustment of this value. We don't lower this precision during further processing of our generated CSS, such as during minification, but if you chose to do so we recommend maintaining a precision of at least 6 to prevent issues with browser rounding.
## Autoprefixer
Bootstrap uses [Autoprefixer][autoprefixer] (included in our build process) to automatically add vendor prefixes to some CSS properties at build time. Doing so saves us time and code by allowing us to write key parts of our CSS a single time while eliminating the need for vendor mixins like those found in v3.

View file

@ -21,10 +21,10 @@ This doesn't include documentation, source files, or any optional JavaScript dep
Compile Bootstrap with your own asset pipeline by downloading our source Sass, JavaScript, and documentation files. This option requires some additional tooling:
- Sass compiler (Libsass or Ruby Sass is supported) for compiling your CSS.
- [Sass compiler]({{< docsref "/getting-started/build-tools#sass" >}}) for compiling Sass source files into CSS files
- [Autoprefixer](https://github.com/postcss/autoprefixer) for CSS vendor prefixing
Should you require [build tools]({{< docsref "/getting-started/build-tools#tooling-setup" >}}), they are included for developing Bootstrap and its docs, but they're likely unsuitable for your own purposes.
Should you require our full set of [build tools]({{< docsref "/getting-started/build-tools#tooling-setup" >}}), they are included for developing Bootstrap and its docs, but they're likely unsuitable for your own purposes.
<a href="{{< param "download.source" >}}" class="btn btn-bd-primary" onclick="ga('send', 'event', 'Getting started', 'Download', 'Download source');">Download source</a>
@ -52,7 +52,7 @@ If you're using our compiled JavaScript and prefer to include Popper separately,
## Package managers
Pull in Bootstrap's **source files** into nearly any project with some of the most popular package managers. No matter the package manager, Bootstrap will **require a Sass compiler and [Autoprefixer](https://github.com/postcss/autoprefixer)** for a setup that matches our official compiled versions.
Pull in Bootstrap's **source files** into nearly any project with some of the most popular package managers. No matter the package manager, Bootstrap will **require a [Sass compiler]({{< docsref "/getting-started/build-tools#sass" >}}) and [Autoprefixer](https://github.com/postcss/autoprefixer)** for a setup that matches our official compiled versions.
### npm

View file

@ -136,6 +136,42 @@ $font-family-sans-serif:
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !default;
```
### LTR and RTL at the same time
Need both LTR and RTL on the same page? Thanks to [RTLCSS String Maps](https://rtlcss.com/learn/usage-guide/string-map/), this is pretty straightforward. Wrap your `@import`s with a class, and set a custom rename rule for RTLCSS:
```scss
/* rtl:begin:options: {
"autoRename": true,
"stringMap":[
"name": "ltr-rtl",
"priority": 100,
"search": ["ltr"],
"replace": ["rtl"],
"options": {
"scope": "*",
"ignoreCase": false
}
]
} */
.ltr {
@import "../node_modules/bootstrap/scss/bootstrap";
}
/*rtl:end:options*/
```
After running Sass then RTLCSS, each selector in your CSS files will be prepended by `.ltr`, and `.rtl` for RTL files. Now you're able to use both files on the same page, and simply use `.ltr` or `.rtl` on your components wrappers to use one or the other direction.
{{< callout warning >}}
#### Edge cases and known limitations
While this approach is understandable, please pay attention to the following:
1. When switching `.ltr` and `.rtl`, make sure you add `dir` and `lang` attributes accordingly.
2. Loading both files can be a real performance bottleneck: consider some [optimization]({{< docsref "/customize/optimize" >}}), and maybe try to [load one of those files asynchronously](https://www.filamentgroup.com/lab/load-css-simpler/).
3. Nesting styles this way will prevent our `form-validation-state()` mixin from working as intended, thus require you tweak it a bit by yourself. [See #31223](https://github.com/twbs/bootstrap/issues/31223).
{{< /callout >}}
## The breadcrumb case
The [breadcrumb separator]({{< docsref "/components/breadcrumb" >}}/#changing-the-separator) is the only case requiring its own brand new variable— namely `$breadcrumb-divider-flipped` —defaulting to `$breadcrumb-divider`.

View file

@ -5,7 +5,7 @@ description: Make any HTML element or Bootstrap component clickable by "stretchi
group: helpers
---
Add `.stretched-link` to a link to make its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block) clickable via a `::after` pseudo element. In most cases, this means that an element with `position: relative;` that contains a link with the `.stretched-link` class is clickable.
Add `.stretched-link` to a link to make its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block) clickable via a `::after` pseudo element. In most cases, this means that an element with `position: relative;` that contains a link with the `.stretched-link` class is clickable. Please note given [how CSS `position` works](https://www.w3.org/TR/CSS21/visuren.html#propdef-position), `.stretched-link` cannot be mixed with most table elements.
Cards have `position: relative` by default in Bootstrap, so in this case you can safely add the `.stretched-link` class to a link in the card without any other HTML changes.

View file

@ -50,7 +50,9 @@ Horizontal direction sensitive variables, utilities and mixins are renamed with
- Renamed `.pl-*` and `.pr-*` to `.ps-*` and `.pe-*`.
- Renamed `.text-left` and `.text-right` to `.text-start` and `.text-end`.
Breakpoints specific variants are consequently renamed too (eg. `.text-md-start` replaces `.text-md-left`).
Breakpoints specific variants are consequently renamed too (e.g. `.text-md-start` replaces `.text-md-left`).
**Note**: if you used v4 to make RTL pages, ensure to reverse changes mentioned above: e.g. use `.*-start` were you used `.*-right`.
##### Mixins
@ -127,6 +129,7 @@ Breakpoints specific variants are consequently renamed too (eg. `.text-md-start`
- Renamed `.font-style-*` utilities as `.fst-*` for brevity and consistency.
- Added `.d-grid` to display utilities
- Added new `gap` utilities (`.gap`) for CSS Grid layouts
- Removed `.rounded-sm` and `rounded-lg`, and introduced `.rounded-0` to `.rounded-3`. [See #31687](https://github.com/twbs/bootstrap/pull/31687).
## v5.0.0-alpha2

View file

@ -289,6 +289,25 @@ $utilities: map-merge(
);
```
#### Rename utilities
Missing v4 utilities, or used to another naming convention? The utilities API can be used to override the resulting `class` of a given utility—for example, to rename `.ms-*` utilities to oldish `.ml-*`:
```scss
@import "bootstrap/scss/functions";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/utilities";
$utilities: map-merge(
$utilities, (
"margin-start": map-merge(
map-get($utilities, "margin-start"),
( class: ml ),
),
)
);
```
### Remove utilities
Remove any of the default utilities by setting the group key to `null`. For example, to remove all our `width` utilities, create a `$utilities` `map-merge` and add `"width": null` within.