Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 679759f0c3 | |||
| 14b39390c2 | |||
| 97b6cbcbfd | |||
| 9ed525eb83 | |||
| 572c9756e5 | |||
| f27a9c02d8 | |||
| b7c62d51f2 | |||
| 7f6b214a2a | |||
| 45149e75dc | |||
| 625344c242 | |||
| f6d129182a | |||
| 8dded72f16 | |||
| 9bf1787783 | |||
| dca50e4735 | |||
| 90add983e1 | |||
| 918766ebec | |||
| 6ea5131e06 | |||
| b5dac1e50a | |||
| d42acd2453 | |||
| ce75d1d0e3 | |||
| 4c3849f231 | |||
| 41d6ad9bc3 |
+15
-5
@@ -67,6 +67,7 @@
|
||||
}
|
||||
],
|
||||
"no-console": "error",
|
||||
"no-negated-condition": "off",
|
||||
"object-curly-spacing": [
|
||||
"error",
|
||||
"always"
|
||||
@@ -86,7 +87,6 @@
|
||||
"unicorn/filename-case": "off",
|
||||
"unicorn/no-array-callback-reference": "off",
|
||||
"unicorn/no-array-method-this-argument": "off",
|
||||
"unicorn/no-negated-condition": "off",
|
||||
"unicorn/no-null": "off",
|
||||
"unicorn/no-typeof-undefined": "off",
|
||||
"unicorn/no-unused-properties": "error",
|
||||
@@ -96,6 +96,7 @@
|
||||
"unicorn/prefer-dom-node-dataset": "off",
|
||||
"unicorn/prefer-module": "off",
|
||||
"unicorn/prefer-query-selector": "off",
|
||||
"unicorn/prefer-spread": "off",
|
||||
"unicorn/prefer-string-replace-all": "off",
|
||||
"unicorn/prevent-abbreviations": "off"
|
||||
},
|
||||
@@ -112,7 +113,8 @@
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
"no-console": "off",
|
||||
"unicorn/prefer-top-level-await": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -163,7 +165,8 @@
|
||||
},
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"no-new": "off"
|
||||
"no-new": "off",
|
||||
"unicorn/no-array-for-each": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -190,7 +193,8 @@
|
||||
"ecmaVersion": 2019
|
||||
},
|
||||
"rules": {
|
||||
"no-new": "off"
|
||||
"no-new": "off",
|
||||
"unicorn/no-array-for-each": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -206,7 +210,13 @@
|
||||
"files": [
|
||||
"**/*.md/*.js"
|
||||
],
|
||||
"extends": "plugin:markdown/recommended"
|
||||
"extends": "plugin:markdown/recommended",
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"unicorn/prefer-node-protocol": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
browserstack:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'twbs/bootstrap'
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
name: LambdaTest
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
- "!dependabot/**"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 2
|
||||
NODE: 20
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lambdatest:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'twbs/bootstrap'
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "${{ env.NODE }}"
|
||||
cache: npm
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run dist
|
||||
run: npm run dist
|
||||
|
||||
- name: Run LambdaTest tests
|
||||
run: npm run js-test-lt-cloud
|
||||
env:
|
||||
LT_USERNAME: "${{ secrets.LT_USERNAME }}"
|
||||
LT_ACCESS_KEY: "${{ secrets.LT_ACCESS_KEY }}"
|
||||
LT_TUNNEL_NAME: "tunnel-${{ github.sha }}"
|
||||
GITHUB_SHA: "${{ github.sha }}"
|
||||
@@ -41,3 +41,4 @@ Thumbs.db
|
||||
/dist-sass/
|
||||
/js/coverage/
|
||||
/node_modules/
|
||||
/.lambdatest/
|
||||
|
||||
+13
-6
@@ -87,12 +87,19 @@ const build = async plugin => {
|
||||
console.log(`Built ${plugin.className}`)
|
||||
}
|
||||
|
||||
const basename = path.basename(__filename)
|
||||
const timeLabel = `[${basename}] finished`
|
||||
(async () => {
|
||||
try {
|
||||
const basename = path.basename(__filename)
|
||||
const timeLabel = `[${basename}] finished`
|
||||
|
||||
console.log('Building individual plugins...')
|
||||
console.time(timeLabel)
|
||||
console.log('Building individual plugins...')
|
||||
console.time(timeLabel)
|
||||
|
||||
await Promise.all(Object.values(resolvedPlugins).map(plugin => build(plugin)))
|
||||
await Promise.all(Object.values(resolvedPlugins).map(plugin => build(plugin)))
|
||||
|
||||
console.timeEnd(timeLabel)
|
||||
console.timeEnd(timeLabel)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
}
|
||||
})()
|
||||
|
||||
+27
-19
@@ -81,25 +81,33 @@ function showUsage(args) {
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
let [oldVersion, newVersion] = args
|
||||
async function main(args) {
|
||||
let [oldVersion, newVersion] = args
|
||||
|
||||
if (!oldVersion || !newVersion) {
|
||||
showUsage(args)
|
||||
if (!oldVersion || !newVersion) {
|
||||
showUsage(args)
|
||||
}
|
||||
|
||||
// Strip any leading `v` from arguments because
|
||||
// otherwise we will end up with duplicate `v`s
|
||||
[oldVersion, newVersion] = [oldVersion, newVersion].map(arg => {
|
||||
return arg.startsWith('v') ? arg.slice(1) : arg
|
||||
})
|
||||
|
||||
if (oldVersion === newVersion) {
|
||||
showUsage(args)
|
||||
}
|
||||
|
||||
bumpNpmVersion(newVersion)
|
||||
|
||||
try {
|
||||
await Promise.all(
|
||||
FILES.map(file => replaceRecursively(file, oldVersion, newVersion))
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Strip any leading `v` from arguments because
|
||||
// otherwise we will end up with duplicate `v`s
|
||||
[oldVersion, newVersion] = [oldVersion, newVersion].map(arg => {
|
||||
return arg.startsWith('v') ? arg.slice(1) : arg
|
||||
})
|
||||
|
||||
if (oldVersion === newVersion) {
|
||||
showUsage(args)
|
||||
}
|
||||
|
||||
bumpNpmVersion(newVersion)
|
||||
|
||||
await Promise.all(
|
||||
FILES.map(file => replaceRecursively(file, oldVersion, newVersion))
|
||||
)
|
||||
main(process.argv.slice(2))
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ export default {
|
||||
// can be removed later when multiple key/instances are fine to be used
|
||||
if (!instanceMap.has(key) && instanceMap.size !== 0) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${[...instanceMap.keys()][0]}.`)
|
||||
console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ const getSelector = element => {
|
||||
|
||||
const SelectorEngine = {
|
||||
find(selector, element = document.documentElement) {
|
||||
return [...Element.prototype.querySelectorAll.call(element, selector)]
|
||||
return [].concat(...Element.prototype.querySelectorAll.call(element, selector))
|
||||
},
|
||||
|
||||
findOne(selector, element = document.documentElement) {
|
||||
@@ -42,7 +42,7 @@ const SelectorEngine = {
|
||||
},
|
||||
|
||||
children(element, selector) {
|
||||
return [...element.children].filter(child => child.matches(selector))
|
||||
return [].concat(...element.children).filter(child => child.matches(selector))
|
||||
},
|
||||
|
||||
parents(element, selector) {
|
||||
|
||||
+2
-4
@@ -143,8 +143,7 @@ class Dropdown extends BaseComponent {
|
||||
// only needed because of broken event delegation on iOS
|
||||
// https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
|
||||
if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {
|
||||
const children = [...document.body.children]
|
||||
for (const element of children) {
|
||||
for (const element of [].concat(...document.body.children)) {
|
||||
EventHandler.on(element, 'mouseover', noop)
|
||||
}
|
||||
}
|
||||
@@ -194,8 +193,7 @@ class Dropdown extends BaseComponent {
|
||||
// If this is a touch-enabled device we remove the extra
|
||||
// empty mouseover listeners we added for iOS support
|
||||
if ('ontouchstart' in document.documentElement) {
|
||||
const children = [...document.body.children]
|
||||
for (const element of children) {
|
||||
for (const element of [].concat(...document.body.children)) {
|
||||
EventHandler.off(element, 'mouseover', noop)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -222,8 +222,7 @@ class Tooltip extends BaseComponent {
|
||||
// only needed because of broken event delegation on iOS
|
||||
// https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
|
||||
if ('ontouchstart' in document.documentElement) {
|
||||
const children = [...document.body.children]
|
||||
for (const element of children) {
|
||||
for (const element of [].concat(...document.body.children)) {
|
||||
EventHandler.on(element, 'mouseover', noop)
|
||||
}
|
||||
}
|
||||
@@ -257,8 +256,7 @@ class Tooltip extends BaseComponent {
|
||||
// If this is a touch-enabled device we remove the extra
|
||||
// empty mouseover listeners we added for iOS support
|
||||
if ('ontouchstart' in document.documentElement) {
|
||||
const children = [...document.body.children]
|
||||
for (const element of children) {
|
||||
for (const element of [].concat(...document.body.children)) {
|
||||
EventHandler.off(element, 'mouseover', noop)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {
|
||||
|
||||
const domParser = new window.DOMParser()
|
||||
const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')
|
||||
const elements = [...createdDocument.body.querySelectorAll('*')]
|
||||
const elements = [].concat(...createdDocument.body.querySelectorAll('*'))
|
||||
|
||||
for (const element of elements) {
|
||||
const elementName = element.nodeName.toLowerCase()
|
||||
@@ -103,8 +103,8 @@ export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {
|
||||
continue
|
||||
}
|
||||
|
||||
const attributeList = [...element.attributes]
|
||||
const allowedAttributes = [...allowList['*'] || [], ...allowList[elementName] || []]
|
||||
const attributeList = [].concat(...element.attributes)
|
||||
const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || [])
|
||||
|
||||
for (const attribute of attributeList) {
|
||||
if (!allowedAttribute(attribute, allowedAttributes)) {
|
||||
|
||||
+142
-68
@@ -3,75 +3,149 @@
|
||||
'use strict'
|
||||
|
||||
const browsers = {
|
||||
safariMac: {
|
||||
base: 'BrowserStack',
|
||||
os: 'OS X',
|
||||
os_version: 'Catalina',
|
||||
browser: 'Safari',
|
||||
browser_version: 'latest'
|
||||
browserStack: {
|
||||
safariMac: {
|
||||
base: 'BrowserStack',
|
||||
os: 'OS X',
|
||||
os_version: 'Catalina',
|
||||
browser: 'Safari',
|
||||
browser_version: 'latest'
|
||||
},
|
||||
chromeMac: {
|
||||
base: 'BrowserStack',
|
||||
os: 'OS X',
|
||||
os_version: 'Catalina',
|
||||
browser: 'Chrome',
|
||||
browser_version: 'latest'
|
||||
},
|
||||
firefoxMac: {
|
||||
base: 'BrowserStack',
|
||||
os: 'OS X',
|
||||
os_version: 'Catalina',
|
||||
browser: 'Firefox',
|
||||
browser_version: 'latest'
|
||||
},
|
||||
chromeWin10: {
|
||||
base: 'BrowserStack',
|
||||
os: 'Windows',
|
||||
os_version: '10',
|
||||
browser: 'Chrome',
|
||||
browser_version: '60'
|
||||
},
|
||||
firefoxWin10: {
|
||||
base: 'BrowserStack',
|
||||
os: 'Windows',
|
||||
os_version: '10',
|
||||
browser: 'Firefox',
|
||||
browser_version: '60'
|
||||
},
|
||||
chromeWin10Latest: {
|
||||
base: 'BrowserStack',
|
||||
os: 'Windows',
|
||||
os_version: '10',
|
||||
browser: 'Chrome',
|
||||
browser_version: 'latest'
|
||||
},
|
||||
firefoxWin10Latest: {
|
||||
base: 'BrowserStack',
|
||||
os: 'Windows',
|
||||
os_version: '10',
|
||||
browser: 'Firefox',
|
||||
browser_version: 'latest'
|
||||
},
|
||||
iphone7: {
|
||||
base: 'BrowserStack',
|
||||
os: 'ios',
|
||||
os_version: '12.0',
|
||||
device: 'iPhone 7',
|
||||
real_mobile: true
|
||||
},
|
||||
iphone12: {
|
||||
base: 'BrowserStack',
|
||||
os: 'ios',
|
||||
os_version: '14.0',
|
||||
device: 'iPhone 12',
|
||||
real_mobile: true
|
||||
},
|
||||
pixel3: {
|
||||
base: 'BrowserStack',
|
||||
os: 'android',
|
||||
os_version: '9.0',
|
||||
device: 'Google Pixel 3',
|
||||
real_mobile: true
|
||||
}
|
||||
},
|
||||
chromeMac: {
|
||||
base: 'BrowserStack',
|
||||
os: 'OS X',
|
||||
os_version: 'Catalina',
|
||||
browser: 'Chrome',
|
||||
browser_version: 'latest'
|
||||
},
|
||||
firefoxMac: {
|
||||
base: 'BrowserStack',
|
||||
os: 'OS X',
|
||||
os_version: 'Catalina',
|
||||
browser: 'Firefox',
|
||||
browser_version: 'latest'
|
||||
},
|
||||
chromeWin10: {
|
||||
base: 'BrowserStack',
|
||||
os: 'Windows',
|
||||
os_version: '10',
|
||||
browser: 'Chrome',
|
||||
browser_version: '60'
|
||||
},
|
||||
firefoxWin10: {
|
||||
base: 'BrowserStack',
|
||||
os: 'Windows',
|
||||
os_version: '10',
|
||||
browser: 'Firefox',
|
||||
browser_version: '60'
|
||||
},
|
||||
chromeWin10Latest: {
|
||||
base: 'BrowserStack',
|
||||
os: 'Windows',
|
||||
os_version: '10',
|
||||
browser: 'Chrome',
|
||||
browser_version: 'latest'
|
||||
},
|
||||
firefoxWin10Latest: {
|
||||
base: 'BrowserStack',
|
||||
os: 'Windows',
|
||||
os_version: '10',
|
||||
browser: 'Firefox',
|
||||
browser_version: 'latest'
|
||||
},
|
||||
iphone7: {
|
||||
base: 'BrowserStack',
|
||||
os: 'ios',
|
||||
os_version: '12.0',
|
||||
device: 'iPhone 7',
|
||||
real_mobile: true
|
||||
},
|
||||
iphone12: {
|
||||
base: 'BrowserStack',
|
||||
os: 'ios',
|
||||
os_version: '14.0',
|
||||
device: 'iPhone 12',
|
||||
real_mobile: true
|
||||
},
|
||||
pixel2: {
|
||||
base: 'BrowserStack',
|
||||
os: 'android',
|
||||
os_version: '8.0',
|
||||
device: 'Google Pixel 2',
|
||||
real_mobile: true
|
||||
lambdaTest: {
|
||||
safariMac: {
|
||||
browserName: 'Safari',
|
||||
browserVersion: 'latest',
|
||||
'LT:Options': {
|
||||
platformName: 'MacOS Monterey'
|
||||
}
|
||||
},
|
||||
chromeMac: {
|
||||
browserName: 'Chrome',
|
||||
browserVersion: 'latest',
|
||||
'LT:Options': {
|
||||
platformName: 'MacOS Monterey'
|
||||
}
|
||||
},
|
||||
firefoxMac: {
|
||||
browserName: 'Firefox',
|
||||
browserVersion: 'latest',
|
||||
'LT:Options': {
|
||||
platformName: 'MacOS Monterey'
|
||||
}
|
||||
},
|
||||
chromeWin10: {
|
||||
browserName: 'Chrome',
|
||||
browserVersion: '60',
|
||||
'LT:Options': {
|
||||
platformName: 'Windows 10'
|
||||
}
|
||||
},
|
||||
firefoxWin10: {
|
||||
browserName: 'Firefox',
|
||||
browserVersion: '60',
|
||||
'LT:Options': {
|
||||
platformName: 'Windows 10'
|
||||
}
|
||||
},
|
||||
chromeWin10Latest: {
|
||||
browserName: 'Chrome',
|
||||
browserVersion: 'latest',
|
||||
'LT:Options': {
|
||||
platformName: 'Windows 10'
|
||||
}
|
||||
},
|
||||
firefoxWin10Latest: {
|
||||
browserName: 'Firefox',
|
||||
browserVersion: 'latest',
|
||||
'LT:Options': {
|
||||
platformName: 'Windows 10'
|
||||
}
|
||||
},
|
||||
ios15: {
|
||||
platformName: 'ios',
|
||||
platformVersion: '15',
|
||||
browserName: 'Safari',
|
||||
deviceName: 'iPhone.*',
|
||||
isRealMobile: true
|
||||
},
|
||||
ios16: {
|
||||
platformName: 'ios',
|
||||
platformVersion: '16',
|
||||
browserName: 'Safari',
|
||||
deviceName: 'iPhone.*',
|
||||
isRealMobile: true
|
||||
},
|
||||
android12: {
|
||||
platformName: 'android',
|
||||
platformVersion: '12',
|
||||
browserName: 'Chrome',
|
||||
deviceName: 'Pixel.*',
|
||||
isRealMobile: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable import/no-unassigned-import */
|
||||
/* eslint-disable import/extensions, import/no-unassigned-import */
|
||||
|
||||
import Tooltip from '../../dist/tooltip.js'
|
||||
import '../../dist/carousel.js'
|
||||
import Tooltip from '../../dist/tooltip'
|
||||
import '../../dist/carousel'
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
[].concat(...document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Tooltip } from '../../../dist/js/bootstrap.esm.js'
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
[...document.querySelectorAll('[data-bs-toggle="tooltip"]')]
|
||||
[].concat(...document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
.map(tooltipNode => new Tooltip(tooltipNode))
|
||||
})
|
||||
|
||||
+62
-9
@@ -9,9 +9,21 @@ const replace = require('@rollup/plugin-replace')
|
||||
const { browsers } = require('./browsers.js')
|
||||
|
||||
const ENV = process.env
|
||||
const BROWSERSTACK = Boolean(ENV.BROWSERSTACK)
|
||||
const DEBUG = Boolean(ENV.DEBUG)
|
||||
const JQUERY_TEST = Boolean(ENV.JQUERY)
|
||||
const BROWSERSTACK = Boolean(ENV.BROWSERSTACK)
|
||||
const LAMBDATEST = Boolean(ENV.LAMBDATEST)
|
||||
|
||||
const webDriverConfig = {
|
||||
desktop: {
|
||||
hostname: 'hub.lambdatest.com',
|
||||
port: 80
|
||||
},
|
||||
mobile: {
|
||||
hostname: 'mobile-hub.lambdatest.com',
|
||||
port: 80
|
||||
}
|
||||
}
|
||||
|
||||
const frameworks = [
|
||||
'jasmine'
|
||||
@@ -54,15 +66,23 @@ const config = {
|
||||
colors: true,
|
||||
autoWatch: false,
|
||||
singleRun: true,
|
||||
concurrency: Number.POSITIVE_INFINITY,
|
||||
captureTimeout: 180_000,
|
||||
browserDisconnectTolerance: 3,
|
||||
browserDisconnectTimeout: 180_000,
|
||||
browserNoActivityTimeout: 400_000,
|
||||
retryLimit: 2,
|
||||
concurrency: 5,
|
||||
client: {
|
||||
clearContext: false
|
||||
clearContext: false,
|
||||
jasmine: {
|
||||
timeoutInterval: 120_000
|
||||
}
|
||||
},
|
||||
files: [
|
||||
'node_modules/hammer-simulator/index.js',
|
||||
{
|
||||
pattern: 'js/tests/unit/**/!(jquery).spec.js',
|
||||
watched: !BROWSERSTACK
|
||||
watched: !BROWSERSTACK && !LAMBDATEST
|
||||
}
|
||||
],
|
||||
preprocessors: {
|
||||
@@ -98,18 +118,51 @@ const config = {
|
||||
}
|
||||
}
|
||||
|
||||
if (BROWSERSTACK) {
|
||||
if (LAMBDATEST) {
|
||||
config.hostname = 'localhost.lambdatest.com'
|
||||
config.captureTimeout = 600_000
|
||||
|
||||
for (const key of Object.keys(browsers.lambdaTest)) {
|
||||
browsers.lambdaTest[key].base = 'WebDriver'
|
||||
browsers.lambdaTest[key].build = `bootstrap-${ENV.GITHUB_SHA ? `${ENV.GITHUB_SHA.slice(0, 7)}-` : ''}${new Date().toISOString()}`
|
||||
browsers.lambdaTest[key].project = 'Bootstrap'
|
||||
|
||||
if (browsers.lambdaTest[key].isRealMobile) {
|
||||
browsers.lambdaTest[key].config = webDriverConfig.mobile
|
||||
browsers.lambdaTest[key].user = ENV.LT_USERNAME
|
||||
browsers.lambdaTest[key].accessKey = ENV.LT_ACCESS_KEY
|
||||
browsers.lambdaTest[key].tunnel = true
|
||||
browsers.lambdaTest[key].console = true
|
||||
browsers.lambdaTest[key].network = true
|
||||
browsers.lambdaTest[key].tunnelName = ENV.LT_TUNNEL_NAME || 'jasmine'
|
||||
browsers.lambdaTest[key].pseudoActivityInterval = 5000 // 5000 ms heartbeat
|
||||
} else {
|
||||
browsers.lambdaTest[key].config = webDriverConfig.desktop
|
||||
browsers.lambdaTest[key]['LT:Options'].username = ENV.LT_USERNAME
|
||||
browsers.lambdaTest[key]['LT:Options'].accessKey = ENV.LT_ACCESS_KEY
|
||||
browsers.lambdaTest[key]['LT:Options'].tunnel = true
|
||||
browsers.lambdaTest[key]['LT:Options'].console = true
|
||||
browsers.lambdaTest[key]['LT:Options'].network = true
|
||||
browsers.lambdaTest[key]['LT:Options'].tunnelName = ENV.LT_TUNNEL_NAME || 'jasmine'
|
||||
browsers.lambdaTest[key]['LT:Options'].pseudoActivityInterval = 5000 // 5000 ms heartbeat
|
||||
}
|
||||
}
|
||||
|
||||
plugins.push('karma-webdriver-launcher', 'karma-jasmine-html-reporter')
|
||||
config.customLaunchers = browsers.lambdaTest
|
||||
config.browsers = Object.keys(browsers.lambdaTest)
|
||||
reporters.push('kjhtml')
|
||||
} else if (BROWSERSTACK) {
|
||||
config.hostname = ip.address()
|
||||
config.browserStack = {
|
||||
username: ENV.BROWSER_STACK_USERNAME,
|
||||
accessKey: ENV.BROWSER_STACK_ACCESS_KEY,
|
||||
build: `bootstrap-${ENV.GITHUB_SHA ? `${ENV.GITHUB_SHA.slice(0, 7)}-` : ''}${new Date().toISOString()}`,
|
||||
project: 'Bootstrap',
|
||||
retryLimit: 2
|
||||
project: 'Bootstrap'
|
||||
}
|
||||
plugins.push('karma-browserstack-launcher', 'karma-jasmine-html-reporter')
|
||||
config.customLaunchers = browsers
|
||||
config.browsers = Object.keys(browsers)
|
||||
config.customLaunchers = browsers.browserStack
|
||||
config.browsers = Object.keys(browsers.browserStack)
|
||||
reporters.push('BrowserStack', 'kjhtml')
|
||||
} else if (JQUERY_TEST) {
|
||||
frameworks.push('detectBrowsers')
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/* eslint-env node */
|
||||
/* eslint-disable no-console */
|
||||
|
||||
'use strict'
|
||||
|
||||
const process = require('node:process')
|
||||
const lambdaTunnel = require('@lambdatest/node-tunnel')
|
||||
|
||||
if (process.env.LAMBDATEST !== 'true') {
|
||||
console.log('Skipping lt-local script!')
|
||||
return
|
||||
}
|
||||
|
||||
const tunnelInstance = new lambdaTunnel() // eslint-disable-line new-cap
|
||||
const tunnelArguments = {
|
||||
user: process.env.LT_USERNAME,
|
||||
key: process.env.LT_ACCESS_KEY,
|
||||
tunnelName: process.env.LT_TUNNEL_NAME || 'jasmine',
|
||||
logFile: 'local.log'
|
||||
};
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-top-level-await
|
||||
(async () => {
|
||||
await tunnelInstance.start(tunnelArguments)
|
||||
await new Promise(res => {
|
||||
setTimeout(res, 5000)
|
||||
})
|
||||
})()
|
||||
@@ -153,7 +153,7 @@ describe('Collapse', () => {
|
||||
const collapseEl1 = fixtureEl.querySelector('#collapse1')
|
||||
const collapseEl2 = fixtureEl.querySelector('#collapse2')
|
||||
|
||||
const collapseList = [...fixtureEl.querySelectorAll('.collapse')]
|
||||
const collapseList = [].concat(...fixtureEl.querySelectorAll('.collapse'))
|
||||
.map(el => new Collapse(el, {
|
||||
parent,
|
||||
toggle: false
|
||||
|
||||
@@ -68,7 +68,7 @@ describe('SelectorEngine', () => {
|
||||
].join('')
|
||||
|
||||
const list = fixtureEl.querySelector('ul')
|
||||
const liList = [...fixtureEl.querySelectorAll('li')]
|
||||
const liList = [].concat(...fixtureEl.querySelectorAll('li'))
|
||||
const result = SelectorEngine.children(list, 'li')
|
||||
|
||||
expect(result).toEqual(liList)
|
||||
@@ -356,7 +356,7 @@ describe('SelectorEngine', () => {
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual([...fixtureEl.querySelectorAll('.target')])
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target')))
|
||||
})
|
||||
|
||||
it('should get elements if several ids are given', () => {
|
||||
@@ -368,7 +368,7 @@ describe('SelectorEngine', () => {
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual([...fixtureEl.querySelectorAll('.target')])
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target')))
|
||||
})
|
||||
|
||||
it('should get elements if several ids with special chars are given', () => {
|
||||
@@ -380,7 +380,7 @@ describe('SelectorEngine', () => {
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual([...fixtureEl.querySelectorAll('.target')])
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target')))
|
||||
})
|
||||
|
||||
it('should get elements in array, from href if no data-bs-target set', () => {
|
||||
@@ -392,7 +392,7 @@ describe('SelectorEngine', () => {
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual([...fixtureEl.querySelectorAll('.target')])
|
||||
expect(SelectorEngine.getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target')))
|
||||
})
|
||||
|
||||
it('should return empty array if elements not found', () => {
|
||||
|
||||
@@ -217,13 +217,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
for (const popoverEl of document.querySelectorAll('[data-bs-toggle="popover"]')) {
|
||||
new bootstrap.Popover(popoverEl)
|
||||
}
|
||||
document.querySelectorAll('[data-bs-toggle="popover"]').forEach(popoverEl => new bootstrap.Popover(popoverEl))
|
||||
|
||||
for (const tooltipEl of document.querySelectorAll('[data-bs-toggle="tooltip"]')) {
|
||||
new bootstrap.Tooltip(tooltipEl)
|
||||
}
|
||||
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(tooltipEl => new bootstrap.Tooltip(tooltipEl))
|
||||
|
||||
const tall = document.getElementById('tall')
|
||||
document.getElementById('tall-toggle').addEventListener('click', () => {
|
||||
|
||||
@@ -35,9 +35,7 @@
|
||||
<script>
|
||||
/* global bootstrap: false */
|
||||
|
||||
for (const popoverEl of document.querySelectorAll('[data-bs-toggle="popover"]')) {
|
||||
new bootstrap.Popover(popoverEl)
|
||||
}
|
||||
document.querySelectorAll('[data-bs-toggle="popover"]').forEach(popoverEl => new bootstrap.Popover(popoverEl))
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -55,20 +55,14 @@
|
||||
/* global bootstrap: false */
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
for (const toastEl of document.querySelectorAll('.toast')) {
|
||||
new bootstrap.Toast(toastEl)
|
||||
}
|
||||
document.querySelectorAll('.toast').forEach(toastEl => new bootstrap.Toast(toastEl))
|
||||
|
||||
document.getElementById('btnShowToast').addEventListener('click', () => {
|
||||
for (const toastEl of document.querySelectorAll('.toast')) {
|
||||
bootstrap.Toast.getInstance(toastEl).show()
|
||||
}
|
||||
document.querySelectorAll('.toast').forEach(toastEl => bootstrap.Toast.getInstance(toastEl).show())
|
||||
})
|
||||
|
||||
document.getElementById('btnHideToast').addEventListener('click', () => {
|
||||
for (const toastEl of document.querySelectorAll('.toast')) {
|
||||
bootstrap.Toast.getInstance(toastEl).hide()
|
||||
}
|
||||
document.querySelectorAll('.toast').forEach(toastEl => bootstrap.Toast.getInstance(toastEl).hide())
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -113,9 +113,7 @@
|
||||
})
|
||||
targetTooltip.show()
|
||||
|
||||
for (const tooltipEl of document.querySelectorAll('[data-bs-toggle="tooltip"]')) {
|
||||
new bootstrap.Tooltip(tooltipEl)
|
||||
}
|
||||
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(tooltipEl => new bootstrap.Tooltip(tooltipEl))
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
Generated
+1752
-408
File diff suppressed because it is too large
Load Diff
+9
-3
@@ -72,6 +72,9 @@
|
||||
"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 BROWSERSTACK=true npm run js-test-karma",
|
||||
"js-test-lt-cloud": "cross-env LAMBDATEST=true npm-run-all --parallel --race js-test-lt-local js-test-karma",
|
||||
"js-test-lt-local": "npm-run-all js-test-lt-tunnel sleep",
|
||||
"js-test-lt-tunnel": "node js/tests/lt-local",
|
||||
"js-test-jquery": "cross-env JQUERY=true npm run js-test-karma",
|
||||
"lint": "npm-run-all --aggregate-output --continue-on-error --parallel js-lint css-lint lockfile-lint",
|
||||
"docs": "npm-run-all docs-build docs-lint",
|
||||
@@ -82,7 +85,7 @@
|
||||
"docs-serve": "hugo server --port 9001 --disableFastRender --noHTTPCache --renderToMemory --printPathWarnings --printUnusedTemplates",
|
||||
"docs-serve-only": "npx sirv-cli _site --port 9001",
|
||||
"lockfile-lint": "lockfile-lint --allowed-hosts npm --allowed-schemes https: --empty-hostname false --type npm --path package-lock.json",
|
||||
"update-deps": "ncu -u -x globby,jasmine,karma-browserstack-launcher,karma-rollup-preprocessor && echo Manually update site/assets/js/vendor",
|
||||
"update-deps": "ncu -u -x globby,jasmine,karma-rollup-preprocessor && echo Manually update site/assets/js/vendor",
|
||||
"release": "npm-run-all dist release-sri docs-build release-zip*",
|
||||
"release-sri": "node build/generate-sri.mjs",
|
||||
"release-version": "node build/change-version.mjs",
|
||||
@@ -97,7 +100,8 @@
|
||||
"watch-css-docs": "nodemon --watch site/assets/scss/ --ext scss --exec \"npm run css-lint\"",
|
||||
"watch-css-test": "nodemon --watch scss/ --ext scss,js --exec \"npm run css-test\"",
|
||||
"watch-js-main": "nodemon --watch js/src/ --ext js --exec \"npm-run-all js-lint js-compile\"",
|
||||
"watch-js-docs": "nodemon --watch site/assets/js/ --ext js --exec \"npm run js-lint\""
|
||||
"watch-js-docs": "nodemon --watch site/assets/js/ --ext js --exec \"npm run js-lint\"",
|
||||
"sleep": "sleep 5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@popperjs/core": "^2.11.8"
|
||||
@@ -106,6 +110,7 @@
|
||||
"@babel/cli": "^7.23.9",
|
||||
"@babel/core": "^7.24.0",
|
||||
"@babel/preset-env": "^7.24.0",
|
||||
"@lambdatest/node-tunnel": "^4.0.7",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@rollup/plugin-babel": "^6.0.4",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
@@ -129,7 +134,7 @@
|
||||
"jasmine": "^5.1.0",
|
||||
"jquery": "^3.7.1",
|
||||
"karma": "^6.4.3",
|
||||
"karma-browserstack-launcher": "1.4.0",
|
||||
"karma-browserstack-launcher": "^1.6.0",
|
||||
"karma-chrome-launcher": "^3.2.0",
|
||||
"karma-coverage-istanbul-reporter": "^3.0.3",
|
||||
"karma-detect-browsers": "^2.3.3",
|
||||
@@ -137,6 +142,7 @@
|
||||
"karma-jasmine": "^5.1.0",
|
||||
"karma-jasmine-html-reporter": "^2.1.0",
|
||||
"karma-rollup-preprocessor": "7.0.7",
|
||||
"karma-webdriver-launcher": "^1.0.8",
|
||||
"lockfile-lint": "^4.13.2",
|
||||
"nodemon": "^3.1.0",
|
||||
"npm-run-all2": "^6.1.2",
|
||||
|
||||
@@ -29,13 +29,14 @@
|
||||
].join('')
|
||||
|
||||
// Wrap programmatically code blocks and add copy btn.
|
||||
for (const element of document.querySelectorAll('.highlight')) {
|
||||
// Ignore examples made by shortcode
|
||||
if (!element.closest('.bd-example-snippet')) {
|
||||
element.insertAdjacentHTML('beforebegin', btnHtml)
|
||||
element.previousElementSibling.append(element)
|
||||
}
|
||||
}
|
||||
document.querySelectorAll('.highlight')
|
||||
.forEach(element => {
|
||||
// Ignore examples made by shortcode
|
||||
if (!element.closest('.bd-example-snippet')) {
|
||||
element.insertAdjacentHTML('beforebegin', btnHtml)
|
||||
element.previousElementSibling.append(element)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -43,9 +44,9 @@
|
||||
* @param {string} title
|
||||
*/
|
||||
function snippetButtonTooltip(selector, title) {
|
||||
for (const btn of document.querySelectorAll(selector)) {
|
||||
document.querySelectorAll(selector).forEach(btn => {
|
||||
bootstrap.Tooltip.getOrCreateInstance(btn, { title })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
snippetButtonTooltip('.btn-clipboard', btnTitle)
|
||||
|
||||
+32
-26
@@ -20,17 +20,19 @@
|
||||
// Tooltips
|
||||
// --------
|
||||
// Instantiate all tooltips in a docs or StackBlitz
|
||||
for (const tooltip of document.querySelectorAll('[data-bs-toggle="tooltip"]')) {
|
||||
new bootstrap.Tooltip(tooltip)
|
||||
}
|
||||
document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
||||
.forEach(tooltip => {
|
||||
new bootstrap.Tooltip(tooltip)
|
||||
})
|
||||
|
||||
// --------
|
||||
// Popovers
|
||||
// --------
|
||||
// Instantiate all popovers in docs or StackBlitz
|
||||
for (const popover of document.querySelectorAll('[data-bs-toggle="popover"]')) {
|
||||
new bootstrap.Popover(popover)
|
||||
}
|
||||
document.querySelectorAll('[data-bs-toggle="popover"]')
|
||||
.forEach(popover => {
|
||||
new bootstrap.Popover(popover)
|
||||
})
|
||||
|
||||
// -------------------------------
|
||||
// Toasts
|
||||
@@ -48,13 +50,14 @@
|
||||
}
|
||||
|
||||
// Instantiate all toasts in docs pages only
|
||||
for (const toastEl of document.querySelectorAll('.bd-example .toast')) {
|
||||
const toast = new bootstrap.Toast(toastEl, {
|
||||
autohide: false
|
||||
})
|
||||
document.querySelectorAll('.bd-example .toast')
|
||||
.forEach(toastNode => {
|
||||
const toast = new bootstrap.Toast(toastNode, {
|
||||
autohide: false
|
||||
})
|
||||
|
||||
toast.show()
|
||||
}
|
||||
toast.show()
|
||||
})
|
||||
|
||||
// Instantiate all toasts in docs pages only
|
||||
// js-docs-start live-toast
|
||||
@@ -100,29 +103,32 @@
|
||||
// Carousels
|
||||
// --------
|
||||
// Instantiate all non-autoplaying carousels in docs or StackBlitz
|
||||
for (const carousel of document.querySelectorAll('.carousel:not([data-bs-ride="carousel"])')) {
|
||||
bootstrap.Carousel.getOrCreateInstance(carousel)
|
||||
}
|
||||
document.querySelectorAll('.carousel:not([data-bs-ride="carousel"])')
|
||||
.forEach(carousel => {
|
||||
bootstrap.Carousel.getOrCreateInstance(carousel)
|
||||
})
|
||||
|
||||
// -------------------------------
|
||||
// Checks & Radios
|
||||
// -------------------------------
|
||||
// Indeterminate checkbox example in docs and StackBlitz
|
||||
for (const checkbox of document.querySelectorAll('.bd-example-indeterminate [type="checkbox"]')) {
|
||||
if (checkbox.id.includes('Indeterminate')) {
|
||||
checkbox.indeterminate = true
|
||||
}
|
||||
}
|
||||
document.querySelectorAll('.bd-example-indeterminate [type="checkbox"]')
|
||||
.forEach(checkbox => {
|
||||
if (checkbox.id.includes('Indeterminate')) {
|
||||
checkbox.indeterminate = true
|
||||
}
|
||||
})
|
||||
|
||||
// -------------------------------
|
||||
// Links
|
||||
// -------------------------------
|
||||
// Disable empty links in docs examples only
|
||||
for (const link of document.querySelectorAll('.bd-content [href="#"]')) {
|
||||
link.addEventListener('click', event => {
|
||||
event.preventDefault()
|
||||
document.querySelectorAll('.bd-content [href="#"]')
|
||||
.forEach(link => {
|
||||
link.addEventListener('click', event => {
|
||||
event.preventDefault()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// Modal
|
||||
@@ -155,10 +161,10 @@
|
||||
// 'Offcanvas components' example in docs only
|
||||
const myOffcanvas = document.querySelectorAll('.bd-example-offcanvas .offcanvas')
|
||||
if (myOffcanvas) {
|
||||
for (const offcanvas of myOffcanvas) {
|
||||
myOffcanvas.forEach(offcanvas => {
|
||||
offcanvas.addEventListener('show.bs.offcanvas', event => {
|
||||
event.preventDefault()
|
||||
}, false)
|
||||
}
|
||||
})
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
--docsearch-muted-color: var(--bs-secondary-color);
|
||||
--docsearch-hit-shadow: none;
|
||||
|
||||
position: fixed;
|
||||
z-index: 2000; // Make sure to be over all components showcased in the documentation
|
||||
cursor: auto; // Needed because of [role="button"] in Algolia search modal. Remove once https://github.com/algolia/docsearch/issues/1370 is tackled.
|
||||
|
||||
|
||||
@@ -215,8 +215,8 @@ This makes an alert listen for click events on descendant elements which have th
|
||||
Basic usage:
|
||||
|
||||
```js
|
||||
const myAlert = bootstrap.Alert.getOrCreateInstance('#myAlert')
|
||||
myAlert.close()
|
||||
const alert = bootstrap.Alert.getOrCreateInstance('#myAlert')
|
||||
alert.close()
|
||||
```
|
||||
|
||||
### Events
|
||||
|
||||
@@ -223,10 +223,10 @@ const bsButton = new bootstrap.Button('#myButton')
|
||||
For example, to toggle all buttons
|
||||
|
||||
```js
|
||||
for (const buttonElement of document.querySelectorAll('.btn')) {
|
||||
document.querySelectorAll('.btn').forEach(buttonElement => {
|
||||
const button = bootstrap.Button.getOrCreateInstance(buttonElement)
|
||||
button.toggle()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## CSS
|
||||
|
||||
@@ -413,14 +413,14 @@ Enable tabbable list item via JavaScript (each list item needs to be activated i
|
||||
|
||||
```js
|
||||
const triggerTabList = document.querySelectorAll('#myTab a')
|
||||
for (const triggerEl of triggerTabList) {
|
||||
triggerTabList.forEach(triggerEl => {
|
||||
const tabTrigger = new bootstrap.Tab(triggerEl)
|
||||
|
||||
triggerEl.addEventListener('click', event => {
|
||||
event.preventDefault()
|
||||
tabTrigger.show()
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
You can activate individual list item in several ways:
|
||||
@@ -491,10 +491,10 @@ If no tab was already active, then the `hide.bs.tab` and `hidden.bs.tab` events
|
||||
|
||||
```js
|
||||
const tabElms = document.querySelectorAll('a[data-bs-toggle="list"]')
|
||||
for (const tabElm of tabElms) {
|
||||
tabElms.forEach(tabElm => {
|
||||
tabElm.addEventListener('shown.bs.tab', event => {
|
||||
event.target // newly activated tab
|
||||
event.relatedTarget // previous active tab
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
@@ -613,14 +613,14 @@ Enable tabbable tabs via JavaScript (each tab needs to be activated individually
|
||||
|
||||
```js
|
||||
const triggerTabList = document.querySelectorAll('#myTab button')
|
||||
for (const triggerEl of triggerTabList) {
|
||||
triggerTabList.forEach(triggerEl => {
|
||||
const tabTrigger = new bootstrap.Tab(triggerEl)
|
||||
|
||||
triggerEl.addEventListener('click', event => {
|
||||
event.preventDefault()
|
||||
tabTrigger.show()
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
You can activate individual tabs in several ways:
|
||||
|
||||
@@ -333,13 +333,13 @@ Scrollspy is not limited to nav components and list groups, so it will work on a
|
||||
Target elements that aren’t visible will be ignored and their corresponding nav items won't receive an `.active` class. Scrollspy instances initialized in a non-visible wrapper will ignore all target elements. Use the `refresh` method to check for observable elements once the wrapper becomes visible.
|
||||
|
||||
```js
|
||||
for (const el of document.querySelectorAll('#nav-tab > [data-bs-toggle="tab"]')) {
|
||||
document.querySelectorAll('#nav-tab>[data-bs-toggle="tab"]').forEach(el => {
|
||||
el.addEventListener('shown.bs.tab', () => {
|
||||
const target = el.getAttribute('data-bs-target')
|
||||
const scrollElem = document.querySelector(`${target} [data-bs-spy="scroll"]`)
|
||||
bootstrap.ScrollSpy.getOrCreateInstance(scrollElem).refresh()
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Usage
|
||||
@@ -406,9 +406,9 @@ Here's an example using the refresh method:
|
||||
|
||||
```js
|
||||
const dataSpyList = document.querySelectorAll('[data-bs-spy="scroll"]')
|
||||
for (const dataSpyEl of dataSpyList) {
|
||||
dataSpyList.forEach(dataSpyEl => {
|
||||
bootstrap.ScrollSpy.getInstance(dataSpyEl).refresh()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Events
|
||||
|
||||
@@ -4,30 +4,34 @@
|
||||
'use strict'
|
||||
|
||||
// Tooltip and popover demos
|
||||
for (const tooltip of document.querySelectorAll('.tooltip-demo')) {
|
||||
new bootstrap.Tooltip(tooltip, {
|
||||
selector: '[data-bs-toggle="tooltip"]'
|
||||
})
|
||||
}
|
||||
|
||||
for (const popover of document.querySelectorAll('[data-bs-toggle="popover"]')) {
|
||||
new bootstrap.Popover(popover)
|
||||
}
|
||||
|
||||
for (const toastEl of document.querySelectorAll('.toast')) {
|
||||
const toast = new bootstrap.Toast(toastEl, {
|
||||
autohide: false
|
||||
document.querySelectorAll('.tooltip-demo')
|
||||
.forEach(tooltip => {
|
||||
new bootstrap.Tooltip(tooltip, {
|
||||
selector: '[data-bs-toggle="tooltip"]'
|
||||
})
|
||||
})
|
||||
|
||||
toast.show()
|
||||
}
|
||||
document.querySelectorAll('[data-bs-toggle="popover"]')
|
||||
.forEach(popover => {
|
||||
new bootstrap.Popover(popover)
|
||||
})
|
||||
|
||||
document.querySelectorAll('.toast')
|
||||
.forEach(toastNode => {
|
||||
const toast = new bootstrap.Toast(toastNode, {
|
||||
autohide: false
|
||||
})
|
||||
|
||||
toast.show()
|
||||
})
|
||||
|
||||
// Disable empty links and submit buttons
|
||||
for (const link of document.querySelectorAll('[href="#"], [type="submit"]')) {
|
||||
link.addEventListener('click', event => {
|
||||
event.preventDefault()
|
||||
document.querySelectorAll('[href="#"], [type="submit"]')
|
||||
.forEach(link => {
|
||||
link.addEventListener('click', event => {
|
||||
event.preventDefault()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function setActiveItem() {
|
||||
const { hash } = window.location
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
const forms = document.querySelectorAll('.needs-validation')
|
||||
|
||||
// Loop over them and prevent submission
|
||||
for (const form of forms) {
|
||||
Array.from(forms).forEach(form => {
|
||||
form.addEventListener('submit', event => {
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault()
|
||||
@@ -15,5 +15,5 @@
|
||||
|
||||
form.classList.add('was-validated')
|
||||
}, false)
|
||||
}
|
||||
})
|
||||
})()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* global bootstrap: false */
|
||||
(() => {
|
||||
'use strict'
|
||||
const tooltipTriggerList = [...document.querySelectorAll('[data-bs-toggle="tooltip"]')]
|
||||
for (const tooltipTriggerEl of tooltipTriggerList) {
|
||||
const tooltipTriggerList = Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
tooltipTriggerList.forEach(tooltipTriggerEl => {
|
||||
new bootstrap.Tooltip(tooltipTriggerEl)
|
||||
}
|
||||
})
|
||||
})()
|
||||
|
||||
@@ -39,9 +39,8 @@ We provide a version of Bootstrap built as `ESM` (`bootstrap.esm.js` and `bootst
|
||||
<script type="module">
|
||||
import { Toast } from 'bootstrap.esm.min.js'
|
||||
|
||||
for (const toastEl of document.querySelectorAll('.toast')) {
|
||||
new Toast(toastEl))
|
||||
}
|
||||
Array.from(document.querySelectorAll('.toast'))
|
||||
.forEach(toastNode => new Toast(toastNode))
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -160,7 +159,7 @@ In addition to the `getInstance` and `getOrCreateInstance` methods, all plugin c
|
||||
const modal = new bootstrap.Modal('#myModal')
|
||||
const dropdown = new bootstrap.Dropdown('[data-bs-toggle="dropdown"]')
|
||||
const offcanvas = bootstrap.Offcanvas.getInstance('#myOffcanvas')
|
||||
const myAlert = bootstrap.Alert.getOrCreateInstance('#myAlert')
|
||||
const alert = bootstrap.Alert.getOrCreateInstance('#myAlert')
|
||||
```
|
||||
|
||||
### Asynchronous functions and transitions
|
||||
|
||||
@@ -79,7 +79,7 @@ With dependencies installed and our project folder ready for us to start coding,
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const path = require('node:path')
|
||||
const path = require('path')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
|
||||
module.exports = {
|
||||
@@ -154,7 +154,7 @@ Importing Bootstrap into Webpack requires the loaders we installed in the first
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const path = require('node:path')
|
||||
const path = require('path')
|
||||
const autoprefixer = require('autoprefixer')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
|
||||
{{ .Content }}
|
||||
|
||||
{{- if hugo.IsProduction -}}
|
||||
{{- if eq hugo.Environment "production" -}}
|
||||
<script src="/docs/{{ .Site.Params.docs_version }}/dist/js/bootstrap.bundle.min.js" {{ printf "integrity=%q" .Site.Params.cdn.js_bundle_hash | safeHTMLAttr }}></script>
|
||||
{{- else -}}
|
||||
<script src="/docs/{{ .Site.Params.docs_version }}/dist/js/bootstrap.bundle.js"></script>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="offcanvas-lg offcanvas-end flex-grow-1" tabindex="-1" id="bdNavbar" aria-labelledby="bdNavbarOffcanvasLabel">
|
||||
<div class="offcanvas-lg offcanvas-end flex-grow-1" tabindex="-1" id="bdNavbar" aria-labelledby="bdNavbarOffcanvasLabel" data-bs-scroll="true">
|
||||
<div class="offcanvas-header px-4 pb-0">
|
||||
<h5 class="offcanvas-title text-white" id="bdNavbarOffcanvasLabel">Bootstrap</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas" aria-label="Close" data-bs-target="#bdNavbar"></button>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{{ if hugo.IsProduction -}}
|
||||
{{ if eq hugo.Environment "production" -}}
|
||||
<script src="/docs/{{ .Site.Params.docs_version }}/dist/js/bootstrap.bundle.min.js" {{ printf "integrity=%q" .Site.Params.cdn.js_bundle_hash | safeHTMLAttr }}></script>
|
||||
{{ else -}}
|
||||
<script src="/docs/{{ .Site.Params.docs_version }}/dist/js/bootstrap.bundle.js"></script>
|
||||
@@ -15,7 +15,7 @@
|
||||
{{- $targetDocsJSPath := path.Join "/docs" .Site.Params.docs_version "assets/js/docs.js" -}}
|
||||
{{- $docsJs := append $js $vendor | resources.Concat $targetDocsJSPath -}}
|
||||
|
||||
{{- if hugo.IsProduction -}}
|
||||
{{- if eq hugo.Environment "production" -}}
|
||||
{{- $docsJs = $docsJs | resources.Minify -}}
|
||||
{{- end }}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@docsearch/css@3">
|
||||
|
||||
{{ if hugo.IsProduction -}}
|
||||
{{ if eq hugo.Environment "production" -}}
|
||||
{{ if eq .Page.Params.direction "rtl" -}}
|
||||
<link href="/docs/{{ .Site.Params.docs_version }}/dist/css/bootstrap.rtl.min.css" rel="stylesheet" {{ printf "integrity=%q" .Site.Params.cdn.css_rtl_hash | safeHTMLAttr }}>
|
||||
{{- else -}}
|
||||
@@ -15,7 +15,7 @@
|
||||
{{- $sassOptions := dict "targetPath" $targetDocsCssPath "outputStyle" "expanded" "precision" 6 -}}
|
||||
{{- $postcssOptions := dict "use" "autoprefixer" "noMap" true -}}
|
||||
|
||||
{{ if hugo.IsProduction -}}
|
||||
{{ if eq hugo.Environment "production" -}}
|
||||
{{- $sassOptions = merge $sassOptions (dict "outputStyle" "compressed") -}}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# www.robotstxt.org
|
||||
|
||||
{{- $isProduction := eq hugo.Environment "production" -}}
|
||||
{{- $isNetlify := eq (getenv "NETLIFY") "true" -}}
|
||||
{{- $allowCrawling := and (not $isNetlify) hugo.IsProduction -}}
|
||||
{{- $allowCrawling := and (not $isNetlify) $isProduction -}}
|
||||
|
||||
{{ if $allowCrawling }}
|
||||
# Allow crawling of all content
|
||||
|
||||
@@ -41,10 +41,10 @@
|
||||
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`)
|
||||
const svgOfActiveBtn = btnToActive.querySelector('svg use').getAttribute('href')
|
||||
|
||||
for (const element of document.querySelectorAll('[data-bs-theme-value]')) {
|
||||
document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
|
||||
element.classList.remove('active')
|
||||
element.setAttribute('aria-pressed', 'false')
|
||||
}
|
||||
})
|
||||
|
||||
btnToActive.classList.add('active')
|
||||
btnToActive.setAttribute('aria-pressed', 'true')
|
||||
@@ -67,13 +67,14 @@
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
showActiveTheme(getPreferredTheme())
|
||||
|
||||
for (const toggle of document.querySelectorAll('[data-bs-theme-value]')) {
|
||||
toggle.addEventListener('click', () => {
|
||||
const theme = toggle.getAttribute('data-bs-theme-value')
|
||||
setStoredTheme(theme)
|
||||
setTheme(theme)
|
||||
showActiveTheme(theme, true)
|
||||
document.querySelectorAll('[data-bs-theme-value]')
|
||||
.forEach(toggle => {
|
||||
toggle.addEventListener('click', () => {
|
||||
const theme = toggle.getAttribute('data-bs-theme-value')
|
||||
setStoredTheme(theme)
|
||||
setTheme(theme)
|
||||
showActiveTheme(theme, true)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})()
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
const forms = document.querySelectorAll('.needs-validation')
|
||||
|
||||
// Loop over them and prevent submission
|
||||
for (const form of forms) {
|
||||
Array.from(forms).forEach(form => {
|
||||
form.addEventListener('submit', event => {
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault()
|
||||
@@ -15,5 +15,5 @@
|
||||
|
||||
form.classList.add('was-validated')
|
||||
}, false)
|
||||
}
|
||||
})
|
||||
})()
|
||||
|
||||
Reference in New Issue
Block a user