diff --git a/.babelrc.js b/.babelrc.js deleted file mode 100644 index 5139969..0000000 --- a/.babelrc.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = { - presets: [ - [ - '@babel/preset-env', - { - loose: true, - bugfixes: true, - modules: false - } - ] - ] -}; diff --git a/.browserslistrc b/.browserslistrc deleted file mode 100644 index cddd230..0000000 --- a/.browserslistrc +++ /dev/null @@ -1,12 +0,0 @@ -# https://github.com/browserslist/browserslist#readme - ->= 0.5% -last 2 major versions -not dead -Chrome >= 60 -Firefox >= 60 -Firefox ESR -iOS >= 12 -Safari >= 12 -not Explorer <= 11 -not kaios <= 2.5 # fix floating label issues in Firefox (see https://github.com/postcss/autoprefixer/issues/1533) diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 235ad54..0000000 --- a/.gitignore +++ /dev/null @@ -1,45 +0,0 @@ -# Ignore docs files -/_site/ - -# Numerous always-ignore extensions -*.diff -*.err -*.log -*.orig -*.rej -*.swo -*.swp -*.vi -*.zip -*~ - -# OS or Editor folders -._* -.cache -.DS_Store -.idea -.project -.settings -.tmproj -*.esproj -*.sublime-project -*.sublime-workspace -nbproject -Thumbs.db -# Local Netlify folder -.netlify - -# Komodo -.komodotools -*.komodoproject - -# Folders to ignore -/dist-sass/ -/js/coverage/ -/node_modules/ - -# Site -/site/dist -/site/node_modules -/site/.astro -/site/public diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 7562983..28fd5e8 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -17,23 +17,23 @@ diverse, inclusive, and healthy community. Examples of behavior that contributes to a positive environment for our community include: -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -- Focusing on what is best not just for us as individuals, but for the overall +* Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: -- The use of sexualized language or imagery, and sexual attention or advances of +* The use of sexualized language or imagery, and sexual attention or advances of any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others’ private information, such as a physical or email address, +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a +* Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities diff --git a/LICENSE b/LICENSE index fa7c00b..dda75ca 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ The MIT License (MIT) -Copyright (c) 2011-2025 The Bootstrap Authors +Copyright (c) 2011-2022 Twitter, Inc. +Copyright (c) 2011-2022 The Bootstrap Authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index e3618e6..bd40192 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- Bootstrap logo + Bootstrap logo

@@ -9,13 +9,15 @@

Sleek, intuitive, and powerful front-end framework for faster and easier web development.
- Explore Bootstrap docs » + Explore Bootstrap docs »

Report bug · Request feature · + Themes + · Blog

@@ -29,7 +31,7 @@ Our default branch is for development of our Bootstrap 5 release. Head to the [` - [Quick start](#quick-start) - [Status](#status) -- [What’s included](#whats-included) +- [What's included](#whats-included) - [Bugs and feature requests](#bugs-and-feature-requests) - [Documentation](#documentation) - [Contributing](#contributing) @@ -44,38 +46,37 @@ Our default branch is for development of our Bootstrap 5 release. Head to the [` Several quick start options are available: -- [Download the latest release](https://github.com/twbs/bootstrap/archive/v5.3.8.zip) +- [Download the latest release](https://github.com/twbs/bootstrap/archive/v5.2.3.zip) - Clone the repo: `git clone https://github.com/twbs/bootstrap.git` -- Install with [npm](https://www.npmjs.com/): `npm install bootstrap@v5.3.8` -- Install with [yarn](https://yarnpkg.com/): `yarn add bootstrap@v5.3.8` -- Install with [Bun](https://bun.sh/): `bun add bootstrap@v5.3.8` -- Install with [Composer](https://getcomposer.org/): `composer require twbs/bootstrap:5.3.8` +- Install with [npm](https://www.npmjs.com/): `npm install bootstrap@v5.2.3` +- Install with [yarn](https://yarnpkg.com/): `yarn add bootstrap@v5.2.3` +- Install with [Composer](https://getcomposer.org/): `composer require twbs/bootstrap:5.2.3` - Install with [NuGet](https://www.nuget.org/): CSS: `Install-Package bootstrap` Sass: `Install-Package bootstrap.sass` -Read the [Getting started page](https://getbootstrap.com/docs/5.3/getting-started/introduction/) for information on the framework contents, templates, examples, and more. +Read the [Getting started page](https://getbootstrap.com/docs/5.2/getting-started/introduction/) for information on the framework contents, templates, examples, and more. ## Status -[![Build Status](https://img.shields.io/github/actions/workflow/status/twbs/bootstrap/js.yml?branch=main&label=JS%20Tests&logo=github)](https://github.com/twbs/bootstrap/actions/workflows/js.yml?query=workflow%3AJS+branch%3Amain) -[![npm version](https://img.shields.io/npm/v/bootstrap?logo=npm&logoColor=fff)](https://www.npmjs.com/package/bootstrap) -[![Gem version](https://img.shields.io/gem/v/bootstrap?logo=rubygems&logoColor=fff)](https://rubygems.org/gems/bootstrap) -[![Meteor Atmosphere](https://img.shields.io/badge/meteor-twbs%3Abootstrap-blue?logo=meteor&logoColor=fff)](https://atmospherejs.com/twbs/bootstrap) -[![Packagist Prerelease](https://img.shields.io/packagist/vpre/twbs/bootstrap?logo=packagist&logoColor=fff)](https://packagist.org/packages/twbs/bootstrap) -[![NuGet](https://img.shields.io/nuget/vpre/bootstrap?logo=nuget&logoColor=fff)](https://www.nuget.org/packages/bootstrap/absoluteLatest) -[![Coverage Status](https://img.shields.io/coveralls/github/twbs/bootstrap/main?logo=coveralls&logoColor=fff)](https://coveralls.io/github/twbs/bootstrap?branch=main) +[![Build Status](https://img.shields.io/github/workflow/status/twbs/bootstrap/JS%20Tests/main?label=JS%20Tests&logo=github)](https://github.com/twbs/bootstrap/actions?query=workflow%3AJS+Tests+branch%3Amain) +[![npm version](https://img.shields.io/npm/v/bootstrap)](https://www.npmjs.com/package/bootstrap) +[![Gem version](https://img.shields.io/gem/v/bootstrap)](https://rubygems.org/gems/bootstrap) +[![Meteor Atmosphere](https://img.shields.io/badge/meteor-twbs%3Abootstrap-blue)](https://atmospherejs.com/twbs/bootstrap) +[![Packagist Prerelease](https://img.shields.io/packagist/vpre/twbs/bootstrap)](https://packagist.org/packages/twbs/bootstrap) +[![NuGet](https://img.shields.io/nuget/vpre/bootstrap)](https://www.nuget.org/packages/bootstrap/absoluteLatest) +[![Coverage Status](https://img.shields.io/coveralls/github/twbs/bootstrap/main)](https://coveralls.io/github/twbs/bootstrap?branch=main) [![CSS gzip size](https://img.badgesize.io/twbs/bootstrap/main/dist/css/bootstrap.min.css?compression=gzip&label=CSS%20gzip%20size)](https://github.com/twbs/bootstrap/blob/main/dist/css/bootstrap.min.css) [![CSS Brotli size](https://img.badgesize.io/twbs/bootstrap/main/dist/css/bootstrap.min.css?compression=brotli&label=CSS%20Brotli%20size)](https://github.com/twbs/bootstrap/blob/main/dist/css/bootstrap.min.css) [![JS gzip size](https://img.badgesize.io/twbs/bootstrap/main/dist/js/bootstrap.min.js?compression=gzip&label=JS%20gzip%20size)](https://github.com/twbs/bootstrap/blob/main/dist/js/bootstrap.min.js) [![JS Brotli size](https://img.badgesize.io/twbs/bootstrap/main/dist/js/bootstrap.min.js?compression=brotli&label=JS%20Brotli%20size)](https://github.com/twbs/bootstrap/blob/main/dist/js/bootstrap.min.js) -![Open Source Security Foundation Scorecard](https://img.shields.io/ossf-scorecard/github.com/twbs/bootstrap) -[![Backers on Open Collective](https://img.shields.io/opencollective/backers/bootstrap?logo=opencollective&logoColor=fff)](#backers) -[![Sponsors on Open Collective](https://img.shields.io/opencollective/sponsors/bootstrap?logo=opencollective&logoColor=fff)](#sponsors) +[![BrowserStack Status](https://www.browserstack.com/automate/badge.svg?badge_key=SkxZcStBeExEdVJqQ2hWYnlWckpkNmNEY213SFp6WHFETWk2bGFuY3pCbz0tLXhqbHJsVlZhQnRBdEpod3NLSDMzaHc9PQ==--3d0b75245708616eb93113221beece33e680b229)](https://www.browserstack.com/automate/public-build/SkxZcStBeExEdVJqQ2hWYnlWckpkNmNEY213SFp6WHFETWk2bGFuY3pCbz0tLXhqbHJsVlZhQnRBdEpod3NLSDMzaHc9PQ==--3d0b75245708616eb93113221beece33e680b229) +[![Backers on Open Collective](https://img.shields.io/opencollective/backers/bootstrap)](#backers) +[![Sponsors on Open Collective](https://img.shields.io/opencollective/sponsors/bootstrap)](#sponsors) -## What’s included +## What's included -Within the download you’ll find the following directories and files, logically grouping common assets and providing both compiled and minified variations. +Within the download you'll find the following directories and files, logically grouping common assets and providing both compiled and minified variations.
Download contents @@ -131,7 +132,7 @@ Within the download you’ll find the following directories and files, logically ```
-We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [Source maps](https://web.dev/articles/source-maps) (`bootstrap.*.map`) are available for use with certain browsers’ developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/docs/v2/). +We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [Source maps](https://developers.google.com/web/tools/chrome-devtools/javascript/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/). ## Bugs and feature requests @@ -141,18 +142,18 @@ Have a bug or a feature request? Please first read the [issue guidelines](https: ## Documentation -Bootstrap’s documentation, included in this repo in the root directory, is built with [Astro](https://astro.build/) and publicly hosted on GitHub Pages at . The docs may also be run locally. +Bootstrap's documentation, included in this repo in the root directory, is built with [Hugo](https://gohugo.io/) and publicly hosted on GitHub Pages at . The docs may also be run locally. -Documentation search is powered by [Algolia's DocSearch](https://docsearch.algolia.com/). +Documentation search is powered by [Algolia's DocSearch](https://docsearch.algolia.com/). Working on our search? Be sure to set `debug: true` in `site/assets/js/search.js`. ### Running documentation locally -1. Run `npm install` to install the Node.js dependencies, including Astro (the site builder). +1. Run `npm install` to install the Node.js dependencies, including Hugo (the site builder). 2. Run `npm run test` (or a specific npm script) to rebuild distributed CSS and JavaScript files, as well as our docs assets. 3. From the root `/bootstrap` directory, run `npm run docs-serve` in the command line. -4. Open in your browser, and voilà. +4. Open `http://localhost:9001/` in your browser, and voilà. -Learn more about using Astro by reading its [documentation](https://docs.astro.build/en/getting-started/). +Learn more about using Hugo by reading its [documentation](https://gohugo.io/documentation/). ### Documentation for previous releases @@ -172,12 +173,11 @@ Editor preferences are available in the [editor config](https://github.com/twbs/ ## Community -Get updates on Bootstrap’s development and chat with the project maintainers and community members. +Get updates on Bootstrap's development and chat with the project maintainers and community members. -- Follow [@getbootstrap on X](https://x.com/getbootstrap). +- Follow [@getbootstrap on Twitter](https://twitter.com/getbootstrap). - Read and subscribe to [The Official Bootstrap Blog](https://blog.getbootstrap.com/). -- Ask questions and explore [our GitHub Discussions](https://github.com/twbs/bootstrap/discussions). -- Discuss, ask questions, and more on [the community Discord](https://discord.gg/bZUvakRU3M) or [Bootstrap subreddit](https://www.reddit.com/r/bootstrap/). +- Ask and explore [our GitHub Discussions](https://github.com/twbs/bootstrap/discussions). - Chat with fellow Bootstrappers in IRC. On the `irc.libera.chat` server, in the `#bootstrap` channel. - Implementation help may be found at Stack Overflow (tagged [`bootstrap-5`](https://stackoverflow.com/questions/tagged/bootstrap-5)). - Developers should use the keyword `bootstrap` on packages which modify or add to the functionality of Bootstrap when distributing through [npm](https://www.npmjs.com/browse/keyword/bootstrap) or similar delivery mechanisms for maximum discoverability. @@ -194,12 +194,12 @@ See [the Releases section of our GitHub project](https://github.com/twbs/bootstr **Mark Otto** -- +- - **Jacob Thornton** -- +- - @@ -243,4 +243,4 @@ Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com ## Copyright and license -Code and documentation copyright 2011-2025 the [Bootstrap Authors](https://github.com/twbs/bootstrap/graphs/contributors). Code released under the [MIT License](https://github.com/twbs/bootstrap/blob/main/LICENSE). Docs released under [Creative Commons](https://creativecommons.org/licenses/by/3.0/). +Code and documentation copyright 2011–2022 the [Bootstrap Authors](https://github.com/twbs/bootstrap/graphs/contributors) and [Twitter, Inc.](https://twitter.com) Code released under the [MIT License](https://github.com/twbs/bootstrap/blob/main/LICENSE). Docs released under [Creative Commons](https://creativecommons.org/licenses/by/3.0/). diff --git a/build/.eslintrc.json b/build/.eslintrc.json new file mode 100644 index 0000000..dec6323 --- /dev/null +++ b/build/.eslintrc.json @@ -0,0 +1,15 @@ +{ + "env": { + "browser": false, + "node": true + }, + "parserOptions": { + "sourceType": "script" + }, + "extends": "../.eslintrc.json", + "rules": { + "no-console": "off", + "strict": "error", + "unicorn/prefer-top-level-await": "off" + } +} diff --git a/build/banner.mjs b/build/banner.js similarity index 50% rename from build/banner.mjs rename to build/banner.js index 3fea93c..df82ff3 100644 --- a/build/banner.mjs +++ b/build/banner.js @@ -1,12 +1,6 @@ -import fs from 'node:fs/promises' -import path from 'node:path' -import { fileURLToPath } from 'node:url' - -const __dirname = path.dirname(fileURLToPath(import.meta.url)) - -const pkgJson = path.join(__dirname, '../package.json') -const pkg = JSON.parse(await fs.readFile(pkgJson, 'utf8')) +'use strict' +const pkg = require('../package.json') const year = new Date().getFullYear() function getBanner(pluginFilename) { @@ -17,4 +11,4 @@ function getBanner(pluginFilename) { */` } -export default getBanner +module.exports = getBanner diff --git a/build/build-plugins.mjs b/build/build-plugins.js similarity index 82% rename from build/build-plugins.mjs rename to build/build-plugins.js index f56cd21..a160209 100644 --- a/build/build-plugins.mjs +++ b/build/build-plugins.js @@ -2,22 +2,21 @@ /*! * Script to build our plugins to use them separately. - * Copyright 2020-2025 The Bootstrap Authors + * Copyright 2020-2022 The Bootstrap Authors + * Copyright 2020-2022 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ -import path from 'node:path' -import { fileURLToPath } from 'node:url' -import { babel } from '@rollup/plugin-babel' -import { globby } from 'globby' -import { rollup } from 'rollup' -import banner from './banner.mjs' +'use strict' -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const path = require('node:path') +const rollup = require('rollup') +const globby = require('globby') +const { babel } = require('@rollup/plugin-babel') +const banner = require('./banner.js') const sourcePath = path.resolve(__dirname, '../js/src/').replace(/\\/g, '/') -const jsFiles = await globby(`${sourcePath}/**/*.js`) +const jsFiles = globby.sync(sourcePath + '/**/*.js') // Array which holds the resolved plugins const resolvedPlugins = [] @@ -28,7 +27,7 @@ const filenameToEntity = filename => filename.replace('.js', '') for (const file of jsFiles) { resolvedPlugins.push({ - src: file, + src: file.replace('.js', ''), dist: file.replace('src', 'dist'), fileName: path.basename(file), className: filenameToEntity(path.basename(file)) @@ -37,12 +36,9 @@ for (const file of jsFiles) { } const build = async plugin => { - /** - * @type {import('rollup').GlobalsOption} - */ const globals = {} - const bundle = await rollup({ + const bundle = await rollup.rollup({ input: plugin.src, plugins: [ babel({ diff --git a/build/change-version.js b/build/change-version.js new file mode 100644 index 0000000..57c5fde --- /dev/null +++ b/build/change-version.js @@ -0,0 +1,81 @@ +#!/usr/bin/env node + +/*! + * Script to update version number references in the project. + * Copyright 2017-2022 The Bootstrap Authors + * Copyright 2017-2022 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ + +'use strict' + +const fs = require('node:fs').promises +const path = require('node:path') +const globby = require('globby') + +const VERBOSE = process.argv.includes('--verbose') +const DRY_RUN = process.argv.includes('--dry') || process.argv.includes('--dry-run') + +// These are the filetypes we only care about replacing the version +const GLOB = [ + '**/*.{css,html,js,json,md,scss,txt,yml}' +] +const GLOBBY_OPTIONS = { + cwd: path.join(__dirname, '..'), + gitignore: true +} + +// Blame TC39... https://github.com/benjamingr/RegExp.escape/issues/37 +function regExpQuote(string) { + return string.replace(/[$()*+-.?[\\\]^{|}]/g, '\\$&') +} + +function regExpQuoteReplacement(string) { + return string.replace(/\$/g, '$$') +} + +async function replaceRecursively(file, oldVersion, newVersion) { + const originalString = await fs.readFile(file, 'utf8') + const newString = originalString.replace( + new RegExp(regExpQuote(oldVersion), 'g'), regExpQuoteReplacement(newVersion) + ) + + // No need to move any further if the strings are identical + if (originalString === newString) { + return + } + + if (VERBOSE) { + console.log(`FILE: ${file}`) + } + + if (DRY_RUN) { + return + } + + await fs.writeFile(file, newString, 'utf8') +} + +async function main(args) { + let [oldVersion, newVersion] = args + + if (!oldVersion || !newVersion) { + console.error('USAGE: change-version old_version new_version [--verbose] [--dry[-run]]') + console.error('Got arguments:', args) + 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 => arg.startsWith('v') ? arg.slice(1) : arg) + + try { + const files = await globby(GLOB, GLOBBY_OPTIONS) + + await Promise.all(files.map(file => replaceRecursively(file, oldVersion, newVersion))) + } catch (error) { + console.error(error) + process.exit(1) + } +} + +main(process.argv.slice(2)) diff --git a/build/change-version.mjs b/build/change-version.mjs deleted file mode 100644 index 1400274..0000000 --- a/build/change-version.mjs +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env node - -/*! - * Script to update version number references in the project. - * Copyright 2017-2025 The Bootstrap Authors - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */ - -import { execFile } from 'node:child_process' -import fs from 'node:fs/promises' -import process from 'node:process' - -const VERBOSE = process.argv.includes('--verbose') -const DRY_RUN = process.argv.includes('--dry') || process.argv.includes('--dry-run') - -// These are the files we only care about replacing the version -const FILES = [ - 'README.md', - 'config.yml', - 'js/src/base-component.js', - 'package.js', - 'scss/mixins/_banner.scss', - 'site/data/docs-versions.yml' -] - -// Blame TC39... https://github.com/benjamingr/RegExp.escape/issues/37 -function regExpQuote(string) { - return string.replace(/[$()*+-.?[\\\]^{|}]/g, '\\$&') -} - -function regExpQuoteReplacement(string) { - return string.replace(/\$/g, '$$') -} - -async function replaceRecursively(file, oldVersion, newVersion) { - const originalString = await fs.readFile(file, 'utf8') - const newString = originalString - .replace( - new RegExp(regExpQuote(oldVersion), 'g'), - regExpQuoteReplacement(newVersion) - ) - // Also replace the version used by the rubygem, - // which is using periods (`.`) instead of hyphens (`-`) - .replace( - new RegExp(regExpQuote(oldVersion.replace(/-/g, '.')), 'g'), - regExpQuoteReplacement(newVersion.replace(/-/g, '.')) - ) - - // No need to move any further if the strings are identical - if (originalString === newString) { - return - } - - if (VERBOSE) { - console.log(`Found ${oldVersion} in ${file}`) - } - - if (DRY_RUN) { - return - } - - await fs.writeFile(file, newString, 'utf8') -} - -function bumpNpmVersion(newVersion) { - if (DRY_RUN) { - return - } - - execFile('npm', ['version', newVersion, '--no-git-tag'], { shell: true }, error => { - if (error) { - console.error(error) - process.exit(1) - } - }) -} - -function showUsage(args) { - console.error('USAGE: change-version old_version new_version [--verbose] [--dry[-run]]') - console.error('Got arguments:', args) - process.exit(1) -} - -async function main(args) { - let [oldVersion, newVersion] = 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) - } -} - -main(process.argv.slice(2)) diff --git a/build/docs-prep.sh b/build/docs-prep.sh deleted file mode 100755 index 357768a..0000000 --- a/build/docs-prep.sh +++ /dev/null @@ -1,157 +0,0 @@ -#!/bin/bash - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Default branch suffix -BRANCH_SUFFIX="release" - -# Check if a custom version parameter was provided -if [ $# -eq 1 ]; then - BRANCH_SUFFIX="$1" -fi - -# Branch name to create -NEW_BRANCH="gh-pages-${BRANCH_SUFFIX}" - -# Get the current docs version from config -DOCS_VERSION=$(node -p "require('js-yaml').load(require('fs').readFileSync('config.yml', 'utf8')).docs_version") - -# Function to print colored messages -print_success() { - echo -e "${GREEN}✓ $1${NC}" -} - -print_error() { - echo -e "${RED}✗ $1${NC}" - exit 1 -} - -print_info() { - echo -e "${BLUE}ℹ $1${NC}" -} - -print_warning() { - echo -e "${YELLOW}⚠ $1${NC}" -} - -# Function to execute command with error handling -execute() { - print_info "Running: $1" - eval $1 - if [ $? -ne 0 ]; then - print_error "Failed to execute: $1" - else - print_success "Successfully executed: $1" - fi -} - -# Check if /tmp/_site directory exists from a previous run -if [ -d "/tmp/_site" ]; then - print_warning "Found existing /tmp/_site directory. Removing it…" - rm -rf /tmp/_site -fi - -# Main process -print_info "Starting documentation deployment process…" - -# Step 1: Build documentation -print_info "Building documentation with npm run docs…" -npm run docs -if [ $? -ne 0 ]; then - print_error "Documentation build failed!" -fi -print_success "Documentation built successfully" - -# Step 2: Move _site to /tmp/ -print_info "Moving _site to temporary location…" -execute "mv _site /tmp/" - -# Step 3: Switch to gh-pages branch -print_info "Checking out gh-pages branch…" -git checkout gh-pages -if [ $? -ne 0 ]; then - print_error "Failed to checkout gh-pages branch. Make sure it exists." -fi -print_success "Switched to gh-pages branch" - -git reset --hard origin/gh-pages -if [ $? -ne 0 ]; then - print_error "Failed to reset to origin/gh-pages. Check your git configuration." -fi -print_success "Reset to origin/gh-pages" - -git pull origin gh-pages -if [ $? -ne 0 ]; then - print_error "Failed to pull from origin/gh-pages. Check your network connection and git configuration." -fi -print_success "Pulled latest changes from origin/gh-pages" - -# Step 4: Create a new branch for the update -print_info "Checking if branch ${NEW_BRANCH} exists and deleting it if it does…" -if git show-ref --verify --quiet refs/heads/${NEW_BRANCH}; then - execute "git branch -D ${NEW_BRANCH}" -else - print_info "Branch ${NEW_BRANCH} does not exist, proceeding with creation…" -fi -print_info "Creating new branch ${NEW_BRANCH}…" -execute "git checkout -b ${NEW_BRANCH}" - -# Step 5: Move all root-level files from Astro build -find /tmp/_site -maxdepth 1 -type f -exec mv {} . \; - -# Step 6: Move all top-level directories except 'docs' (which needs special handling) -find /tmp/_site -maxdepth 1 -type d ! -name "_site" ! -name "docs" -exec sh -c 'dir=$(basename "$1"); rm -rf "$dir"; mv "$1" .' _ {} \; - -# Step 7: Handle docs directory specially -if [ -d "/tmp/_site/docs" ]; then - # Replace only the current version's docs - if [ -d "docs/$DOCS_VERSION" ]; then - rm -rf "docs/$DOCS_VERSION" - fi - mv "/tmp/_site/docs/$DOCS_VERSION" "docs/" - - # Handle docs root files - find /tmp/_site/docs -maxdepth 1 -type f -exec mv {} docs/ \; - - # Handle special docs directories (getting-started, versions) - for special_dir in getting-started versions; do - if [ -d "/tmp/_site/docs/$special_dir" ]; then - rm -rf "docs/$special_dir" - mv "/tmp/_site/docs/$special_dir" "docs/" - fi - done -fi - -# Clean up remaining files in /tmp/_site if any -if [ -d "/tmp/_site" ]; then - remaining_files=$(find /tmp/_site -type f | wc -l) - remaining_dirs=$(find /tmp/_site -type d | wc -l) - if [ $remaining_files -gt 0 ] || [ $remaining_dirs -gt 1 ]; then - print_warning "There are still some files or directories in /tmp/_site that weren't moved." - print_warning "You may want to inspect /tmp/_site to see if anything important was missed." - else - print_info "Cleaning up temporary directory…" - rm -rf /tmp/_site - print_success "Temporary directory cleaned up" - fi -fi - -# Step 10: Remove empty site directory if it exists -if [ -d "site" ]; then - print_info "Removing empty site directory…" - execute "rm -rf site" -fi - -print_success "Docs prep complete!" -print_info "Review changes before committing and pushing." -print_info "Next steps:" -print_info " 1. Run a local server to review changes" -print_info " 2. Check browser and web inspector for any errors" -print_info " 3. git add ." -print_info " 4. git commit -m \"Update documentation\"" -print_info " 5. git push origin ${NEW_BRANCH}" diff --git a/build/generate-sri.mjs b/build/generate-sri.js similarity index 60% rename from build/generate-sri.mjs rename to build/generate-sri.js index 5622843..ef1b39f 100644 --- a/build/generate-sri.mjs +++ b/build/generate-sri.js @@ -5,17 +5,17 @@ * Remember to use the same vendor files as the CDN ones, * otherwise the hashes won't match! * - * Copyright 2017-2025 The Bootstrap Authors + * Copyright 2017-2022 The Bootstrap Authors + * Copyright 2017-2022 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ -import crypto from 'node:crypto' -import fs from 'node:fs' -import path from 'node:path' -import { fileURLToPath } from 'node:url' -import sh from 'shelljs' +'use strict' -const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const crypto = require('node:crypto') +const fs = require('node:fs') +const path = require('node:path') +const sh = require('shelljs') sh.config.fatal = true @@ -47,18 +47,18 @@ const files = [ } ] -for (const { file, configPropertyName } of files) { - fs.readFile(file, 'utf8', (error, data) => { +for (const file of files) { + fs.readFile(file.file, 'utf8', (error, data) => { if (error) { throw error } - const algorithm = 'sha384' - const hash = crypto.createHash(algorithm).update(data, 'utf8').digest('base64') - const integrity = `${algorithm}-${hash}` + const algo = 'sha384' + const hash = crypto.createHash(algo).update(data, 'utf8').digest('base64') + const integrity = `${algo}-${hash}` - console.log(`${configPropertyName}: ${integrity}`) + console.log(`${file.configPropertyName}: ${integrity}`) - sh.sed('-i', new RegExp(`^(\\s+${configPropertyName}:\\s+["'])\\S*(["'])`), `$1${integrity}$2`, configFile) + sh.sed('-i', new RegExp(`^(\\s+${file.configPropertyName}:\\s+["'])\\S*(["'])`), `$1${integrity}$2`, configFile) }) } diff --git a/build/postcss.config.mjs b/build/postcss.config.js similarity index 86% rename from build/postcss.config.mjs rename to build/postcss.config.js index 7717cfc..7f8186d 100644 --- a/build/postcss.config.mjs +++ b/build/postcss.config.js @@ -1,10 +1,12 @@ +'use strict' + const mapConfig = { inline: false, annotation: true, sourcesContent: true } -export default context => { +module.exports = context => { return { map: context.file.dirname.includes('examples') ? false : mapConfig, plugins: { diff --git a/build/rollup.config.mjs b/build/rollup.config.js similarity index 62% rename from build/rollup.config.mjs rename to build/rollup.config.js index dd6c7d1..27f12ac 100644 --- a/build/rollup.config.mjs +++ b/build/rollup.config.js @@ -1,17 +1,15 @@ -import path from 'node:path' -import process from 'node:process' -import { fileURLToPath } from 'node:url' -import { babel } from '@rollup/plugin-babel' -import { nodeResolve } from '@rollup/plugin-node-resolve' -import replace from '@rollup/plugin-replace' -import banner from './banner.mjs' +'use strict' -const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const path = require('node:path') +const { babel } = require('@rollup/plugin-babel') +const { nodeResolve } = require('@rollup/plugin-node-resolve') +const replace = require('@rollup/plugin-replace') +const banner = require('./banner.js') const BUNDLE = process.env.BUNDLE === 'true' const ESM = process.env.ESM === 'true' -let destinationFile = `bootstrap${ESM ? '.esm' : ''}` +let fileDestination = `bootstrap${ESM ? '.esm' : ''}` const external = ['@popperjs/core'] const plugins = [ babel({ @@ -26,7 +24,7 @@ const globals = { } if (BUNDLE) { - destinationFile += '.bundle' + fileDestination += '.bundle' // Remove last entry in external array to bundle Popper external.pop() delete globals['@popperjs/core'] @@ -42,8 +40,8 @@ if (BUNDLE) { const rollupConfig = { input: path.resolve(__dirname, `../js/index.${ESM ? 'esm' : 'umd'}.js`), output: { - banner: banner(), - file: path.resolve(__dirname, `../dist/js/${destinationFile}.js`), + banner, + file: path.resolve(__dirname, `../dist/js/${fileDestination}.js`), format: ESM ? 'esm' : 'umd', globals, generatedCode: 'es2015' @@ -56,4 +54,4 @@ if (!ESM) { rollupConfig.output.name = 'bootstrap' } -export default rollupConfig +module.exports = rollupConfig diff --git a/build/vnu-jar.mjs b/build/vnu-jar.js similarity index 64% rename from build/vnu-jar.mjs rename to build/vnu-jar.js index 4eedb1b..f29eeb7 100644 --- a/build/vnu-jar.mjs +++ b/build/vnu-jar.js @@ -2,22 +2,22 @@ /*! * Script to run vnu-jar if Java is available. - * Copyright 2017-2025 The Bootstrap Authors + * Copyright 2017-2022 The Bootstrap Authors + * Copyright 2017-2022 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ -import { execFile, spawn } from 'node:child_process' -import vnu from 'vnu-jar' +'use strict' + +const { execFile, spawn } = require('node:child_process') +const vnu = require('vnu-jar') execFile('java', ['-version'], (error, stdout, stderr) => { if (error) { - console.error('Skipping vnu-jar test; Java is probably missing.') - console.error(error) + console.error('Skipping vnu-jar test; Java is missing.') return } - console.log('Running vnu-jar validation...') - const is32bitJava = !/64-Bit/.test(stderr) // vnu-jar accepts multiple ignores joined with a `|`. @@ -30,14 +30,7 @@ execFile('java', ['-version'], (error, stdout, stderr) => { // Per https://www.w3.org/TR/html-aria/#docconformance having "aria-disabled" on a link is // NOT RECOMMENDED, but it's still valid - we explain in the docs that it's not ideal, // and offer more robust alternatives, but also need to show a less-than-ideal example - 'An “aria-disabled” attribute whose value is “true” should not be specified on an “a” element that has an “href” attribute.', - // A `code` element with the `is:raw` attribute coming from remark-prismjs (Astro upstream possible bug) - 'Attribute “is:raw” is not serializable as XML 1.0.', - 'Attribute “is:raw” not allowed on element “code” at this point.', - // Astro's expecting trailing slashes on HTML tags such as
- 'Trailing slash on void elements has no effect and interacts badly with unquoted attribute values.', - // Allow `switch` attribute. - 'Attribute “switch” not allowed on element “input” at this point.' + 'An “aria-disabled” attribute whose value is “true” should not be specified on an “a” element that has an “href” attribute.' ].join('|') const args = [ @@ -56,8 +49,6 @@ execFile('java', ['-version'], (error, stdout, stderr) => { args.splice(0, 0, '-Xss512k') } - console.log(`command used: java ${args.join(' ')}`) - return spawn('java', args, { shell: true, stdio: 'inherit' diff --git a/build/zip-examples.mjs b/build/zip-examples.js similarity index 57% rename from build/zip-examples.mjs rename to build/zip-examples.js index 6b2e1e0..077901e 100644 --- a/build/zip-examples.mjs +++ b/build/zip-examples.js @@ -3,20 +3,16 @@ /*! * Script to create the built examples zip archive; * requires the `zip` command to be present! - * Copyright 2020-2025 The Bootstrap Authors + * Copyright 2020-2022 The Bootstrap Authors * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ -import fs from 'node:fs/promises' -import path from 'node:path' -import { fileURLToPath } from 'node:url' -import sh from 'shelljs' -import { format } from 'prettier' +'use strict' -const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const path = require('node:path') +const sh = require('shelljs') -const pkgJson = path.join(__dirname, '../package.json') -const pkg = JSON.parse(await fs.readFile(pkgJson, 'utf8')) +const pkg = require('../package.json') const versionShort = pkg.config.version_short const distFolder = `bootstrap-${pkg.version}-examples` @@ -38,9 +34,6 @@ const imgFiles = [ 'bootstrap-logo.svg', 'bootstrap-logo-white.svg' ] -const staticJsFiles = [ - 'color-modes.js' -] sh.config.fatal = true @@ -59,8 +52,7 @@ sh.mkdir('-p', [ distFolder, `${distFolder}/assets/brand/`, `${distFolder}/assets/dist/css/`, - `${distFolder}/assets/dist/js/`, - `${distFolder}/assets/js/` + `${distFolder}/assets/dist/js/` ]) sh.cp('-Rf', `${docsDir}/examples/*`, distFolder) @@ -77,44 +69,22 @@ for (const file of imgFiles) { sh.cp('-f', `${docsDir}/assets/brand/${file}`, `${distFolder}/assets/brand/`) } -for (const file of staticJsFiles) { - sh.cp('-f', `${docsDir}/assets/js/${file}`, `${distFolder}/assets/js/`) -} - sh.rm(`${distFolder}/index.html`) // get all examples' HTML files -const htmlFiles = sh.find(`${distFolder}/**/*.html`) - -const formatPromises = htmlFiles.map(async file => { +for (const file of sh.find(`${distFolder}/**/*.html`)) { const fileContents = sh.cat(file) .toString() .replace(new RegExp(`"/docs/${versionShort}/`, 'g'), '"../') .replace(/"..\/dist\//g, '"../assets/dist/') - .replace(/(]*) integrity="[^"]*"/g, '$1') - .replace(/]*href="\.\.\/assets\/img\/favicons\/[^"]*"[^>]*>/g, '') - .replace(/(') + .replace(/( +) 'click') event = event.replace(stripNameRegex, ''); return customEvents[event] || event; } + const EventHandler = { on(element, event, handler, delegationFunction) { addHandler(element, event, handler, delegationFunction, false); }, + one(element, event, handler, delegationFunction) { addHandler(element, event, handler, delegationFunction, true); }, + off(element, originalTypeEvent, handler, delegationFunction) { if (typeof originalTypeEvent !== 'string' || !element) { return; } + const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction); const inNamespace = typeEvent !== originalTypeEvent; const events = getElementEvents(element); const storeElementEvent = events[typeEvent] || {}; const isNamespace = originalTypeEvent.startsWith('.'); + if (typeof callable !== 'undefined') { // Simplest case: handler is passed, remove that listener ONLY. if (!Object.keys(storeElementEvent).length) { return; } + removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null); return; } + if (isNamespace) { for (const elementEvent of Object.keys(events)) { removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1)); } } - for (const [keyHandlers, event] of Object.entries(storeElementEvent)) { + + for (const keyHandlers of Object.keys(storeElementEvent)) { const handlerKey = keyHandlers.replace(stripUidRegex, ''); + if (!inNamespace || originalTypeEvent.includes(handlerKey)) { + const event = storeElementEvent[keyHandlers]; removeHandler(element, events, typeEvent, event.callable, event.delegationSelector); } } }, + trigger(element, event, args) { if (typeof event !== 'string' || !element) { return null; } + const $ = getjQuery(); const typeEvent = getTypeEvent(event); const inNamespace = event !== typeEvent; @@ -486,6 +542,7 @@ let bubbles = true; let nativeDispatch = true; let defaultPrevented = false; + if (inNamespace && $) { jQueryEvent = $.Event(event, args); $(element).trigger(jQueryEvent); @@ -493,103 +550,177 @@ nativeDispatch = !jQueryEvent.isImmediatePropagationStopped(); defaultPrevented = jQueryEvent.isDefaultPrevented(); } - const evt = hydrateObj(new Event(event, { + + let evt = new Event(event, { bubbles, cancelable: true - }), args); + }); + evt = hydrateObj(evt, args); + if (defaultPrevented) { evt.preventDefault(); } + if (nativeDispatch) { element.dispatchEvent(evt); } + if (evt.defaultPrevented && jQueryEvent) { jQueryEvent.preventDefault(); } + return evt; } + }; - function hydrateObj(obj, meta = {}) { - for (const [key, value] of Object.entries(meta)) { + + function hydrateObj(obj, meta) { + for (const [key, value] of Object.entries(meta || {})) { try { obj[key] = value; } catch (_unused) { Object.defineProperty(obj, key, { configurable: true, + get() { return value; } + }); } } + return obj; } /** * -------------------------------------------------------------------------- - * Bootstrap dom/manipulator.js + * Bootstrap (v5.2.3): dom/data.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ + /** + * Constants + */ + const elementMap = new Map(); + const Data = { + set(element, key, instance) { + if (!elementMap.has(element)) { + elementMap.set(element, new Map()); + } + + const instanceMap = elementMap.get(element); // make it clear we only want one instance per element + // 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: ${Array.from(instanceMap.keys())[0]}.`); + return; + } + + instanceMap.set(key, instance); + }, + + get(element, key) { + if (elementMap.has(element)) { + return elementMap.get(element).get(key) || null; + } + + return null; + }, + + remove(element, key) { + if (!elementMap.has(element)) { + return; + } + + const instanceMap = elementMap.get(element); + instanceMap.delete(key); // free up element references if there are no instances left for an element + + if (instanceMap.size === 0) { + elementMap.delete(element); + } + } + + }; + + /** + * -------------------------------------------------------------------------- + * Bootstrap (v5.2.3): dom/manipulator.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ function normalizeData(value) { if (value === 'true') { return true; } + if (value === 'false') { return false; } + if (value === Number(value).toString()) { return Number(value); } + if (value === '' || value === 'null') { return null; } + if (typeof value !== 'string') { return value; } + try { return JSON.parse(decodeURIComponent(value)); } catch (_unused) { return value; } } + function normalizeDataKey(key) { return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`); } + const Manipulator = { setDataAttribute(element, key, value) { element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value); }, + removeDataAttribute(element, key) { element.removeAttribute(`data-bs-${normalizeDataKey(key)}`); }, + getDataAttributes(element) { if (!element) { return {}; } + const attributes = {}; const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig')); + for (const key of bsKeys) { let pureKey = key.replace(/^bs/, ''); - pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1); + pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length); attributes[pureKey] = normalizeData(element.dataset[key]); } + return attributes; }, + getDataAttribute(element, key) { return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`)); } + }; /** * -------------------------------------------------------------------------- - * Bootstrap util/config.js + * Bootstrap (v5.2.3): util/config.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - - /** * Class definition */ @@ -599,56 +730,63 @@ static get Default() { return {}; } + static get DefaultType() { return {}; } + static get NAME() { throw new Error('You have to implement the static method "NAME", for each component!'); } + _getConfig(config) { config = this._mergeConfigObj(config); config = this._configAfterMerge(config); + this._typeCheckConfig(config); + return config; } + _configAfterMerge(config) { return config; } + _mergeConfigObj(config, element) { const jsonConfig = isElement$1(element) ? Manipulator.getDataAttribute(element, 'config') : {}; // try to parse - return { - ...this.constructor.Default, + return { ...this.constructor.Default, ...(typeof jsonConfig === 'object' ? jsonConfig : {}), ...(isElement$1(element) ? Manipulator.getDataAttributes(element) : {}), ...(typeof config === 'object' ? config : {}) }; } + _typeCheckConfig(config, configTypes = this.constructor.DefaultType) { - for (const [property, expectedTypes] of Object.entries(configTypes)) { + for (const property of Object.keys(configTypes)) { + const expectedTypes = configTypes[property]; const value = config[property]; const valueType = isElement$1(value) ? 'element' : toType(value); + if (!new RegExp(expectedTypes).test(valueType)) { throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".`); } } } + } /** * -------------------------------------------------------------------------- - * Bootstrap base-component.js + * Bootstrap (v5.2.3): base-component.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - - /** * Constants */ - const VERSION = '5.3.8'; - + const VERSION = '5.2.3'; /** * Class definition */ @@ -657,147 +795,69 @@ constructor(element, config) { super(); element = getElement(element); + if (!element) { return; } + this._element = element; this._config = this._getConfig(config); Data.set(this._element, this.constructor.DATA_KEY, this); - } + } // Public + - // Public dispose() { Data.remove(this._element, this.constructor.DATA_KEY); EventHandler.off(this._element, this.constructor.EVENT_KEY); + for (const propertyName of Object.getOwnPropertyNames(this)) { this[propertyName] = null; } } - // Private _queueCallback(callback, element, isAnimated = true) { executeAfterTransition(callback, element, isAnimated); } + _getConfig(config) { config = this._mergeConfigObj(config, this._element); config = this._configAfterMerge(config); - this._typeCheckConfig(config); - return config; - } - // Static + this._typeCheckConfig(config); + + return config; + } // Static + + static getInstance(element) { return Data.get(getElement(element), this.DATA_KEY); } + static getOrCreateInstance(element, config = {}) { return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null); } + static get VERSION() { return VERSION; } + static get DATA_KEY() { return `bs.${this.NAME}`; } + static get EVENT_KEY() { return `.${this.DATA_KEY}`; } + static eventName(name) { return `${name}${this.EVENT_KEY}`; } + } /** * -------------------------------------------------------------------------- - * Bootstrap dom/selector-engine.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - - const getSelector = element => { - let selector = element.getAttribute('data-bs-target'); - if (!selector || selector === '#') { - let hrefAttribute = element.getAttribute('href'); - - // The only valid content that could double as a selector are IDs or classes, - // so everything starting with `#` or `.`. If a "real" URL is used as the selector, - // `document.querySelector` will rightfully complain it is invalid. - // See https://github.com/twbs/bootstrap/issues/32273 - if (!hrefAttribute || !hrefAttribute.includes('#') && !hrefAttribute.startsWith('.')) { - return null; - } - - // Just in case some CMS puts out a full URL with the anchor appended - if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) { - hrefAttribute = `#${hrefAttribute.split('#')[1]}`; - } - selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null; - } - return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null; - }; - const SelectorEngine = { - find(selector, element = document.documentElement) { - return [].concat(...Element.prototype.querySelectorAll.call(element, selector)); - }, - findOne(selector, element = document.documentElement) { - return Element.prototype.querySelector.call(element, selector); - }, - children(element, selector) { - return [].concat(...element.children).filter(child => child.matches(selector)); - }, - parents(element, selector) { - const parents = []; - let ancestor = element.parentNode.closest(selector); - while (ancestor) { - parents.push(ancestor); - ancestor = ancestor.parentNode.closest(selector); - } - return parents; - }, - prev(element, selector) { - let previous = element.previousElementSibling; - while (previous) { - if (previous.matches(selector)) { - return [previous]; - } - previous = previous.previousElementSibling; - } - return []; - }, - // TODO: this is now unused; remove later along with prev() - next(element, selector) { - let next = element.nextElementSibling; - while (next) { - if (next.matches(selector)) { - return [next]; - } - next = next.nextElementSibling; - } - return []; - }, - focusableChildren(element) { - const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable="true"]'].map(selector => `${selector}:not([tabindex^="-"])`).join(','); - return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el)); - }, - getSelectorFromElement(element) { - const selector = getSelector(element); - if (selector) { - return SelectorEngine.findOne(selector) ? selector : null; - } - return null; - }, - getElementFromSelector(element) { - const selector = getSelector(element); - return selector ? SelectorEngine.findOne(selector) : null; - }, - getMultipleElementsFromSelector(element) { - const selector = getSelector(element); - return selector ? SelectorEngine.find(selector) : []; - } - }; - - /** - * -------------------------------------------------------------------------- - * Bootstrap util/component-functions.js + * Bootstrap (v5.2.3): util/component-functions.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ @@ -809,25 +869,24 @@ if (['A', 'AREA'].includes(this.tagName)) { event.preventDefault(); } + if (isDisabled(this)) { return; } - const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`); - const instance = component.getOrCreateInstance(target); - // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method + const target = getElementFromSelector(this) || this.closest(`.${name}`); + const instance = component.getOrCreateInstance(target); // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method + instance[method](); }); }; /** * -------------------------------------------------------------------------- - * Bootstrap alert.js + * Bootstrap (v5.2.3): alert.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - - /** * Constants */ @@ -839,7 +898,6 @@ const EVENT_CLOSED = `closed${EVENT_KEY$b}`; const CLASS_NAME_FADE$5 = 'fade'; const CLASS_NAME_SHOW$8 = 'show'; - /** * Class definition */ @@ -848,47 +906,55 @@ // Getters static get NAME() { return NAME$f; - } + } // Public + - // Public close() { const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE); + if (closeEvent.defaultPrevented) { return; } - this._element.classList.remove(CLASS_NAME_SHOW$8); - const isAnimated = this._element.classList.contains(CLASS_NAME_FADE$5); - this._queueCallback(() => this._destroyElement(), this._element, isAnimated); - } - // Private + this._element.classList.remove(CLASS_NAME_SHOW$8); + + const isAnimated = this._element.classList.contains(CLASS_NAME_FADE$5); + + this._queueCallback(() => this._destroyElement(), this._element, isAnimated); + } // Private + + _destroyElement() { this._element.remove(); + EventHandler.trigger(this._element, EVENT_CLOSED); this.dispose(); - } + } // Static + - // Static static jQueryInterface(config) { return this.each(function () { const data = Alert.getOrCreateInstance(this); + if (typeof config !== 'string') { return; } + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { throw new TypeError(`No method named "${config}"`); } + data[config](this); }); } - } + } /** * Data API implementation */ - enableDismissTrigger(Alert, 'close'); + enableDismissTrigger(Alert, 'close'); /** * jQuery */ @@ -897,12 +963,10 @@ /** * -------------------------------------------------------------------------- - * Bootstrap button.js + * Bootstrap (v5.2.3): button.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - - /** * Constants */ @@ -914,7 +978,6 @@ const CLASS_NAME_ACTIVE$3 = 'active'; const SELECTOR_DATA_TOGGLE$5 = '[data-bs-toggle="button"]'; const EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`; - /** * Class definition */ @@ -923,36 +986,37 @@ // Getters static get NAME() { return NAME$e; - } + } // Public + - // Public toggle() { // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE$3)); - } + } // Static + - // Static static jQueryInterface(config) { return this.each(function () { const data = Button.getOrCreateInstance(this); + if (config === 'toggle') { data[config](); } }); } - } + } /** * Data API implementation */ + EventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_TOGGLE$5, event => { event.preventDefault(); const button = event.target.closest(SELECTOR_DATA_TOGGLE$5); const data = Button.getOrCreateInstance(button); data.toggle(); }); - /** * jQuery */ @@ -961,12 +1025,81 @@ /** * -------------------------------------------------------------------------- - * Bootstrap util/swipe.js + * Bootstrap (v5.2.3): dom/selector-engine.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ + /** + * Constants + */ + const SelectorEngine = { + find(selector, element = document.documentElement) { + return [].concat(...Element.prototype.querySelectorAll.call(element, selector)); + }, + findOne(selector, element = document.documentElement) { + return Element.prototype.querySelector.call(element, selector); + }, + + children(element, selector) { + return [].concat(...element.children).filter(child => child.matches(selector)); + }, + + parents(element, selector) { + const parents = []; + let ancestor = element.parentNode.closest(selector); + + while (ancestor) { + parents.push(ancestor); + ancestor = ancestor.parentNode.closest(selector); + } + + return parents; + }, + + prev(element, selector) { + let previous = element.previousElementSibling; + + while (previous) { + if (previous.matches(selector)) { + return [previous]; + } + + previous = previous.previousElementSibling; + } + + return []; + }, + + // TODO: this is now unused; remove later along with prev() + next(element, selector) { + let next = element.nextElementSibling; + + while (next) { + if (next.matches(selector)) { + return [next]; + } + + next = next.nextElementSibling; + } + + return []; + }, + + focusableChildren(element) { + const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable="true"]'].map(selector => `${selector}:not([tabindex^="-"])`).join(','); + return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el)); + } + + }; + + /** + * -------------------------------------------------------------------------- + * Bootstrap (v5.2.3): util/swipe.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ /** * Constants */ @@ -992,7 +1125,6 @@ leftCallback: '(function|null)', rightCallback: '(function|null)' }; - /** * Class definition */ @@ -1001,67 +1133,84 @@ constructor(element, config) { super(); this._element = element; + if (!element || !Swipe.isSupported()) { return; } + this._config = this._getConfig(config); this._deltaX = 0; this._supportPointerEvents = Boolean(window.PointerEvent); - this._initEvents(); - } - // Getters + this._initEvents(); + } // Getters + + static get Default() { return Default$c; } + static get DefaultType() { return DefaultType$c; } + static get NAME() { return NAME$d; - } + } // Public + - // Public dispose() { EventHandler.off(this._element, EVENT_KEY$9); - } + } // Private + - // Private _start(event) { if (!this._supportPointerEvents) { this._deltaX = event.touches[0].clientX; return; } + if (this._eventIsPointerPenTouch(event)) { this._deltaX = event.clientX; } } + _end(event) { if (this._eventIsPointerPenTouch(event)) { this._deltaX = event.clientX - this._deltaX; } + this._handleSwipe(); + execute(this._config.endCallback); } + _move(event) { this._deltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this._deltaX; } + _handleSwipe() { const absDeltaX = Math.abs(this._deltaX); + if (absDeltaX <= SWIPE_THRESHOLD) { return; } + const direction = absDeltaX / this._deltaX; this._deltaX = 0; + if (!direction) { return; } + execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback); } + _initEvents() { if (this._supportPointerEvents) { EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event)); EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event)); + this._element.classList.add(CLASS_NAME_POINTER_EVENT); } else { EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event)); @@ -1069,24 +1218,24 @@ EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event)); } } + _eventIsPointerPenTouch(event) { return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH); - } + } // Static + - // Static static isSupported() { return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0; } + } /** * -------------------------------------------------------------------------- - * Bootstrap carousel.js + * Bootstrap (v5.2.3): carousel.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - - /** * Constants */ @@ -1146,7 +1295,6 @@ touch: 'boolean', wrap: 'boolean' }; - /** * Class definition */ @@ -1160,27 +1308,32 @@ this.touchTimeout = null; this._swipeHelper = null; this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element); + this._addEventListeners(); + if (this._config.ride === CLASS_NAME_CAROUSEL) { this.cycle(); } - } + } // Getters + - // Getters static get Default() { return Default$b; } + static get DefaultType() { return DefaultType$b; } + static get NAME() { return NAME$c; - } + } // Public + - // Public next() { this._slide(ORDER_NEXT); } + nextWhenVisible() { // FIXME TODO use `document.visibilityState` // Don't call next when the page isn't visible @@ -1189,80 +1342,101 @@ this.next(); } } + prev() { this._slide(ORDER_PREV); } + pause() { if (this._isSliding) { triggerTransitionEnd(this._element); } + this._clearInterval(); } + cycle() { this._clearInterval(); + this._updateInterval(); + this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval); } + _maybeEnableCycle() { if (!this._config.ride) { return; } + if (this._isSliding) { EventHandler.one(this._element, EVENT_SLID, () => this.cycle()); return; } + this.cycle(); } + to(index) { const items = this._getItems(); + if (index > items.length - 1 || index < 0) { return; } + if (this._isSliding) { EventHandler.one(this._element, EVENT_SLID, () => this.to(index)); return; } + const activeIndex = this._getItemIndex(this._getActive()); + if (activeIndex === index) { return; } + const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV; + this._slide(order, items[index]); } + dispose() { if (this._swipeHelper) { this._swipeHelper.dispose(); } - super.dispose(); - } - // Private + super.dispose(); + } // Private + + _configAfterMerge(config) { config.defaultInterval = config.interval; return config; } + _addEventListeners() { if (this._config.keyboard) { EventHandler.on(this._element, EVENT_KEYDOWN$1, event => this._keydown(event)); } + if (this._config.pause === 'hover') { EventHandler.on(this._element, EVENT_MOUSEENTER$1, () => this.pause()); EventHandler.on(this._element, EVENT_MOUSELEAVE$1, () => this._maybeEnableCycle()); } + if (this._config.touch && Swipe.isSupported()) { this._addTouchEventListeners(); } } + _addTouchEventListeners() { for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) { EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault()); } + const endCallBack = () => { if (this._config.pause !== 'hover') { return; - } - - // If it's a touch-enabled device, mouseenter/leave are fired as + } // If it's a touch-enabled device, mouseenter/leave are fired as // part of the mouse compatibility events on first tap - the carousel // would stop cycling until user tapped out of it; // here, we listen for touchend, explicitly pause the carousel @@ -1270,12 +1444,16 @@ // is NOT fired) and after a timeout (to allow for mouse compatibility // events to fire) we explicitly restart cycling + this.pause(); + if (this.touchTimeout) { clearTimeout(this.touchTimeout); } + this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval); }; + const swipeConfig = { leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)), rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)), @@ -1283,51 +1461,68 @@ }; this._swipeHelper = new Swipe(this._element, swipeConfig); } + _keydown(event) { if (/input|textarea/i.test(event.target.tagName)) { return; } + const direction = KEY_TO_DIRECTION[event.key]; + if (direction) { event.preventDefault(); + this._slide(this._directionToOrder(direction)); } } + _getItemIndex(element) { return this._getItems().indexOf(element); } + _setActiveIndicatorElement(index) { if (!this._indicatorsElement) { return; } + const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement); activeIndicator.classList.remove(CLASS_NAME_ACTIVE$2); activeIndicator.removeAttribute('aria-current'); const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to="${index}"]`, this._indicatorsElement); + if (newActiveIndicator) { newActiveIndicator.classList.add(CLASS_NAME_ACTIVE$2); newActiveIndicator.setAttribute('aria-current', 'true'); } } + _updateInterval() { const element = this._activeElement || this._getActive(); + if (!element) { return; } + const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10); this._config.interval = elementInterval || this._config.defaultInterval; } + _slide(order, element = null) { if (this._isSliding) { return; } + const activeElement = this._getActive(); + const isNext = order === ORDER_NEXT; const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap); + if (nextElement === activeElement) { return; } + const nextElementIndex = this._getItemIndex(nextElement); + const triggerEvent = eventName => { return EventHandler.trigger(this._element, eventName, { relatedTarget: nextElement, @@ -1336,19 +1531,25 @@ to: nextElementIndex }); }; + const slideEvent = triggerEvent(EVENT_SLIDE); + if (slideEvent.defaultPrevented) { return; } + if (!activeElement || !nextElement) { // Some weirdness is happening, so we bail - // TODO: change tests that use empty divs to avoid this check + // todo: change tests that use empty divs to avoid this check return; } + const isCycling = Boolean(this._interval); this.pause(); this._isSliding = true; + this._setActiveIndicatorElement(nextElementIndex); + this._activeElement = nextElement; const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END; const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV; @@ -1356,6 +1557,7 @@ reflow(nextElement); activeElement.classList.add(directionalClassName); nextElement.classList.add(directionalClassName); + const completeCallBack = () => { nextElement.classList.remove(directionalClassName, orderClassName); nextElement.classList.add(CLASS_NAME_ACTIVE$2); @@ -1363,89 +1565,113 @@ this._isSliding = false; triggerEvent(EVENT_SLID); }; + this._queueCallback(completeCallBack, activeElement, this._isAnimated()); + if (isCycling) { this.cycle(); } } + _isAnimated() { return this._element.classList.contains(CLASS_NAME_SLIDE); } + _getActive() { return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element); } + _getItems() { return SelectorEngine.find(SELECTOR_ITEM, this._element); } + _clearInterval() { if (this._interval) { clearInterval(this._interval); this._interval = null; } } + _directionToOrder(direction) { if (isRTL()) { return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT; } + return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV; } + _orderToDirection(order) { if (isRTL()) { return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT; } - return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT; - } - // Static + return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT; + } // Static + + static jQueryInterface(config) { return this.each(function () { const data = Carousel.getOrCreateInstance(this, config); + if (typeof config === 'number') { data.to(config); return; } + if (typeof config === 'string') { if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { throw new TypeError(`No method named "${config}"`); } + data[config](); } }); } - } + } /** * Data API implementation */ + EventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_SLIDE, function (event) { - const target = SelectorEngine.getElementFromSelector(this); + const target = getElementFromSelector(this); + if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) { return; } + event.preventDefault(); const carousel = Carousel.getOrCreateInstance(target); const slideIndex = this.getAttribute('data-bs-slide-to'); + if (slideIndex) { carousel.to(slideIndex); + carousel._maybeEnableCycle(); + return; } + if (Manipulator.getDataAttribute(this, 'slide') === 'next') { carousel.next(); + carousel._maybeEnableCycle(); + return; } + carousel.prev(); + carousel._maybeEnableCycle(); }); EventHandler.on(window, EVENT_LOAD_DATA_API$3, () => { const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE); + for (const carousel of carousels) { Carousel.getOrCreateInstance(carousel); } }); - /** * jQuery */ @@ -1454,12 +1680,10 @@ /** * -------------------------------------------------------------------------- - * Bootstrap collapse.js + * Bootstrap (v5.2.3): collapse.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - - /** * Constants */ @@ -1491,7 +1715,6 @@ parent: '(null|element)', toggle: 'boolean' }; - /** * Class definition */ @@ -1502,34 +1725,41 @@ this._isTransitioning = false; this._triggerArray = []; const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$4); + for (const elem of toggleList) { - const selector = SelectorEngine.getSelectorFromElement(elem); + const selector = getSelectorFromElement(elem); const filterElement = SelectorEngine.find(selector).filter(foundElement => foundElement === this._element); + if (selector !== null && filterElement.length) { this._triggerArray.push(elem); } } + this._initializeChildren(); + if (!this._config.parent) { this._addAriaAndCollapsedClass(this._triggerArray, this._isShown()); } + if (this._config.toggle) { this.toggle(); } - } + } // Getters + - // Getters static get Default() { return Default$a; } + static get DefaultType() { return DefaultType$a; } + static get NAME() { return NAME$b; - } + } // Public + - // Public toggle() { if (this._isShown()) { this.hide(); @@ -1537,149 +1767,201 @@ this.show(); } } + show() { if (this._isTransitioning || this._isShown()) { return; } - let activeChildren = []; - // find active children + let activeChildren = []; // find active children + if (this._config.parent) { activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES).filter(element => element !== this._element).map(element => Collapse.getOrCreateInstance(element, { toggle: false })); } + if (activeChildren.length && activeChildren[0]._isTransitioning) { return; } + const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$6); + if (startEvent.defaultPrevented) { return; } + for (const activeInstance of activeChildren) { activeInstance.hide(); } + const dimension = this._getDimension(); + this._element.classList.remove(CLASS_NAME_COLLAPSE); + this._element.classList.add(CLASS_NAME_COLLAPSING); + this._element.style[dimension] = 0; + this._addAriaAndCollapsedClass(this._triggerArray, true); + this._isTransitioning = true; + const complete = () => { this._isTransitioning = false; + this._element.classList.remove(CLASS_NAME_COLLAPSING); + this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7); + this._element.style[dimension] = ''; EventHandler.trigger(this._element, EVENT_SHOWN$6); }; + const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1); const scrollSize = `scroll${capitalizedDimension}`; + this._queueCallback(complete, this._element, true); + this._element.style[dimension] = `${this._element[scrollSize]}px`; } + hide() { if (this._isTransitioning || !this._isShown()) { return; } + const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$6); + if (startEvent.defaultPrevented) { return; } + const dimension = this._getDimension(); + this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`; reflow(this._element); + this._element.classList.add(CLASS_NAME_COLLAPSING); + this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7); + for (const trigger of this._triggerArray) { - const element = SelectorEngine.getElementFromSelector(trigger); + const element = getElementFromSelector(trigger); + if (element && !this._isShown(element)) { this._addAriaAndCollapsedClass([trigger], false); } } + this._isTransitioning = true; + const complete = () => { this._isTransitioning = false; + this._element.classList.remove(CLASS_NAME_COLLAPSING); + this._element.classList.add(CLASS_NAME_COLLAPSE); + EventHandler.trigger(this._element, EVENT_HIDDEN$6); }; + this._element.style[dimension] = ''; + this._queueCallback(complete, this._element, true); } - // Private _isShown(element = this._element) { return element.classList.contains(CLASS_NAME_SHOW$7); - } + } // Private + + _configAfterMerge(config) { config.toggle = Boolean(config.toggle); // Coerce string values + config.parent = getElement(config.parent); return config; } + _getDimension() { return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT; } + _initializeChildren() { if (!this._config.parent) { return; } + const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE$4); + for (const element of children) { - const selected = SelectorEngine.getElementFromSelector(element); + const selected = getElementFromSelector(element); + if (selected) { this._addAriaAndCollapsedClass([element], this._isShown(selected)); } } } + _getFirstLevelChildren(selector) { - const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent); - // remove children if greater depth + const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent); // remove children if greater depth + return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element)); } + _addAriaAndCollapsedClass(triggerArray, isOpen) { if (!triggerArray.length) { return; } + for (const element of triggerArray) { element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen); element.setAttribute('aria-expanded', isOpen); } - } + } // Static + - // Static static jQueryInterface(config) { const _config = {}; + if (typeof config === 'string' && /show|hide/.test(config)) { _config.toggle = false; } + return this.each(function () { const data = Collapse.getOrCreateInstance(this, _config); + if (typeof config === 'string') { if (typeof data[config] === 'undefined') { throw new TypeError(`No method named "${config}"`); } + data[config](); } }); } - } + } /** * Data API implementation */ + EventHandler.on(document, EVENT_CLICK_DATA_API$4, SELECTOR_DATA_TOGGLE$4, function (event) { // preventDefault only for elements (which change the URL) not inside the collapsible element if (event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') { event.preventDefault(); } - for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) { + + const selector = getSelectorFromElement(this); + const selectorElements = SelectorEngine.find(selector); + + for (const element of selectorElements) { Collapse.getOrCreateInstance(element, { toggle: false }).toggle(); } }); - /** * jQuery */ @@ -1849,7 +2131,7 @@ function getUAString() { var uaData = navigator.userAgentData; - if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) { + if (uaData != null && uaData.brands) { return uaData.brands.map(function (item) { return item.brand + "/" + item.version; }).join(' '); @@ -2137,6 +2419,7 @@ } if (!contains(state.elements.popper, arrowElement)) { + return; } @@ -2167,9 +2450,10 @@ // Zooming can change the DPR, but it seems to report a value that will // cleanly divide the values into the appropriate subpixels. - function roundOffsetsByDPR(_ref, win) { + function roundOffsetsByDPR(_ref) { var x = _ref.x, y = _ref.y; + var win = window; var dpr = win.devicePixelRatio || 1; return { x: round(x * dpr) / dpr || 0, @@ -2252,7 +2536,7 @@ var _ref4 = roundOffsets === true ? roundOffsetsByDPR({ x: x, y: y - }, getWindow(popper)) : { + }) : { x: x, y: y }; @@ -2278,6 +2562,7 @@ adaptive = _options$adaptive === void 0 ? true : _options$adaptive, _options$roundOffsets = options.roundOffsets, roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets; + var commonStyles = { placement: getBasePlacement(state.placement), variation: getVariation(state.placement), @@ -2668,6 +2953,7 @@ var popperOffsets = computeOffsets({ reference: referenceClientRect, element: popperRect, + strategy: 'absolute', placement: placement }); var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets)); @@ -2995,6 +3281,7 @@ state.modifiersData[name] = computeOffsets({ reference: state.rects.reference, element: state.rects.popper, + strategy: 'absolute', placement: state.placement }); } // eslint-disable-next-line import/no-unused-modules @@ -3343,7 +3630,8 @@ state.orderedModifiers = orderedModifiers.filter(function (m) { return m.enabled; - }); + }); // Validate the provided modifiers so that the consumer will get warned + runModifierEffects(); return instance.update(); }, @@ -3363,6 +3651,7 @@ // anymore if (!areValidElements(reference, popper)) { + return; } // Store the reference and popper rects to be read by modifiers @@ -3387,6 +3676,7 @@ }); for (var index = 0; index < state.orderedModifiers.length; index++) { + if (state.reset === true) { state.reset = false; index = -1; @@ -3424,6 +3714,7 @@ }; if (!areValidElements(reference, popper)) { + return instance; } @@ -3438,11 +3729,11 @@ // one. function runModifierEffects() { - state.orderedModifiers.forEach(function (_ref) { - var name = _ref.name, - _ref$options = _ref.options, - options = _ref$options === void 0 ? {} : _ref$options, - effect = _ref.effect; + state.orderedModifiers.forEach(function (_ref3) { + var name = _ref3.name, + _ref3$options = _ref3.options, + options = _ref3$options === void 0 ? {} : _ref3$options, + effect = _ref3.effect; if (typeof effect === 'function') { var cleanupFn = effect({ @@ -3483,54 +3774,52 @@ const Popper = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, - afterMain, - afterRead, - afterWrite, - applyStyles: applyStyles$1, - arrow: arrow$1, + popperGenerator, + detectOverflow, + createPopperBase: createPopper$2, + createPopper, + createPopperLite: createPopper$1, + top, + bottom, + right, + left, auto, basePlacements, - beforeMain, - beforeRead, - beforeWrite, - bottom, - clippingParents, - computeStyles: computeStyles$1, - createPopper, - createPopperBase: createPopper$2, - createPopperLite: createPopper$1, - detectOverflow, + start, end, + clippingParents, + viewport, + popper, + reference, + variationPlacements, + placements, + beforeRead, + read, + afterRead, + beforeMain, + main, + afterMain, + beforeWrite, + write, + afterWrite, + modifierPhases, + applyStyles: applyStyles$1, + arrow: arrow$1, + computeStyles: computeStyles$1, eventListeners, flip: flip$1, hide: hide$1, - left, - main, - modifierPhases, offset: offset$1, - placements, - popper, - popperGenerator, popperOffsets: popperOffsets$1, - preventOverflow: preventOverflow$1, - read, - reference, - right, - start, - top, - variationPlacements, - viewport, - write + preventOverflow: preventOverflow$1 }, Symbol.toStringTag, { value: 'Module' })); /** * -------------------------------------------------------------------------- - * Bootstrap dropdown.js + * Bootstrap (v5.2.3): dropdown.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - - /** * Constants */ @@ -3588,7 +3877,6 @@ popperConfig: '(null|object|function)', reference: '(string|element|object)' }; - /** * Class definition */ @@ -3598,112 +3886,143 @@ super(element, config); this._popper = null; this._parent = this._element.parentNode; // dropdown wrapper - // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/ + // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/ + this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent); this._inNavbar = this._detectNavbar(); - } + } // Getters + - // Getters static get Default() { return Default$9; } + static get DefaultType() { return DefaultType$9; } + static get NAME() { return NAME$a; - } + } // Public + - // Public toggle() { return this._isShown() ? this.hide() : this.show(); } + show() { if (isDisabled(this._element) || this._isShown()) { return; } + const relatedTarget = { relatedTarget: this._element }; const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$5, relatedTarget); + if (showEvent.defaultPrevented) { return; } - this._createPopper(); - // If this is a touch-enabled device we add extra + this._createPopper(); // If this is a touch-enabled device we add extra // empty mouseover listeners to the body's immediate children; // 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)) { for (const element of [].concat(...document.body.children)) { EventHandler.on(element, 'mouseover', noop); } } + this._element.focus(); + this._element.setAttribute('aria-expanded', true); + this._menu.classList.add(CLASS_NAME_SHOW$6); + this._element.classList.add(CLASS_NAME_SHOW$6); + EventHandler.trigger(this._element, EVENT_SHOWN$5, relatedTarget); } + hide() { if (isDisabled(this._element) || !this._isShown()) { return; } + const relatedTarget = { relatedTarget: this._element }; + this._completeHide(relatedTarget); } + dispose() { if (this._popper) { this._popper.destroy(); } + super.dispose(); } + update() { this._inNavbar = this._detectNavbar(); + if (this._popper) { this._popper.update(); } - } + } // Private + - // Private _completeHide(relatedTarget) { const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$5, relatedTarget); + if (hideEvent.defaultPrevented) { return; - } - - // If this is a touch-enabled device we remove the extra + } // If this is a touch-enabled device we remove the extra // empty mouseover listeners we added for iOS support + + if ('ontouchstart' in document.documentElement) { for (const element of [].concat(...document.body.children)) { EventHandler.off(element, 'mouseover', noop); } } + if (this._popper) { this._popper.destroy(); } + this._menu.classList.remove(CLASS_NAME_SHOW$6); + this._element.classList.remove(CLASS_NAME_SHOW$6); + this._element.setAttribute('aria-expanded', 'false'); + Manipulator.removeDataAttribute(this._menu, 'popper'); EventHandler.trigger(this._element, EVENT_HIDDEN$5, relatedTarget); } + _getConfig(config) { config = super._getConfig(config); + if (typeof config.reference === 'object' && !isElement$1(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') { // Popper virtual elements require a getBoundingClientRect method throw new TypeError(`${NAME$a.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`); } + return config; } + _createPopper() { if (typeof Popper === 'undefined') { - throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org/docs/v2/)'); + throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)'); } + let referenceElement = this._element; + if (this._config.reference === 'parent') { referenceElement = this._parent; } else if (isElement$1(this._config.reference)) { @@ -3711,49 +4030,65 @@ } else if (typeof this._config.reference === 'object') { referenceElement = this._config.reference; } + const popperConfig = this._getPopperConfig(); + this._popper = createPopper(referenceElement, this._menu, popperConfig); } + _isShown() { return this._menu.classList.contains(CLASS_NAME_SHOW$6); } + _getPlacement() { const parentDropdown = this._parent; + if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) { return PLACEMENT_RIGHT; } + if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) { return PLACEMENT_LEFT; } + if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) { return PLACEMENT_TOPCENTER; } + if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) { return PLACEMENT_BOTTOMCENTER; - } + } // We need to trim the value because custom properties can also include spaces + - // We need to trim the value because custom properties can also include spaces const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'; + if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) { return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP; } + return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM; } + _detectNavbar() { return this._element.closest(SELECTOR_NAVBAR) !== null; } + _getOffset() { const { offset } = this._config; + if (typeof offset === 'string') { return offset.split(',').map(value => Number.parseInt(value, 10)); } + if (typeof offset === 'function') { return popperData => offset(popperData, this._element); } + return offset; } + _getPopperConfig() { const defaultBsPopperConfig = { placement: this._getPlacement(), @@ -3768,101 +4103,121 @@ offset: this._getOffset() } }] - }; + }; // Disable Popper if we have a static display or Dropdown is in Navbar - // Disable Popper if we have a static display or Dropdown is in Navbar if (this._inNavbar || this._config.display === 'static') { - Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // TODO: v6 remove + Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // todo:v6 remove + defaultBsPopperConfig.modifiers = [{ name: 'applyStyles', enabled: false }]; } - return { - ...defaultBsPopperConfig, - ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig]) + + return { ...defaultBsPopperConfig, + ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig) }; } + _selectMenuItem({ key, target }) { const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element)); + if (!items.length) { return; - } - - // if target isn't included in items (e.g. when expanding the dropdown) + } // if target isn't included in items (e.g. when expanding the dropdown) // allow cycling to get the last item in case key equals ARROW_UP_KEY - getNextActiveElement(items, target, key === ARROW_DOWN_KEY$1, !items.includes(target)).focus(); - } - // Static + + getNextActiveElement(items, target, key === ARROW_DOWN_KEY$1, !items.includes(target)).focus(); + } // Static + + static jQueryInterface(config) { return this.each(function () { const data = Dropdown.getOrCreateInstance(this, config); + if (typeof config !== 'string') { return; } + if (typeof data[config] === 'undefined') { throw new TypeError(`No method named "${config}"`); } + data[config](); }); } + static clearMenus(event) { if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY$1) { return; } + const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN); + for (const toggle of openToggles) { const context = Dropdown.getInstance(toggle); + if (!context || context._config.autoClose === false) { continue; } + const composedPath = event.composedPath(); const isMenuTarget = composedPath.includes(context._menu); + if (composedPath.includes(context._element) || context._config.autoClose === 'inside' && !isMenuTarget || context._config.autoClose === 'outside' && isMenuTarget) { continue; - } + } // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu + - // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu if (context._menu.contains(event.target) && (event.type === 'keyup' && event.key === TAB_KEY$1 || /input|select|option|textarea|form/i.test(event.target.tagName))) { continue; } + const relatedTarget = { relatedTarget: context._element }; + if (event.type === 'click') { relatedTarget.clickEvent = event; } + context._completeHide(relatedTarget); } } + static dataApiKeydownHandler(event) { // If not an UP | DOWN | ESCAPE key => not a dropdown command // If input/textarea && if key is other than ESCAPE => not a dropdown command - const isInput = /input|textarea/i.test(event.target.tagName); const isEscapeEvent = event.key === ESCAPE_KEY$2; const isUpOrDownEvent = [ARROW_UP_KEY$1, ARROW_DOWN_KEY$1].includes(event.key); + if (!isUpOrDownEvent && !isEscapeEvent) { return; } + if (isInput && !isEscapeEvent) { return; } - event.preventDefault(); - // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/ + event.preventDefault(); // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/ + const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE$3) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE$3, event.delegateTarget.parentNode); const instance = Dropdown.getOrCreateInstance(getToggleButton); + if (isUpOrDownEvent) { event.stopPropagation(); instance.show(); + instance._selectMenuItem(event); + return; } + if (instance._isShown()) { // else is escape and we check if it is shown event.stopPropagation(); @@ -3870,12 +4225,13 @@ getToggleButton.focus(); } } - } + } /** * Data API implementation */ + EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE$3, Dropdown.dataApiKeydownHandler); EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler); EventHandler.on(document, EVENT_CLICK_DATA_API$3, Dropdown.clearMenus); @@ -3884,7 +4240,6 @@ event.preventDefault(); Dropdown.getOrCreateInstance(this).toggle(); }); - /** * jQuery */ @@ -3893,12 +4248,129 @@ /** * -------------------------------------------------------------------------- - * Bootstrap util/backdrop.js + * Bootstrap (v5.2.3): util/scrollBar.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ + /** + * Constants + */ + + const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'; + const SELECTOR_STICKY_CONTENT = '.sticky-top'; + const PROPERTY_PADDING = 'padding-right'; + const PROPERTY_MARGIN = 'margin-right'; + /** + * Class definition + */ + + class ScrollBarHelper { + constructor() { + this._element = document.body; + } // Public + getWidth() { + // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes + const documentWidth = document.documentElement.clientWidth; + return Math.abs(window.innerWidth - documentWidth); + } + + hide() { + const width = this.getWidth(); + + this._disableOverFlow(); // give padding to element to balance the hidden scrollbar width + + + this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width); // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth + + + this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width); + + this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width); + } + + reset() { + this._resetElementAttributes(this._element, 'overflow'); + + this._resetElementAttributes(this._element, PROPERTY_PADDING); + + this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING); + + this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN); + } + + isOverflowing() { + return this.getWidth() > 0; + } // Private + + + _disableOverFlow() { + this._saveInitialAttribute(this._element, 'overflow'); + + this._element.style.overflow = 'hidden'; + } + + _setElementAttributes(selector, styleProperty, callback) { + const scrollbarWidth = this.getWidth(); + + const manipulationCallBack = element => { + if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) { + return; + } + + this._saveInitialAttribute(element, styleProperty); + + const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty); + element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`); + }; + + this._applyManipulationCallback(selector, manipulationCallBack); + } + + _saveInitialAttribute(element, styleProperty) { + const actualValue = element.style.getPropertyValue(styleProperty); + + if (actualValue) { + Manipulator.setDataAttribute(element, styleProperty, actualValue); + } + } + + _resetElementAttributes(selector, styleProperty) { + const manipulationCallBack = element => { + const value = Manipulator.getDataAttribute(element, styleProperty); // We only want to remove the property if the value is `null`; the value can also be zero + + if (value === null) { + element.style.removeProperty(styleProperty); + return; + } + + Manipulator.removeDataAttribute(element, styleProperty); + element.style.setProperty(styleProperty, value); + }; + + this._applyManipulationCallback(selector, manipulationCallBack); + } + + _applyManipulationCallback(selector, callBack) { + if (isElement$1(selector)) { + callBack(selector); + return; + } + + for (const sel of SelectorEngine.find(selector, this._element)) { + callBack(sel); + } + } + + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap (v5.2.3): util/backdrop.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ /** * Constants */ @@ -3914,6 +4386,7 @@ isVisible: true, // if false, we use the backdrop helper without adding any element to the dom rootElement: 'body' // give the choice to place backdrop under different elements + }; const DefaultType$8 = { className: 'string', @@ -3922,7 +4395,6 @@ isVisible: 'boolean', rootElement: '(element|string)' }; - /** * Class definition */ @@ -3933,96 +4405,118 @@ this._config = this._getConfig(config); this._isAppended = false; this._element = null; - } + } // Getters + - // Getters static get Default() { return Default$8; } + static get DefaultType() { return DefaultType$8; } + static get NAME() { return NAME$9; - } + } // Public + - // Public show(callback) { if (!this._config.isVisible) { execute(callback); return; } + this._append(); + const element = this._getElement(); + if (this._config.isAnimated) { reflow(element); } + element.classList.add(CLASS_NAME_SHOW$5); + this._emulateAnimation(() => { execute(callback); }); } + hide(callback) { if (!this._config.isVisible) { execute(callback); return; } + this._getElement().classList.remove(CLASS_NAME_SHOW$5); + this._emulateAnimation(() => { this.dispose(); execute(callback); }); } + dispose() { if (!this._isAppended) { return; } - EventHandler.off(this._element, EVENT_MOUSEDOWN); - this._element.remove(); - this._isAppended = false; - } - // Private + EventHandler.off(this._element, EVENT_MOUSEDOWN); + + this._element.remove(); + + this._isAppended = false; + } // Private + + _getElement() { if (!this._element) { const backdrop = document.createElement('div'); backdrop.className = this._config.className; + if (this._config.isAnimated) { backdrop.classList.add(CLASS_NAME_FADE$4); } + this._element = backdrop; } + return this._element; } + _configAfterMerge(config) { // use getElement() with the default "body" to get a fresh Element on each instantiation config.rootElement = getElement(config.rootElement); return config; } + _append() { if (this._isAppended) { return; } + const element = this._getElement(); + this._config.rootElement.append(element); + EventHandler.on(element, EVENT_MOUSEDOWN, () => { execute(this._config.clickCallback); }); this._isAppended = true; } + _emulateAnimation(callback) { executeAfterTransition(callback, this._getElement(), this._config.isAnimated); } + } /** * -------------------------------------------------------------------------- - * Bootstrap util/focustrap.js + * Bootstrap (v5.2.3): util/focustrap.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - - /** * Constants */ @@ -4038,12 +4532,12 @@ const Default$7 = { autofocus: true, trapElement: null // The element to trap focus inside of + }; const DefaultType$7 = { autofocus: 'boolean', trapElement: 'element' }; - /** * Class definition */ @@ -4054,49 +4548,59 @@ this._config = this._getConfig(config); this._isActive = false; this._lastTabNavDirection = null; - } + } // Getters + - // Getters static get Default() { return Default$7; } + static get DefaultType() { return DefaultType$7; } + static get NAME() { return NAME$8; - } + } // Public + - // Public activate() { if (this._isActive) { return; } + if (this._config.autofocus) { this._config.trapElement.focus(); } + EventHandler.off(document, EVENT_KEY$5); // guard against infinite focus loop + EventHandler.on(document, EVENT_FOCUSIN$2, event => this._handleFocusin(event)); EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event)); this._isActive = true; } + deactivate() { if (!this._isActive) { return; } + this._isActive = false; EventHandler.off(document, EVENT_KEY$5); - } + } // Private + - // Private _handleFocusin(event) { const { trapElement } = this._config; + if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) { return; } + const elements = SelectorEngine.focusableChildren(trapElement); + if (elements.length === 0) { trapElement.focus(); } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) { @@ -4105,120 +4609,23 @@ elements[0].focus(); } } + _handleKeydown(event) { if (event.key !== TAB_KEY) { return; } + this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD; } + } /** * -------------------------------------------------------------------------- - * Bootstrap util/scrollBar.js + * Bootstrap (v5.2.3): modal.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - - - /** - * Constants - */ - - const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'; - const SELECTOR_STICKY_CONTENT = '.sticky-top'; - const PROPERTY_PADDING = 'padding-right'; - const PROPERTY_MARGIN = 'margin-right'; - - /** - * Class definition - */ - - class ScrollBarHelper { - constructor() { - this._element = document.body; - } - - // Public - getWidth() { - // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes - const documentWidth = document.documentElement.clientWidth; - return Math.abs(window.innerWidth - documentWidth); - } - hide() { - const width = this.getWidth(); - this._disableOverFlow(); - // give padding to element to balance the hidden scrollbar width - this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width); - // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth - this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width); - this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width); - } - reset() { - this._resetElementAttributes(this._element, 'overflow'); - this._resetElementAttributes(this._element, PROPERTY_PADDING); - this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING); - this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN); - } - isOverflowing() { - return this.getWidth() > 0; - } - - // Private - _disableOverFlow() { - this._saveInitialAttribute(this._element, 'overflow'); - this._element.style.overflow = 'hidden'; - } - _setElementAttributes(selector, styleProperty, callback) { - const scrollbarWidth = this.getWidth(); - const manipulationCallBack = element => { - if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) { - return; - } - this._saveInitialAttribute(element, styleProperty); - const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty); - element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`); - }; - this._applyManipulationCallback(selector, manipulationCallBack); - } - _saveInitialAttribute(element, styleProperty) { - const actualValue = element.style.getPropertyValue(styleProperty); - if (actualValue) { - Manipulator.setDataAttribute(element, styleProperty, actualValue); - } - } - _resetElementAttributes(selector, styleProperty) { - const manipulationCallBack = element => { - const value = Manipulator.getDataAttribute(element, styleProperty); - // We only want to remove the property if the value is `null`; the value can also be zero - if (value === null) { - element.style.removeProperty(styleProperty); - return; - } - Manipulator.removeDataAttribute(element, styleProperty); - element.style.setProperty(styleProperty, value); - }; - this._applyManipulationCallback(selector, manipulationCallBack); - } - _applyManipulationCallback(selector, callBack) { - if (isElement$1(selector)) { - callBack(selector); - return; - } - for (const sel of SelectorEngine.find(selector, this._element)) { - callBack(sel); - } - } - } - - /** - * -------------------------------------------------------------------------- - * Bootstrap modal.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - - /** * Constants */ @@ -4256,7 +4663,6 @@ focus: 'boolean', keyboard: 'boolean' }; - /** * Class definition */ @@ -4270,67 +4676,91 @@ this._isShown = false; this._isTransitioning = false; this._scrollBar = new ScrollBarHelper(); - this._addEventListeners(); - } - // Getters + this._addEventListeners(); + } // Getters + + static get Default() { return Default$6; } + static get DefaultType() { return DefaultType$6; } + static get NAME() { return NAME$7; - } + } // Public + - // Public toggle(relatedTarget) { return this._isShown ? this.hide() : this.show(relatedTarget); } + show(relatedTarget) { if (this._isShown || this._isTransitioning) { return; } + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$4, { relatedTarget }); + if (showEvent.defaultPrevented) { return; } + this._isShown = true; this._isTransitioning = true; + this._scrollBar.hide(); + document.body.classList.add(CLASS_NAME_OPEN); + this._adjustDialog(); + this._backdrop.show(() => this._showElement(relatedTarget)); } + hide() { if (!this._isShown || this._isTransitioning) { return; } + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$4); + if (hideEvent.defaultPrevented) { return; } + this._isShown = false; this._isTransitioning = true; + this._focustrap.deactivate(); + this._element.classList.remove(CLASS_NAME_SHOW$4); + this._queueCallback(() => this._hideModal(), this._element, this._isAnimated()); } + dispose() { - EventHandler.off(window, EVENT_KEY$4); - EventHandler.off(this._dialog, EVENT_KEY$4); + for (const htmlElement of [window, this._dialog]) { + EventHandler.off(htmlElement, EVENT_KEY$4); + } + this._backdrop.dispose(); + this._focustrap.deactivate(); + super.dispose(); } + handleUpdate() { this._adjustDialog(); - } + } // Private + - // Private _initializeBackDrop() { return new Backdrop({ isVisible: Boolean(this._config.backdrop), @@ -4338,47 +4768,64 @@ isAnimated: this._isAnimated() }); } + _initializeFocusTrap() { return new FocusTrap({ trapElement: this._element }); } + _showElement(relatedTarget) { // try to append dynamic modal if (!document.body.contains(this._element)) { document.body.append(this._element); } + this._element.style.display = 'block'; + this._element.removeAttribute('aria-hidden'); + this._element.setAttribute('aria-modal', true); + this._element.setAttribute('role', 'dialog'); + this._element.scrollTop = 0; const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog); + if (modalBody) { modalBody.scrollTop = 0; } + reflow(this._element); + this._element.classList.add(CLASS_NAME_SHOW$4); + const transitionComplete = () => { if (this._config.focus) { this._focustrap.activate(); } + this._isTransitioning = false; EventHandler.trigger(this._element, EVENT_SHOWN$4, { relatedTarget }); }; + this._queueCallback(transitionComplete, this._dialog, this._isAnimated()); } + _addEventListeners() { EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS$1, event => { if (event.key !== ESCAPE_KEY$1) { return; } + if (this._config.keyboard) { + event.preventDefault(); this.hide(); return; } + this._triggerBackdropTransition(); }); EventHandler.on(window, EVENT_RESIZE$1, () => { @@ -4392,124 +4839,157 @@ if (this._element !== event.target || this._element !== event2.target) { return; } + if (this._config.backdrop === 'static') { this._triggerBackdropTransition(); + return; } + if (this._config.backdrop) { this.hide(); } }); }); } + _hideModal() { this._element.style.display = 'none'; + this._element.setAttribute('aria-hidden', true); + this._element.removeAttribute('aria-modal'); + this._element.removeAttribute('role'); + this._isTransitioning = false; + this._backdrop.hide(() => { document.body.classList.remove(CLASS_NAME_OPEN); + this._resetAdjustments(); + this._scrollBar.reset(); + EventHandler.trigger(this._element, EVENT_HIDDEN$4); }); } + _isAnimated() { return this._element.classList.contains(CLASS_NAME_FADE$3); } + _triggerBackdropTransition() { const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED$1); + if (hideEvent.defaultPrevented) { return; } + const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight; - const initialOverflowY = this._element.style.overflowY; - // return if the following background transition hasn't yet completed + const initialOverflowY = this._element.style.overflowY; // return if the following background transition hasn't yet completed + if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) { return; } + if (!isModalOverflowing) { this._element.style.overflowY = 'hidden'; } + this._element.classList.add(CLASS_NAME_STATIC); + this._queueCallback(() => { this._element.classList.remove(CLASS_NAME_STATIC); + this._queueCallback(() => { this._element.style.overflowY = initialOverflowY; }, this._dialog); }, this._dialog); + this._element.focus(); } - /** * The following methods are used to handle overflowing modals */ + _adjustDialog() { const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight; + const scrollbarWidth = this._scrollBar.getWidth(); + const isBodyOverflowing = scrollbarWidth > 0; + if (isBodyOverflowing && !isModalOverflowing) { const property = isRTL() ? 'paddingLeft' : 'paddingRight'; this._element.style[property] = `${scrollbarWidth}px`; } + if (!isBodyOverflowing && isModalOverflowing) { const property = isRTL() ? 'paddingRight' : 'paddingLeft'; this._element.style[property] = `${scrollbarWidth}px`; } } + _resetAdjustments() { this._element.style.paddingLeft = ''; this._element.style.paddingRight = ''; - } + } // Static + - // Static static jQueryInterface(config, relatedTarget) { return this.each(function () { const data = Modal.getOrCreateInstance(this, config); + if (typeof config !== 'string') { return; } + if (typeof data[config] === 'undefined') { throw new TypeError(`No method named "${config}"`); } + data[config](relatedTarget); }); } - } + } /** * Data API implementation */ + EventHandler.on(document, EVENT_CLICK_DATA_API$2, SELECTOR_DATA_TOGGLE$2, function (event) { - const target = SelectorEngine.getElementFromSelector(this); + const target = getElementFromSelector(this); + if (['A', 'AREA'].includes(this.tagName)) { event.preventDefault(); } + EventHandler.one(target, EVENT_SHOW$4, showEvent => { if (showEvent.defaultPrevented) { // only register focus restorer if modal will actually get shown return; } + EventHandler.one(target, EVENT_HIDDEN$4, () => { if (isVisible(this)) { this.focus(); } }); - }); + }); // avoid conflict when clicking modal toggler while another one is open - // avoid conflict when clicking modal toggler while another one is open const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR$1); + if (alreadyOpen) { Modal.getInstance(alreadyOpen).hide(); } + const data = Modal.getOrCreateInstance(target); data.toggle(this); }); enableDismissTrigger(Modal); - /** * jQuery */ @@ -4518,12 +4998,10 @@ /** * -------------------------------------------------------------------------- - * Bootstrap offcanvas.js + * Bootstrap (v5.2.3): offcanvas.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - - /** * Constants */ @@ -4558,7 +5036,6 @@ keyboard: 'boolean', scroll: 'boolean' }; - /** * Class definition */ @@ -4569,95 +5046,130 @@ this._isShown = false; this._backdrop = this._initializeBackDrop(); this._focustrap = this._initializeFocusTrap(); - this._addEventListeners(); - } - // Getters + this._addEventListeners(); + } // Getters + + static get Default() { return Default$5; } + static get DefaultType() { return DefaultType$5; } + static get NAME() { return NAME$6; - } + } // Public + - // Public toggle(relatedTarget) { return this._isShown ? this.hide() : this.show(relatedTarget); } + show(relatedTarget) { if (this._isShown) { return; } + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$3, { relatedTarget }); + if (showEvent.defaultPrevented) { return; } + this._isShown = true; + this._backdrop.show(); + if (!this._config.scroll) { new ScrollBarHelper().hide(); } + this._element.setAttribute('aria-modal', true); + this._element.setAttribute('role', 'dialog'); + this._element.classList.add(CLASS_NAME_SHOWING$1); + const completeCallBack = () => { if (!this._config.scroll || this._config.backdrop) { this._focustrap.activate(); } + this._element.classList.add(CLASS_NAME_SHOW$3); + this._element.classList.remove(CLASS_NAME_SHOWING$1); + EventHandler.trigger(this._element, EVENT_SHOWN$3, { relatedTarget }); }; + this._queueCallback(completeCallBack, this._element, true); } + hide() { if (!this._isShown) { return; } + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$3); + if (hideEvent.defaultPrevented) { return; } + this._focustrap.deactivate(); + this._element.blur(); + this._isShown = false; + this._element.classList.add(CLASS_NAME_HIDING); + this._backdrop.hide(); + const completeCallback = () => { this._element.classList.remove(CLASS_NAME_SHOW$3, CLASS_NAME_HIDING); + this._element.removeAttribute('aria-modal'); + this._element.removeAttribute('role'); + if (!this._config.scroll) { new ScrollBarHelper().reset(); } + EventHandler.trigger(this._element, EVENT_HIDDEN$3); }; + this._queueCallback(completeCallback, this._element, true); } + dispose() { this._backdrop.dispose(); - this._focustrap.deactivate(); - super.dispose(); - } - // Private + this._focustrap.deactivate(); + + super.dispose(); + } // Private + + _initializeBackDrop() { const clickCallback = () => { if (this._config.backdrop === 'static') { EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED); return; } - this.hide(); - }; - // 'static' option will be translated to true, and booleans will keep their value + this.hide(); + }; // 'static' option will be translated to true, and booleans will keep their value + + const isVisible = Boolean(this._config.backdrop); return new Backdrop({ className: CLASS_NAME_BACKDROP, @@ -4667,63 +5179,75 @@ clickCallback: isVisible ? clickCallback : null }); } + _initializeFocusTrap() { return new FocusTrap({ trapElement: this._element }); } + _addEventListeners() { EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => { if (event.key !== ESCAPE_KEY) { return; } - if (this._config.keyboard) { - this.hide(); + + if (!this._config.keyboard) { + EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED); return; } - EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED); - }); - } - // Static + this.hide(); + }); + } // Static + + static jQueryInterface(config) { return this.each(function () { const data = Offcanvas.getOrCreateInstance(this, config); + if (typeof config !== 'string') { return; } + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { throw new TypeError(`No method named "${config}"`); } + data[config](this); }); } - } + } /** * Data API implementation */ + EventHandler.on(document, EVENT_CLICK_DATA_API$1, SELECTOR_DATA_TOGGLE$1, function (event) { - const target = SelectorEngine.getElementFromSelector(this); + const target = getElementFromSelector(this); + if (['A', 'AREA'].includes(this.tagName)) { event.preventDefault(); } + if (isDisabled(this)) { return; } + EventHandler.one(target, EVENT_HIDDEN$3, () => { // focus on trigger when it is closed if (isVisible(this)) { this.focus(); } - }); + }); // avoid conflict when clicking a toggler of an offcanvas, while another is open - // avoid conflict when clicking a toggler of an offcanvas, while another is open const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR); + if (alreadyOpen && alreadyOpen !== target) { Offcanvas.getInstance(alreadyOpen).hide(); } + const data = Offcanvas.getOrCreateInstance(target); data.toggle(this); }); @@ -4740,7 +5264,6 @@ } }); enableDismissTrigger(Offcanvas); - /** * jQuery */ @@ -4749,13 +5272,42 @@ /** * -------------------------------------------------------------------------- - * Bootstrap util/sanitizer.js + * Bootstrap (v5.2.3): util/sanitizer.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - - // js-docs-start allow-list + const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']); const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i; + /** + * A pattern that recognizes a commonly useful subset of URLs that are safe. + * + * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts + */ + + const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i; + /** + * A pattern that matches safe data URLs. Only matches image, video and audio types. + * + * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts + */ + + const DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i; + + const allowedAttribute = (attribute, allowedAttributeList) => { + const attributeName = attribute.nodeName.toLowerCase(); + + if (allowedAttributeList.includes(attributeName)) { + if (uriAttributes.has(attributeName)) { + return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue) || DATA_URL_PATTERN.test(attribute.nodeValue)); + } + + return true; + } // Check if a regular expression validates the attribute. + + + return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName)); + }; + const DefaultAllowlist = { // Global attributes allowed on any supplied element below. '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN], @@ -4765,10 +5317,7 @@ br: [], col: [], code: [], - dd: [], div: [], - dl: [], - dt: [], em: [], hr: [], h1: [], @@ -4792,64 +5341,46 @@ u: [], ul: [] }; - // js-docs-end allow-list - - const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']); - - /** - * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation - * contexts. - * - * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38 - */ - const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i; - const allowedAttribute = (attribute, allowedAttributeList) => { - const attributeName = attribute.nodeName.toLowerCase(); - if (allowedAttributeList.includes(attributeName)) { - if (uriAttributes.has(attributeName)) { - return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue)); - } - return true; - } - - // Check if a regular expression validates the attribute. - return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName)); - }; function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) { if (!unsafeHtml.length) { return unsafeHtml; } + if (sanitizeFunction && typeof sanitizeFunction === 'function') { return sanitizeFunction(unsafeHtml); } + const domParser = new window.DOMParser(); const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html'); const elements = [].concat(...createdDocument.body.querySelectorAll('*')); + for (const element of elements) { const elementName = element.nodeName.toLowerCase(); + if (!Object.keys(allowList).includes(elementName)) { element.remove(); continue; } + const attributeList = [].concat(...element.attributes); const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []); + for (const attribute of attributeList) { if (!allowedAttribute(attribute, allowedAttributes)) { element.removeAttribute(attribute.nodeName); } } } + return createdDocument.body.innerHTML; } /** * -------------------------------------------------------------------------- - * Bootstrap util/template-factory.js + * Bootstrap (v5.2.3): util/template-factory.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - - /** * Constants */ @@ -4878,7 +5409,6 @@ entry: '(string|element|function|null)', selector: '(string|element)' }; - /** * Class definition */ @@ -4887,53 +5417,65 @@ constructor(config) { super(); this._config = this._getConfig(config); - } + } // Getters + - // Getters static get Default() { return Default$4; } + static get DefaultType() { return DefaultType$4; } + static get NAME() { return NAME$5; - } + } // Public + - // Public getContent() { return Object.values(this._config.content).map(config => this._resolvePossibleFunction(config)).filter(Boolean); } + hasContent() { return this.getContent().length > 0; } + changeContent(content) { this._checkContent(content); - this._config.content = { - ...this._config.content, + + this._config.content = { ...this._config.content, ...content }; return this; } + toHtml() { const templateWrapper = document.createElement('div'); templateWrapper.innerHTML = this._maybeSanitize(this._config.template); + for (const [selector, text] of Object.entries(this._config.content)) { this._setContent(templateWrapper, text, selector); } + const template = templateWrapper.children[0]; + const extraClass = this._resolvePossibleFunction(this._config.extraClass); + if (extraClass) { template.classList.add(...extraClass.split(' ')); } - return template; - } - // Private + return template; + } // Private + + _typeCheckConfig(config) { super._typeCheckConfig(config); + this._checkContent(config.content); } + _checkContent(arg) { for (const [selector, content] of Object.entries(arg)) { super._typeCheckConfig({ @@ -4942,50 +5484,61 @@ }, DefaultContentType); } } + _setContent(template, content, selector) { const templateElement = SelectorEngine.findOne(selector, template); + if (!templateElement) { return; } + content = this._resolvePossibleFunction(content); + if (!content) { templateElement.remove(); return; } + if (isElement$1(content)) { this._putElementInTemplate(getElement(content), templateElement); + return; } + if (this._config.html) { templateElement.innerHTML = this._maybeSanitize(content); return; } + templateElement.textContent = content; } + _maybeSanitize(arg) { return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg; } + _resolvePossibleFunction(arg) { - return execute(arg, [undefined, this]); + return typeof arg === 'function' ? arg(this) : arg; } + _putElementInTemplate(element, templateElement) { if (this._config.html) { templateElement.innerHTML = ''; templateElement.append(element); return; } + templateElement.textContent = element.textContent; } + } /** * -------------------------------------------------------------------------- - * Bootstrap tooltip.js + * Bootstrap (v5.2.3): tooltip.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - - /** * Constants */ @@ -5028,7 +5581,7 @@ delay: 0, fallbackPlacements: ['top', 'right', 'bottom', 'left'], html: false, - offset: [0, 6], + offset: [0, 0], placement: 'top', popperConfig: null, sanitize: true, @@ -5057,7 +5610,6 @@ title: '(string|element|function)', trigger: 'string' }; - /** * Class definition */ @@ -5065,131 +5617,164 @@ class Tooltip extends BaseComponent { constructor(element, config) { if (typeof Popper === 'undefined') { - throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org/docs/v2/)'); + throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org)'); } - super(element, config); - // Private + super(element, config); // Private + this._isEnabled = true; this._timeout = 0; this._isHovered = null; this._activeTrigger = {}; this._popper = null; this._templateFactory = null; - this._newContent = null; + this._newContent = null; // Protected - // Protected this.tip = null; + this._setListeners(); + if (!this._config.selector) { this._fixTitle(); } - } + } // Getters + - // Getters static get Default() { return Default$3; } + static get DefaultType() { return DefaultType$3; } + static get NAME() { return NAME$4; - } + } // Public + - // Public enable() { this._isEnabled = true; } + disable() { this._isEnabled = false; } + toggleEnabled() { this._isEnabled = !this._isEnabled; } + toggle() { if (!this._isEnabled) { return; } + + this._activeTrigger.click = !this._activeTrigger.click; + if (this._isShown()) { this._leave(); + return; } + this._enter(); } + dispose() { clearTimeout(this._timeout); EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler); + if (this._element.getAttribute('data-bs-original-title')) { this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title')); } + this._disposePopper(); + super.dispose(); } + show() { if (this._element.style.display === 'none') { throw new Error('Please use show on visible elements'); } + if (!(this._isWithContent() && this._isEnabled)) { return; } + const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW$2)); const shadowRoot = findShadowRoot(this._element); + const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element); + if (showEvent.defaultPrevented || !isInTheDom) { return; - } + } // todo v6 remove this OR make it optional + - // TODO: v6 remove this or make it optional this._disposePopper(); + const tip = this._getTipElement(); + this._element.setAttribute('aria-describedby', tip.getAttribute('id')); + const { container } = this._config; + if (!this._element.ownerDocument.documentElement.contains(this.tip)) { container.append(tip); EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED)); } - this._popper = this._createPopper(tip); - tip.classList.add(CLASS_NAME_SHOW$2); - // If this is a touch-enabled device we add extra + this._popper = this._createPopper(tip); + tip.classList.add(CLASS_NAME_SHOW$2); // If this is a touch-enabled device we add extra // empty mouseover listeners to the body's immediate children; // 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) { for (const element of [].concat(...document.body.children)) { EventHandler.on(element, 'mouseover', noop); } } + const complete = () => { EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN$2)); + if (this._isHovered === false) { this._leave(); } + this._isHovered = false; }; + this._queueCallback(complete, this.tip, this._isAnimated()); } + hide() { if (!this._isShown()) { return; } + const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE$2)); + if (hideEvent.defaultPrevented) { return; } - const tip = this._getTipElement(); - tip.classList.remove(CLASS_NAME_SHOW$2); - // If this is a touch-enabled device we remove the extra + const tip = this._getTipElement(); + + tip.classList.remove(CLASS_NAME_SHOW$2); // If this is a touch-enabled device we remove the extra // empty mouseover listeners we added for iOS support + if ('ontouchstart' in document.documentElement) { for (const element of [].concat(...document.body.children)) { EventHandler.off(element, 'mouseover', noop); } } + this._activeTrigger[TRIGGER_CLICK] = false; this._activeTrigger[TRIGGER_FOCUS] = false; this._activeTrigger[TRIGGER_HOVER] = false; @@ -5199,107 +5784,133 @@ if (this._isWithActiveTrigger()) { return; } + if (!this._isHovered) { this._disposePopper(); } + this._element.removeAttribute('aria-describedby'); + EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN$2)); }; + this._queueCallback(complete, this.tip, this._isAnimated()); } + update() { if (this._popper) { this._popper.update(); } - } + } // Protected + - // Protected _isWithContent() { return Boolean(this._getTitle()); } + _getTipElement() { if (!this.tip) { this.tip = this._createTipElement(this._newContent || this._getContentForTemplate()); } + return this.tip; } - _createTipElement(content) { - const tip = this._getTemplateFactory(content).toHtml(); - // TODO: remove this check in v6 + _createTipElement(content) { + const tip = this._getTemplateFactory(content).toHtml(); // todo: remove this check on v6 + + if (!tip) { return null; } - tip.classList.remove(CLASS_NAME_FADE$2, CLASS_NAME_SHOW$2); - // TODO: v6 the following can be achieved with CSS only + + tip.classList.remove(CLASS_NAME_FADE$2, CLASS_NAME_SHOW$2); // todo: on v6 the following can be achieved with CSS only + tip.classList.add(`bs-${this.constructor.NAME}-auto`); const tipId = getUID(this.constructor.NAME).toString(); tip.setAttribute('id', tipId); + if (this._isAnimated()) { tip.classList.add(CLASS_NAME_FADE$2); } + return tip; } + setContent(content) { this._newContent = content; + if (this._isShown()) { this._disposePopper(); + this.show(); } } + _getTemplateFactory(content) { if (this._templateFactory) { this._templateFactory.changeContent(content); } else { - this._templateFactory = new TemplateFactory({ - ...this._config, + this._templateFactory = new TemplateFactory({ ...this._config, // the `content` var has to be after `this._config` // to override config.content in case of popover content, extraClass: this._resolvePossibleFunction(this._config.customClass) }); } + return this._templateFactory; } + _getContentForTemplate() { return { [SELECTOR_TOOLTIP_INNER]: this._getTitle() }; } + _getTitle() { return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title'); - } + } // Private + - // Private _initializeOnDelegatedTarget(event) { return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig()); } + _isAnimated() { return this._config.animation || this.tip && this.tip.classList.contains(CLASS_NAME_FADE$2); } + _isShown() { return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW$2); } + _createPopper(tip) { - const placement = execute(this._config.placement, [this, tip, this._element]); + const placement = typeof this._config.placement === 'function' ? this._config.placement.call(this, tip, this._element) : this._config.placement; const attachment = AttachmentMap[placement.toUpperCase()]; return createPopper(this._element, tip, this._getPopperConfig(attachment)); } + _getOffset() { const { offset } = this._config; + if (typeof offset === 'string') { return offset.split(',').map(value => Number.parseInt(value, 10)); } + if (typeof offset === 'function') { return popperData => offset(popperData, this._element); } + return offset; } + _resolvePossibleFunction(arg) { - return execute(arg, [this._element, this._element]); + return typeof arg === 'function' ? arg.call(this._element) : arg; } + _getPopperConfig(attachment) { const defaultBsPopperConfig = { placement: attachment, @@ -5334,18 +5945,19 @@ } }] }; - return { - ...defaultBsPopperConfig, - ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig]) + return { ...defaultBsPopperConfig, + ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig) }; } + _setListeners() { const triggers = this._config.trigger.split(' '); + for (const trigger of triggers) { if (trigger === 'click') { EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK$1), this._config.selector, event => { const context = this._initializeOnDelegatedTarget(event); - context._activeTrigger[TRIGGER_CLICK] = !(context._isShown() && context._activeTrigger[TRIGGER_CLICK]); + context.toggle(); }); } else if (trigger !== TRIGGER_MANUAL) { @@ -5353,151 +5965,187 @@ const eventOut = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSELEAVE) : this.constructor.eventName(EVENT_FOCUSOUT$1); EventHandler.on(this._element, eventIn, this._config.selector, event => { const context = this._initializeOnDelegatedTarget(event); + context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true; + context._enter(); }); EventHandler.on(this._element, eventOut, this._config.selector, event => { const context = this._initializeOnDelegatedTarget(event); + context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = context._element.contains(event.relatedTarget); + context._leave(); }); } } + this._hideModalHandler = () => { if (this._element) { this.hide(); } }; + EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler); } + _fixTitle() { const title = this._element.getAttribute('title'); + if (!title) { return; } + if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) { this._element.setAttribute('aria-label', title); } + this._element.setAttribute('data-bs-original-title', title); // DO NOT USE IT. Is only for backwards compatibility + + this._element.removeAttribute('title'); } + _enter() { if (this._isShown() || this._isHovered) { this._isHovered = true; return; } + this._isHovered = true; + this._setTimeout(() => { if (this._isHovered) { this.show(); } }, this._config.delay.show); } + _leave() { if (this._isWithActiveTrigger()) { return; } + this._isHovered = false; + this._setTimeout(() => { if (!this._isHovered) { this.hide(); } }, this._config.delay.hide); } + _setTimeout(handler, timeout) { clearTimeout(this._timeout); this._timeout = setTimeout(handler, timeout); } + _isWithActiveTrigger() { return Object.values(this._activeTrigger).includes(true); } + _getConfig(config) { const dataAttributes = Manipulator.getDataAttributes(this._element); + for (const dataAttribute of Object.keys(dataAttributes)) { if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) { delete dataAttributes[dataAttribute]; } } - config = { - ...dataAttributes, + + config = { ...dataAttributes, ...(typeof config === 'object' && config ? config : {}) }; config = this._mergeConfigObj(config); config = this._configAfterMerge(config); + this._typeCheckConfig(config); + return config; } + _configAfterMerge(config) { config.container = config.container === false ? document.body : getElement(config.container); + if (typeof config.delay === 'number') { config.delay = { show: config.delay, hide: config.delay }; } + if (typeof config.title === 'number') { config.title = config.title.toString(); } + if (typeof config.content === 'number') { config.content = config.content.toString(); } + return config; } + _getDelegateConfig() { const config = {}; - for (const [key, value] of Object.entries(this._config)) { - if (this.constructor.Default[key] !== value) { - config[key] = value; + + for (const key in this._config) { + if (this.constructor.Default[key] !== this._config[key]) { + config[key] = this._config[key]; } } - config.selector = false; - config.trigger = 'manual'; - // In the future can be replaced with: + config.selector = false; + config.trigger = 'manual'; // In the future can be replaced with: // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]]) // `Object.fromEntries(keysWithDifferentValues)` + return config; } + _disposePopper() { if (this._popper) { this._popper.destroy(); + this._popper = null; } + if (this.tip) { this.tip.remove(); this.tip = null; } - } + } // Static + - // Static static jQueryInterface(config) { return this.each(function () { const data = Tooltip.getOrCreateInstance(this, config); + if (typeof config !== 'string') { return; } + if (typeof data[config] === 'undefined') { throw new TypeError(`No method named "${config}"`); } + data[config](); }); } - } + } /** * jQuery */ + defineJQueryPlugin(Tooltip); /** * -------------------------------------------------------------------------- - * Bootstrap popover.js + * Bootstrap (v5.2.3): popover.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - - /** * Constants */ @@ -5505,19 +6153,16 @@ const NAME$3 = 'popover'; const SELECTOR_TITLE = '.popover-header'; const SELECTOR_CONTENT = '.popover-body'; - const Default$2 = { - ...Tooltip.Default, + const Default$2 = { ...Tooltip.Default, content: '', offset: [0, 8], placement: 'right', template: '', trigger: 'click' }; - const DefaultType$2 = { - ...Tooltip.DefaultType, + const DefaultType$2 = { ...Tooltip.DefaultType, content: '(null|string|element|function)' }; - /** * Class definition */ @@ -5527,58 +6172,63 @@ static get Default() { return Default$2; } + static get DefaultType() { return DefaultType$2; } + static get NAME() { return NAME$3; - } + } // Overrides + - // Overrides _isWithContent() { return this._getTitle() || this._getContent(); - } + } // Private + - // Private _getContentForTemplate() { return { [SELECTOR_TITLE]: this._getTitle(), [SELECTOR_CONTENT]: this._getContent() }; } + _getContent() { return this._resolvePossibleFunction(this._config.content); - } + } // Static + - // Static static jQueryInterface(config) { return this.each(function () { const data = Popover.getOrCreateInstance(this, config); + if (typeof config !== 'string') { return; } + if (typeof data[config] === 'undefined') { throw new TypeError(`No method named "${config}"`); } + data[config](); }); } - } + } /** * jQuery */ + defineJQueryPlugin(Popover); /** * -------------------------------------------------------------------------- - * Bootstrap scrollspy.js + * Bootstrap (v5.2.3): scrollspy.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - - /** * Constants */ @@ -5617,16 +6267,14 @@ target: 'element', threshold: 'array' }; - /** * Class definition */ class ScrollSpy extends BaseComponent { constructor(element, config) { - super(element, config); + super(element, config); // this._element is the observablesContainer and config.target the menu links wrapper - // this._element is the observablesContainer and config.target the menu links wrapper this._targetLinks = new Map(); this._observableSections = new Map(); this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element; @@ -5637,75 +6285,87 @@ parentScrollTop: 0 }; this.refresh(); // initialize - } + } // Getters + - // Getters static get Default() { return Default$1; } + static get DefaultType() { return DefaultType$1; } + static get NAME() { return NAME$2; - } + } // Public + - // Public refresh() { this._initializeTargetsAndObservables(); + this._maybeEnableSmoothScroll(); + if (this._observer) { this._observer.disconnect(); } else { this._observer = this._getNewObserver(); } + for (const section of this._observableSections.values()) { this._observer.observe(section); } } + dispose() { this._observer.disconnect(); - super.dispose(); - } - // Private + super.dispose(); + } // Private + + _configAfterMerge(config) { // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case - config.target = getElement(config.target) || document.body; + config.target = getElement(config.target) || document.body; // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only - // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin; + if (typeof config.threshold === 'string') { config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value)); } + return config; } + _maybeEnableSmoothScroll() { if (!this._config.smoothScroll) { return; - } + } // unregister any previous listeners + - // unregister any previous listeners EventHandler.off(this._config.target, EVENT_CLICK); EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => { const observableSection = this._observableSections.get(event.target.hash); + if (observableSection) { event.preventDefault(); const root = this._rootElement || window; const height = observableSection.offsetTop - this._element.offsetTop; + if (root.scrollTo) { root.scrollTo({ top: height, behavior: 'smooth' }); return; - } + } // Chrome 60 doesn't support `scrollTo` + - // Chrome 60 doesn't support `scrollTo` root.scrollTop = height; } }); } + _getNewObserver() { const options = { root: this._rootElement, @@ -5713,77 +6373,95 @@ rootMargin: this._config.rootMargin }; return new IntersectionObserver(entries => this._observerCallback(entries), options); - } + } // The logic of selection + - // The logic of selection _observerCallback(entries) { const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`); + const activate = entry => { this._previousScrollData.visibleEntryTop = entry.target.offsetTop; + this._process(targetElement(entry)); }; + const parentScrollTop = (this._rootElement || document.documentElement).scrollTop; const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop; this._previousScrollData.parentScrollTop = parentScrollTop; + for (const entry of entries) { if (!entry.isIntersecting) { this._activeTarget = null; + this._clearActiveClass(targetElement(entry)); - continue; - } - const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop; - // if we are scrolling down, pick the bigger offsetTop - if (userScrollsDown && entryIsLowerThanPrevious) { - activate(entry); - // if parent isn't scrolled, let's keep the first visible item, breaking the iteration - if (!parentScrollTop) { - return; - } + continue; } - // if we are scrolling up, pick the smallest offsetTop + const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop; // if we are scrolling down, pick the bigger offsetTop + + if (userScrollsDown && entryIsLowerThanPrevious) { + activate(entry); // if parent isn't scrolled, let's keep the first visible item, breaking the iteration + + if (!parentScrollTop) { + return; + } + + continue; + } // if we are scrolling up, pick the smallest offsetTop + + if (!userScrollsDown && !entryIsLowerThanPrevious) { activate(entry); } } } + _initializeTargetsAndObservables() { this._targetLinks = new Map(); this._observableSections = new Map(); const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target); + for (const anchor of targetLinks) { // ensure that the anchor has an id and is not disabled if (!anchor.hash || isDisabled(anchor)) { continue; } - const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element); - // ensure that the observableSection exists & is visible + const observableSection = SelectorEngine.findOne(anchor.hash, this._element); // ensure that the observableSection exists & is visible + if (isVisible(observableSection)) { - this._targetLinks.set(decodeURI(anchor.hash), anchor); + this._targetLinks.set(anchor.hash, anchor); + this._observableSections.set(anchor.hash, observableSection); } } } + _process(target) { if (this._activeTarget === target) { return; } + this._clearActiveClass(this._config.target); + this._activeTarget = target; target.classList.add(CLASS_NAME_ACTIVE$1); + this._activateParents(target); + EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target }); } + _activateParents(target) { // Activate dropdown parents if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) { SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE$1, target.closest(SELECTOR_DROPDOWN)).classList.add(CLASS_NAME_ACTIVE$1); return; } + for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) { // Set triggered links parents as active // With both `} /> +{{< example >}} + +{{< /example >}} ### Text -Place any freeform text within a dropdown menu with text and use [spacing utilities]([[docsref:/utilities/spacing]]). Note that you’ll likely need additional sizing styles to constrain the menu width. +Place any freeform text within a dropdown menu with text and use [spacing utilities]({{< docsref "/utilities/spacing" >}}). Note that you'll likely need additional sizing styles to constrain the menu width. - -

- Some example text that’s free-flowing within the dropdown menu. -

-

- And this is more example text. -

- `} /> +{{< example >}} + +{{< /example >}} ### Forms -Put a form within a dropdown menu, or make it into a dropdown menu, and use [margin or padding utilities]([[docsref:/utilities/spacing]]) to give it the negative space you require. +Put a form within a dropdown menu, or make it into a dropdown menu, and use [margin or padding utilities]({{< docsref "/utilities/spacing" >}}) to give it the negative space you require. - -
-
- - +{{< example >}} + + + + + New around here? Sign up + Forgot password? +
+{{< /example >}} - - - +{{< /example >}} ## Dropdown options Use `data-bs-offset` or `data-bs-reference` to change the location of the dropdown. - - -
- - - -
- `} /> +{{< example >}} +
+ +
+ + + +
+
+{{< /example >}} ### Auto close behavior By default, the dropdown menu is closed when clicking inside or outside the dropdown menu. You can use the `autoClose` option to change this behavior of the dropdown. - - - - +{{< example >}} +
+ + +
-
- - -
+
+ + +
-
- - -
+
+ + +
-
- - -
`} /> +
+ + +
+{{< /example >}} ## CSS ### Variables - +{{< added-in "5.2.0" >}} -As part of Bootstrap’s evolving CSS variables approach, dropdowns now use local CSS variables on `.dropdown-menu` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too. +As part of Bootstrap's evolving CSS variables approach, dropdowns now use local CSS variables on `.dropdown-menu` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too. - - - -Dropdown items include at least one variable that is not set on `.dropdown`. This allows you to provide a new value while Bootstrap defaults to a fallback value. - -- `--bs-dropdown-item-border-radius` - +{{< scss-docs name="dropdown-css-vars" file="scss/_dropdown.scss" >}} Customization through CSS variables can be seen on the `.dropdown-menu-dark` class where we override specific values without adding duplicate CSS selectors. - +{{< scss-docs name="dropdown-dark-css-vars" file="scss/_dropdown.scss" >}} ### Sass variables Variables for all dropdowns: - +{{< scss-docs name="dropdown-variables" file="scss/_variables.scss" >}} Variables for the [dark dropdown](#dark-dropdowns): - +{{< scss-docs name="dropdown-dark-variables" file="scss/_variables.scss" >}} -Variables for the CSS-based carets that indicate a dropdown’s interactivity: +Variables for the CSS-based carets that indicate a dropdown's interactivity: - +{{< scss-docs name="caret-variables" file="scss/_variables.scss" >}} -### Sass mixins +### Mixins Mixins are used to generate the CSS-based carets and can be found in `scss/mixins/_caret.scss`. - +{{< scss-docs name="caret-mixins" file="scss/mixins/_caret.scss" >}} ## Usage -Via data attributes or JavaScript, the dropdown plugin toggles hidden content (dropdown menus) by toggling the `.show` class on the parent `.dropdown-menu`. The `data-bs-toggle="dropdown"` attribute is relied on for closing dropdown menus at an application level, so it’s a good idea to always use it. +Via data attributes or JavaScript, the dropdown plugin toggles hidden content (dropdown menus) by toggling the `.show` class on the parent `.dropdown-menu`. The `data-bs-toggle="dropdown"` attribute is relied on for closing dropdown menus at an application level, so it's a good idea to always use it. - -On touch-enabled devices, opening a dropdown adds empty `mouseover` handlers to the immediate children of the `` element. This admittedly ugly hack is necessary to work around a [quirk in iOs’ event delegation](https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html), which would otherwise prevent a tap anywhere outside of the dropdown from triggering the code that closes the dropdown. Once the dropdown is closed, these additional empty `mouseover` handlers are removed. - +{{< callout info >}} +On touch-enabled devices, opening a dropdown adds empty `mouseover` handlers to the immediate children of the `` element. This admittedly ugly hack is necessary to work around a [quirk in iOS' event delegation](https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html), which would otherwise prevent a tap anywhere outside of the dropdown from triggering the code that closes the dropdown. Once the dropdown is closed, these additional empty `mouseover` handlers are removed. +{{< /callout >}} ### Via data attributes @@ -992,10 +1047,6 @@ Add `data-bs-toggle="dropdown"` to a link or button to toggle a dropdown. ### Via JavaScript - -Dropdowns must have `data-bs-toggle="dropdown"` on their trigger element, regardless of whether you call your dropdown via JavaScript or use the data-api. - - Call the dropdowns via JavaScript: ```js @@ -1003,20 +1054,28 @@ const dropdownElementList = document.querySelectorAll('.dropdown-toggle') const dropdownList = [...dropdownElementList].map(dropdownToggleEl => new bootstrap.Dropdown(dropdownToggleEl)) ``` +{{< callout info >}} +##### `data-bs-toggle="dropdown"` still required + +Regardless of whether you call your dropdown via JavaScript or instead use the data-api, `data-bs-toggle="dropdown"` is always required to be present on the dropdown's trigger element. +{{< /callout >}} + ### Options - +{{< markdown >}} +{{< partial "js-data-attributes.md" >}} +{{< /markdown >}} - +{{< bs-table "table" >}} | Name | Type | Default | Description | | --- | --- | --- | --- | -| `autoClose` | boolean, string | `true` | Configure the auto close behavior of the dropdown:
  • `true` - the dropdown will be closed by clicking outside or inside the dropdown menu.
  • `false` - the dropdown will be closed by clicking the toggle button and manually calling `hide` or `toggle` method. (Also will not be closed by pressing Esc key)
  • `'inside'` - the dropdown will be closed (only) by clicking inside the dropdown menu.
  • `'outside'` - the dropdown will be closed (only) by clicking outside the dropdown menu.
Note: the dropdown can always be closed with the Esc key. | -| `boundary` | string, element | `'clippingParents'` | Overflow constraint boundary of the dropdown menu (applies only to Popper’s preventOverflow modifier). By default it’s `clippingParents` and can accept an HTMLElement reference (via JavaScript only). For more information refer to Popper’s [detectOverflow docs](https://popper.js.org/docs/v2/utils/detect-overflow/#boundary). | +| `autoClose` | boolean, string | `true` | Configure the auto close behavior of the dropdown:
  • `true` - the dropdown will be closed by clicking outside or inside the dropdown menu.
  • `false` - the dropdown will be closed by clicking the toggle button and manually calling `hide` or `toggle` method. (Also will not be closed by pressing esc key)
  • `'inside'` - the dropdown will be closed (only) by clicking inside the dropdown menu.
  • `'outside'` - the dropdown will be closed (only) by clicking outside the dropdown menu.
Note: the dropdown can always be closed with the ESC key. | +| `boundary` | string, element | `'clippingParents'` | Overflow constraint boundary of the dropdown menu (applies only to Popper's preventOverflow modifier). By default it's `clippingParents` and can accept an HTMLElement reference (via JavaScript only). For more information refer to Popper's [detectOverflow docs](https://popper.js.org/docs/v2/utils/detect-overflow/#boundary). | | `display` | string | `'dynamic'` | By default, we use Popper for dynamic positioning. Disable this with `static`. | -| `offset` | array, string, function | `[0, 2]` | Offset of the dropdown relative to its target. You can pass a string in data attributes with comma separated values like: `data-bs-offset="10,20"`. When a function is used to determine the offset, it is called with an object containing the popper placement, the reference, and popper rects as its first argument. The triggering element DOM node is passed as the second argument. The function must return an array with two numbers: [skidding](https://popper.js.org/docs/v2/modifiers/offset/#skidding-1), [distance](https://popper.js.org/docs/v2/modifiers/offset/#distance-1). For more information refer to Popper’s [offset docs](https://popper.js.org/docs/v2/modifiers/offset/#options). | -| `popperConfig` | null, object, function | `null` | To change Bootstrap’s default Popper config, see [Popper’s configuration](https://popper.js.org/docs/v2/constructors/#options). When a function is used to create the Popper configuration, it’s called with an object that contains the Bootstrap’s default Popper configuration. It helps you use and merge the default with your own configuration. The function must return a configuration object for Popper. | -| `reference` | string, element, object | `'toggle'` | Reference element of the dropdown menu. Accepts the values of `'toggle'`, `'parent'`, an HTMLElement reference or an object providing `getBoundingClientRect`. For more information refer to Popper’s [constructor docs](https://popper.js.org/docs/v2/constructors/#createpopper) and [virtual element docs](https://popper.js.org/docs/v2/virtual-elements/). | -
+| `offset` | array, string, function | `[0, 2]` | Offset of the dropdown relative to its target. You can pass a string in data attributes with comma separated values like: `data-bs-offset="10,20"`. When a function is used to determine the offset, it is called with an object containing the popper placement, the reference, and popper rects as its first argument. The triggering element DOM node is passed as the second argument. The function must return an array with two numbers: [skidding](https://popper.js.org/docs/v2/modifiers/offset/#skidding-1), [distance](https://popper.js.org/docs/v2/modifiers/offset/#distance-1). For more information refer to Popper's [offset docs](https://popper.js.org/docs/v2/modifiers/offset/#options). | +| `popperConfig` | null, object, function | `null` | To change Bootstrap's default Popper config, see [Popper's configuration](https://popper.js.org/docs/v2/constructors/#options). When a function is used to create the Popper configuration, it's called with an object that contains the Bootstrap's default Popper configuration. It helps you use and merge the default with your own configuration. The function must return a configuration object for Popper. | +| `reference` | string, element, object | `'toggle'` | Reference element of the dropdown menu. Accepts the values of `'toggle'`, `'parent'`, an HTMLElement reference or an object providing `getBoundingClientRect`. For more information refer to Popper's [constructor docs](https://popper.js.org/docs/v2/constructors/#createpopper) and [virtual element docs](https://popper.js.org/docs/v2/virtual-elements/). | +{{< /bs-table >}} #### Using function with `popperConfig` @@ -1032,30 +1091,30 @@ const dropdown = new bootstrap.Dropdown(element, { ### Methods - +{{< bs-table >}} | Method | Description | | --- | --- | -| `dispose` | Destroys an element’s dropdown. (Removes stored data on the DOM element) | +| `dispose` | Destroys an element's dropdown. (Removes stored data on the DOM element) | | `getInstance` | Static method which allows you to get the dropdown instance associated to a DOM element, you can use it like this: `bootstrap.Dropdown.getInstance(element)`. | -| `getOrCreateInstance` | Static method which returns a dropdown instance associated to a DOM element or create a new one in case it wasn’t initialized. You can use it like this: `bootstrap.Dropdown.getOrCreateInstance(element)`. | +| `getOrCreateInstance` | Static method which returns a dropdown instance associated to a DOM element or create a new one in case it wasn't initialized. You can use it like this: `bootstrap.Dropdown.getOrCreateInstance(element)`. | | `hide` | Hides the dropdown menu of a given navbar or tabbed navigation. | | `show` | Shows the dropdown menu of a given navbar or tabbed navigation. | | `toggle` | Toggles the dropdown menu of a given navbar or tabbed navigation. | -| `update` | Updates the position of an element’s dropdown. | - +| `update` | Updates the position of an element's dropdown. | +{{< /bs-table >}} ### Events -All dropdown events are fired at the toggling element and then bubbled up. So you can also add event listeners on the `.dropdown-menu`’s parent element. `hide.bs.dropdown` and `hidden.bs.dropdown` events have a `clickEvent` property (only when the original Event type is `click`) that contains an Event Object for the click event. +All dropdown events are fired at the toggling element and then bubbled up. So you can also add event listeners on the `.dropdown-menu`'s parent element. `hide.bs.dropdown` and `hidden.bs.dropdown` events have a `clickEvent` property (only when the original Event type is `click`) that contains an Event Object for the click event. - +{{< bs-table >}} | Event type | Description | | --- | --- | | `hide.bs.dropdown` | Fires immediately when the `hide` instance method has been called. | | `hidden.bs.dropdown` | Fired when the dropdown has finished being hidden from the user and CSS transitions have completed. | | `show.bs.dropdown` | Fires immediately when the `show` instance method is called. | | `shown.bs.dropdown` | Fired when the dropdown has been made visible to the user and CSS transitions have completed. | - +{{< /bs-table >}} ```js const myDropdown = document.getElementById('myDropdown') diff --git a/site/src/content/docs/components/list-group.mdx b/site/content/docs/5.2/components/list-group.md similarity index 51% rename from site/src/content/docs/components/list-group.mdx rename to site/content/docs/5.2/components/list-group.md index 59827dd..7804c74 100644 --- a/site/src/content/docs/components/list-group.mdx +++ b/site/content/docs/5.2/components/list-group.md @@ -1,76 +1,98 @@ --- +layout: docs title: List group description: List groups are a flexible and powerful component for displaying a series of content. Modify and extend them to support just about any content within. +group: components toc: true --- -import { getData } from '@libs/data' - ## Basic example The most basic list group is an unordered list with list items and the proper classes. Build upon it with the options that follow, or with your own CSS as needed. - -
  • An item
  • -
  • A second item
  • -
  • A third item
  • -
  • A fourth item
  • -
  • And a fifth one
  • - `} /> +{{< example >}} +
      +
    • An item
    • +
    • A second item
    • +
    • A third item
    • +
    • A fourth item
    • +
    • And a fifth one
    • +
    +{{< /example >}} ## Active items Add `.active` to a `.list-group-item` to indicate the current active selection. - -
  • An active item
  • -
  • A second item
  • -
  • A third item
  • -
  • A fourth item
  • -
  • And a fifth one
  • - `} /> +{{< example >}} +
      +
    • An active item
    • +
    • A second item
    • +
    • A third item
    • +
    • A fourth item
    • +
    • And a fifth one
    • +
    +{{< /example >}} + +## Disabled items + +Add `.disabled` to a `.list-group-item` to make it _appear_ disabled. Note that some elements with `.disabled` will also require custom JavaScript to fully disable their click events (e.g., links). + +{{< example >}} +
      +
    • A disabled item
    • +
    • A second item
    • +
    • A third item
    • +
    • A fourth item
    • +
    • And a fifth one
    • +
    +{{< /example >}} ## Links and buttons -Use ``s or ` - - - - - `} /> +{{< example >}} +
    + + + + + +
    +{{< /example >}} ## Flush Add `.list-group-flush` to remove some borders and rounded corners to render list group items edge-to-edge in a parent container (e.g., cards). - -
  • An item
  • -
  • A second item
  • -
  • A third item
  • -
  • A fourth item
  • -
  • And a fifth one
  • - `} /> +{{< example >}} +
      +
    • An item
    • +
    • A second item
    • +
    • A third item
    • +
    • A fourth item
    • +
    • And a fifth one
    • +
    +{{< /example >}} ## Numbered @@ -78,206 +100,228 @@ Add the `.list-group-numbered` modifier class (and optionally use an `
      ` elem Numbers are generated by `counter-reset` on the `
        `, and then styled and placed with a `::before` pseudo-element on the `
      1. ` with `counter-increment` and `content`. - -
      2. A list item
      3. -
      4. A list item
      5. -
      6. A list item
      7. -
      `} /> +{{< example >}} +
        +
      1. A list item
      2. +
      3. A list item
      4. +
      5. A list item
      6. +
      +{{< /example >}} These work great with custom content as well. - -
    1. -
      -
      Subheading
      - Content for list item -
      - 14 -
    2. -
    3. -
      -
      Subheading
      - Content for list item -
      - 14 -
    4. -
    5. -
      -
      Subheading
      - Content for list item -
      - 14 -
    6. -
    `} /> +{{< example >}} +
      +
    1. +
      +
      Subheading
      + Content for list item +
      + 14 +
    2. +
    3. +
      +
      Subheading
      + Content for list item +
      + 14 +
    4. +
    5. +
      +
      Subheading
      + Content for list item +
      + 14 +
    6. +
    +{{< /example >}} ## Horizontal -Add `.list-group-horizontal` to change the layout of list group items from vertical to horizontal across all breakpoints. Alternatively, choose a responsive variant `.list-group-horizontal-{sm|md|lg|xl|xxl}` to make a list group horizontal starting at that breakpoint’s `min-width`. Currently **horizontal list groups cannot be combined with flush list groups.** +Add `.list-group-horizontal` to change the layout of list group items from vertical to horizontal across all breakpoints. Alternatively, choose a responsive variant `.list-group-horizontal-{sm|md|lg|xl|xxl}` to make a list group horizontal starting at that breakpoint's `min-width`. Currently **horizontal list groups cannot be combined with flush list groups.** **ProTip:** Want equal-width list group items when horizontal? Add `.flex-fill` to each list group item. - `
      -
    • An item
    • -
    • A second item
    • -
    • A third item
    • -
    `)} /> +{{< example >}} +{{< list-group.inline >}} +{{- range $.Site.Data.breakpoints }} +
      +
    • An item
    • +
    • A second item
    • +
    • A third item
    • +
    +{{- end -}} +{{< /list-group.inline >}} +{{< /example >}} -## Variants - - -**Heads up!** As of v5.3.0, the `list-group-item-variant()` Sass mixin is deprecated. List group item variants now have their CSS variables overridden in [a Sass loop](#sass-loops). - +## Contextual classes Use contextual classes to style list items with a stateful background and color. - -
  • A simple default list group item
  • - `, - ...getData('theme-colors').map((themeColor) => `
  • A simple ${themeColor.name} list group item
  • `), - `` - ]} /> +{{< example >}} +
      +
    • A simple default list group item
    • +{{< list.inline >}} +{{- range (index $.Site.Data "theme-colors") }} +
    • A simple {{ .name }} list group item
    • +{{- end -}} +{{< /list.inline >}} +
    +{{< /example >}} -### For links and buttons +Contextual classes also work with `.list-group-item-action`. Note the addition of the hover styles here not present in the previous example. Also supported is the `.active` state; apply it to indicate an active selection on a contextual list group item. -Contextual classes also work with `.list-group-item-action` for `
    ` and ` -Launch demo modal -`} /> +
    + +
    ```html @@ -142,7 +148,7 @@ When backdrop is set to static, the modal will not close when clicking outside o -Launch static backdrop modal -`} /> +
    + +
    ```html @@ -184,7 +192,7 @@ When backdrop is set to static, the modal will not close when clicking outside o ### Scrolling long content -When modals become too long for the user’s viewport or device, they scroll independent of the page itself. Try the demo below to see what we mean. +When modals become too long for the user's viewport or device, they scroll independent of the page itself. Try the demo below to see what we mean. `} /> - - -Button groups require an appropriate `role` attribute and explicit label to ensure assistive technologies like screen readers identify buttons as grouped and announce them. Use `role="group"` for button groups or `role="toolbar"` for button toolbars. Then use `aria-label` or `aria-labelledby` to label them. - - -These classes can also be added to groups of links, as an alternative to the [`.nav` navigation components]([[docsref:/components/navs-tabs]]). - - - Active link - Link - Link - `} /> - -## Mixed styles - - - - - - `} /> - -## Outlined styles - - - - - - `} /> - -## Checkbox and radio button groups - -Combine button-like checkbox and radio [toggle buttons]([[docsref:/forms/checks-radios]]) into a seamless looking button group. - - - - - - - - - - - `} /> - - - - - - - - - - - `} /> - -## Button toolbar - -Combine sets of button groups into button toolbars for more complex components. Use utility classes as needed to space out groups, buttons, and more. - - -
    - - - - -
    -
    - - - -
    -
    - -
    - `} /> - -Feel free to mix input groups with button groups in your toolbars. Similar to the example above, you’ll likely need some utilities though to space things properly. - - -
    - - - - -
    -
    -
    @
    - -
    - - - `} /> - -## Sizing - -Instead of applying button sizing classes to every button in a group, just add `.btn-group-*` to each `.btn-group`, including each one when nesting multiple groups. - - - - - - -
    -
    - - - -
    -
    -
    - - - -
    `} /> - -## Nesting - -Place a `.btn-group` within another `.btn-group` when you want dropdown menus mixed with a series of buttons. - - - - - -
    - - -
    - `} /> - -## Vertical variation - -Make a set of buttons appear vertically stacked rather than horizontally. **Split button dropdowns are not supported here.** - - - - - - - `} /> - - -
    - - -
    - - -
    - - -
    -
    - - -
    -
    - - -
    - `} /> - - - - - - - - - `} /> diff --git a/site/src/content/docs/components/buttons.mdx b/site/src/content/docs/components/buttons.mdx deleted file mode 100644 index 3cf99ae..0000000 --- a/site/src/content/docs/components/buttons.mdx +++ /dev/null @@ -1,227 +0,0 @@ ---- -title: Buttons -description: Use Bootstrap’s custom button styles for actions in forms, dialogs, and more with support for multiple sizes, states, and more. -toc: true ---- - -import { getData } from '@libs/data' - -## Base class - -Bootstrap has a base `.btn` class that sets up basic styles such as padding and content alignment. By default, `.btn` controls have a transparent border and background color, and lack any explicit focus and hover styles. - -Base class`} /> - -The `.btn` class is intended to be used in conjunction with our button variants, or to serve as a basis for your own custom styles. - - -If you are using the `.btn` class on its own, remember to at least define some explicit `:focus` and/or `:focus-visible` styles. - - -## Variants - -Bootstrap includes several button variants, each serving its own semantic purpose, with a few extras thrown in for more control. - - ``), ` -`]} /> - - - -## Disable text wrapping - -If you don’t want the button text to wrap, you can add the `.text-nowrap` class to the button. In Sass, you can set `$btn-white-space: nowrap` to disable text wrapping for each button. - -## Button tags - -The `.btn` classes are designed to be used with the ` - - -`} /> - -## Outline buttons - -In need of a button, but not the hefty background colors they bring? Replace the default modifier classes with the `.btn-outline-*` ones to remove all background images and colors on any button. - - ``)} /> - - -Some of the button styles use a relatively light foreground color, and should only be used on a dark background in order to have sufficient contrast. - - -## Sizes - -Fancy larger or smaller buttons? Add `.btn-lg` or `.btn-sm` for additional sizes. - -Large button -`} /> - -Small button -`} /> - -You can even roll your own custom sizing with CSS variables: - - - Custom button - `} /> - -## Disabled state - -Make buttons look inactive by adding the `disabled` boolean attribute to any ` - - -`} /> - -Disabled buttons using the `` element behave a bit different: - -- ``s don’t support the `disabled` attribute, so you must add the `.disabled` class to make it visually appear disabled. -- Some future-friendly styles are included to disable all `pointer-events` on anchor buttons. -- Disabled buttons using `` should include the `aria-disabled="true"` attribute to indicate the state of the element to assistive technologies. -- Disabled buttons using `` *should not* include the `href` attribute. - -Primary link -Link`} /> - -### Link functionality caveat - -To cover cases where you have to keep the `href` attribute on a disabled link, the `.disabled` class uses `pointer-events: none` to try to disable the link functionality of ``s. Note that this CSS property is not yet standardized for HTML, but all modern browsers support it. In addition, even in browsers that do support `pointer-events: none`, keyboard navigation remains unaffected, meaning that sighted keyboard users and users of assistive technologies will still be able to activate these links. So to be safe, in addition to `aria-disabled="true"`, also include a `tabindex="-1"` attribute on these links to prevent them from receiving keyboard focus, and use custom JavaScript to disable their functionality altogether. - -Primary link -Link`} /> - -## Block buttons - -Create responsive stacks of full-width, “block buttons” like those in Bootstrap 4 with a mix of our display and gap utilities. By using utilities instead of button-specific classes, we have much greater control over spacing, alignment, and responsive behaviors. - - - - - `} /> - -Here we create a responsive variation, starting with vertically stacked buttons until the `md` breakpoint, where `.d-md-block` replaces the `.d-grid` class, thus nullifying the `gap-2` utility. Resize your browser to see them change. - - - - - `} /> - -You can adjust the width of your block buttons with grid column width classes. For example, for a half-width “block button”, use `.col-6`. Center it horizontally with `.mx-auto`, too. - - - - - `} /> - -Additional utilities can be used to adjust the alignment of buttons when horizontal. Here we’ve taken our previous responsive example and added some flex utilities and a margin utility on the button to right-align the buttons when they’re no longer stacked. - - - - - `} /> - -## Button plugin - -The button plugin allows you to create simple on/off toggle buttons. - - -Visually, these toggle buttons are identical to the [checkbox toggle buttons]([[docsref:/forms/checks-radios#checkbox-toggle-buttons]]). However, they are conveyed differently by assistive technologies: the checkbox toggles will be announced by screen readers as “checked”/“not checked” (since, despite their appearance, they are fundamentally still checkboxes), whereas these toggle buttons will be announced as “button”/“button pressed”. The choice between these two approaches will depend on the type of toggle you are creating, and whether or not the toggle will make sense to users when announced as a checkbox or as an actual button. - - -### Toggle states - -Add `data-bs-toggle="button"` to toggle a button’s `active` state. If you’re pre-toggling a button, you must manually add the `.active` class **and** `aria-pressed="true"` to ensure that it is conveyed appropriately to assistive technologies. - - - - - -

    -

    - - - -

    `} /> - - - Toggle link - Active toggle link - Disabled toggle link -

    -

    - Toggle link - Active toggle link - Disabled toggle link -

    `} /> - -### Methods - -You can create a button instance with the button constructor, for example: - -```js -const bsButton = new bootstrap.Button('#myButton') -``` - - -| Method | Description | -| --- | --- | -| `dispose` | Destroys an element’s button. (Removes stored data on the DOM element) | -| `getInstance` | Static method which allows you to get the button instance associated with a DOM element, you can use it like this: `bootstrap.Button.getInstance(element)`. | -| `getOrCreateInstance` | Static method which returns a button instance associated with a DOM element or creates a new one in case it wasn’t initialized. You can use it like this: `bootstrap.Button.getOrCreateInstance(element)`. | -| `toggle` | Toggles push state. Gives the button the appearance that it has been activated. | - - -For example, to toggle all buttons - -```js -document.querySelectorAll('.btn').forEach(buttonElement => { - const button = bootstrap.Button.getOrCreateInstance(buttonElement) - button.toggle() -}) -``` - -## CSS - -### Variables - - - -As part of Bootstrap’s evolving CSS variables approach, buttons now use local CSS variables on `.btn` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too. - - - -Each `.btn-*` modifier class updates the appropriate CSS variables to minimize additional CSS rules with our `button-variant()`, `button-outline-variant()`, and `button-size()` mixins. - -Here’s an example of building a custom `.btn-*` modifier class as we do for the buttons unique to our docs by reassigning Bootstrap’s CSS variables with a mixture of our own CSS and Sass variables. - -Custom button -`} /> - - - -### Sass variables - - - -### Sass mixins - -There are three mixins for buttons: button and button outline variant mixins (both based on `$theme-colors`), plus a button size mixin. - - - - - - - -### Sass loops - -Button variants (for regular and outline buttons) use their respective mixins with our `$theme-colors` map to generate the modifier classes in `scss/_buttons.scss`. - - diff --git a/site/src/content/docs/components/card.mdx b/site/src/content/docs/components/card.mdx deleted file mode 100644 index 1cf2522..0000000 --- a/site/src/content/docs/components/card.mdx +++ /dev/null @@ -1,673 +0,0 @@ ---- -title: Cards -description: Bootstrap’s cards provide a flexible and extensible content container with multiple variants and options. -toc: true ---- - -import { getData } from '@libs/data' - -## About - -A **card** is a flexible and extensible content container. It includes options for headers and footers, a wide variety of content, contextual background colors, and powerful display options. If you’re familiar with Bootstrap 3, cards replace our old panels, wells, and thumbnails. Similar functionality to those components is available as modifier classes for cards. - -## Example - -Cards are built with as little markup and styles as possible, but still manage to deliver a ton of control and customization. Built with flexbox, they offer easy alignment and mix well with other Bootstrap components. They have no `margin` by default, so use [spacing utilities]([[docsref:/utilities/spacing]]) as needed. - -Below is an example of a basic card with mixed content and a fixed width. Cards have no fixed width to start, so they’ll naturally fill the full width of its parent element. This is easily customized with our various [sizing options](#sizing). - - - -
    -
    Card title
    -

    Some quick example text to build on the card title and make up the bulk of the card’s content.

    - Go somewhere -
    - `} /> - -## Content types - -Cards support a wide variety of content, including images, text, list groups, links, and more. Below are examples of what’s supported. - -### Body - -The building block of a card is the `.card-body`. Use it whenever you need a padded section within a card. - - -
    - This is some text within a card body. -
    - `} /> - -### Titles, text, and links - -Card titles are used by adding `.card-title` to a `` tag. In the same way, links are added and placed next to each other by adding `.card-link` to an `` tag. - -Subtitles are used by adding a `.card-subtitle` to a `` tag. If the `.card-title` and the `.card-subtitle` items are placed in a `.card-body` item, the card title and subtitle are aligned nicely. - - - - `} /> - -### Images - -`.card-img-top` and `.card-img-bottom` respectively set the top and bottom corners rounded to match the card’s borders. With `.card-text`, text can be added to the card. Text within `.card-text` can also be styled with the standard HTML tags. - - - -
    -

    Some quick example text to build on the card title and make up the bulk of the card’s content.

    -
    - `} /> - -### List groups - -Create lists of content in a card with a flush list group. - - -
      -
    • An item
    • -
    • A second item
    • -
    • A third item
    • -
    - `} /> - - -
    - Featured -
    -
      -
    • An item
    • -
    • A second item
    • -
    • A third item
    • -
    - `} /> - - -
      -
    • An item
    • -
    • A second item
    • -
    • A third item
    • -
    - - `} /> - -### Kitchen sink - -Mix and match multiple content types to create the card you need, or throw everything in there. Shown below are image styles, blocks, text styles, and a list group—all wrapped in a fixed-width card. - - - -
    -
    Card title
    -

    Some quick example text to build on the card title and make up the bulk of the card’s content.

    -
    -
      -
    • An item
    • -
    • A second item
    • -
    • A third item
    • -
    - - `} /> - -### Header and footer - -Add an optional header and/or footer within a card. - - -
    - Featured -
    -
    -
    Special title treatment
    -

    With supporting text below as a natural lead-in to additional content.

    - Go somewhere -
    - `} /> - -Card headers can be styled by adding `.card-header` to `` elements. - - -
    Featured
    -
    -
    Special title treatment
    -

    With supporting text below as a natural lead-in to additional content.

    - Go somewhere -
    - `} /> - - -
    - Quote -
    -
    -
    -
    -

    A well-known quote, contained in a blockquote element.

    -
    - -
    -
    - `} /> - - -
    - Featured -
    -
    -
    Special title treatment
    -

    With supporting text below as a natural lead-in to additional content.

    - Go somewhere -
    - - `} /> - -## Sizing - -Cards assume no specific `width` to start, so they’ll be 100% wide unless otherwise stated. You can change this as needed with custom CSS, grid classes, grid Sass mixins, or utilities. - -### Using grid markup - -Using the grid, wrap cards in columns and rows as needed. - - -
    -
    -
    -
    Special title treatment
    -

    With supporting text below as a natural lead-in to additional content.

    - Go somewhere -
    -
    -
    -
    -
    -
    -
    Special title treatment
    -

    With supporting text below as a natural lead-in to additional content.

    - Go somewhere -
    -
    -
    - `} /> - -### Using utilities - -Use our handful of [available sizing utilities]([[docsref:/utilities/sizing]]) to quickly set a card’s width. - - -
    -
    Card title
    -

    With supporting text below as a natural lead-in to additional content.

    - Button -
    - - -
    -
    -
    Card title
    -

    With supporting text below as a natural lead-in to additional content.

    - Button -
    -
    `} /> - -### Using custom CSS - -Use custom CSS in your stylesheets or as inline styles to set a width. - - -
    -
    Special title treatment
    -

    With supporting text below as a natural lead-in to additional content.

    - Go somewhere -
    - `} /> - -## Text alignment - -You can quickly change the text alignment of any card—in its entirety or specific parts—with our [text align classes]([[docsref:/utilities/text#text-alignment]]). - - -
    -
    Special title treatment
    -

    With supporting text below as a natural lead-in to additional content.

    - Go somewhere -
    - - -
    -
    -
    Special title treatment
    -

    With supporting text below as a natural lead-in to additional content.

    - Go somewhere -
    -
    - -
    -
    -
    Special title treatment
    -

    With supporting text below as a natural lead-in to additional content.

    - Go somewhere -
    -
    `} /> - -## Navigation - -Add some navigation to a card’s header (or block) with Bootstrap’s [nav components]([[docsref:/components/navs-tabs]]). - - -
    - -
    -
    -
    Special title treatment
    -

    With supporting text below as a natural lead-in to additional content.

    - Go somewhere -
    - `} /> - - -
    - -
    -
    -
    Special title treatment
    -

    With supporting text below as a natural lead-in to additional content.

    - Go somewhere -
    - `} /> - -## Images - -Cards include a few options for working with images. Choose from appending “image caps” at either end of a card, overlaying images with card content, or simply embedding the image in a card. - -### Image caps - -Similar to headers and footers, cards can include top and bottom “image caps”—images at the top or bottom of a card. - - - -
    -
    Card title
    -

    This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.

    -

    Last updated 3 mins ago

    -
    - -
    -
    -
    Card title
    -

    This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.

    -

    Last updated 3 mins ago

    -
    - -
    `} /> - -### Image overlays - -Turn an image into a card background and overlay your card’s text. Depending on the image, you may or may not need additional styles or utilities. - - - -
    -
    Card title
    -

    This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.

    -

    Last updated 3 mins ago

    -
    - `} /> - - -Note that content should not be larger than the height of the image. If content is larger than the image the content will be displayed outside the image. - - -## Horizontal - -Using a combination of grid and utility classes, cards can be made horizontal in a mobile-friendly and responsive way. In the example below, we remove the grid gutters with `.g-0` and use `.col-md-*` classes to make the card horizontal at the `md` breakpoint. Further adjustments may be needed depending on your card content. - - -
    -
    - -
    -
    -
    -
    Card title
    -

    This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.

    -

    Last updated 3 mins ago

    -
    -
    -
    - `} /> - -## Card styles - -Cards include various options for customizing their backgrounds, borders, and color. - -### Background and color - - - -Set a `background-color` with contrasting foreground `color` with [our `.text-bg-{color}` helpers]([[docsref:helpers/color-background]]). Previously it was required to manually pair your choice of [`.text-{color}`]([[docsref:/utilities/colors]]) and [`.bg-{color}`]([[docsref:/utilities/background]]) utilities for styling, which you still may use if you prefer. - - `
    -
    Header
    -
    -
    ${themeColor.title} card title
    -

    Some quick example text to build on the card title and make up the bulk of the card’s content.

    -
    -
    `)} /> - - - -### Border - -Use [border utilities]([[docsref:/utilities/borders]]) to change just the `border-color` of a card. Note that you can put `.text-{color}` classes on the parent `.card` or a subset of the card’s contents as shown below. - - `
    -
    Header
    -
    -
    ${themeColor.title} card title
    -

    Some quick example text to build on the card title and make up the bulk of the card’s content.

    -
    -
    `)} /> - -### Mixins utilities - -You can also change the borders on the card header and footer as needed, and even remove their `background-color` with `.bg-transparent`. - - -
    Header
    -
    -
    Success card title
    -

    Some quick example text to build on the card title and make up the bulk of the card’s content.

    -
    - - `} /> - -## Card layout - -In addition to styling the content within cards, Bootstrap includes a few options for laying out series of cards. For the time being, **these layout options are not yet responsive**. - -### Card groups - -Use card groups to render cards as a single, attached element with equal width and height columns. Card groups start off stacked and use `display: flex;` to become attached with uniform dimensions starting at the `sm` breakpoint. - - -
    - -
    -
    Card title
    -

    This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.

    -

    Last updated 3 mins ago

    -
    -
    -
    - -
    -
    Card title
    -

    This card has supporting text below as a natural lead-in to additional content.

    -

    Last updated 3 mins ago

    -
    -
    -
    - -
    -
    Card title
    -

    This is a wider card with supporting text below as a natural lead-in to additional content. This card has even longer content than the first to show that equal height action.

    -

    Last updated 3 mins ago

    -
    -
    - `} /> - -When using card groups with footers, their content will automatically line up. - - -
    - -
    -
    Card title
    -

    This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.

    -
    - -
    -
    - -
    -
    Card title
    -

    This card has supporting text below as a natural lead-in to additional content.

    -
    - -
    -
    - -
    -
    Card title
    -

    This is a wider card with supporting text below as a natural lead-in to additional content. This card has even longer content than the first to show that equal height action.

    -
    - -
    - `} /> - -### Grid cards - -Use the Bootstrap grid system and its [`.row-cols` classes]([[docsref:/layout/grid#row-columns]]) to control how many grid columns (wrapped around your cards) you show per row. For example, here’s `.row-cols-1` laying out the cards on one column, and `.row-cols-md-2` splitting four cards to equal width across multiple rows, from the medium breakpoint up. - - -
    -
    - -
    -
    Card title
    -

    This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.

    -
    -
    -
    -
    -
    - -
    -
    Card title
    -

    This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.

    -
    -
    -
    -
    -
    - -
    -
    Card title
    -

    This is a longer card with supporting text below as a natural lead-in to additional content.

    -
    -
    -
    -
    -
    - -
    -
    Card title
    -

    This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.

    -
    -
    -
    - `} /> - -Change it to `.row-cols-3` and you’ll see the fourth card wrap. - - -
    -
    - -
    -
    Card title
    -

    This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.

    -
    -
    -
    -
    -
    - -
    -
    Card title
    -

    This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.

    -
    -
    -
    -
    -
    - -
    -
    Card title
    -

    This is a longer card with supporting text below as a natural lead-in to additional content.

    -
    -
    -
    -
    -
    - -
    -
    Card title
    -

    This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.

    -
    -
    -
    - `} /> - -When you need equal height, add `.h-100` to the cards. If you want equal heights by default, you can set `$card-height: 100%` in Sass. - - -
    -
    - -
    -
    Card title
    -

    This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.

    -
    -
    -
    -
    -
    - -
    -
    Card title
    -

    This is a short card.

    -
    -
    -
    -
    -
    - -
    -
    Card title
    -

    This is a longer card with supporting text below as a natural lead-in to additional content.

    -
    -
    -
    -
    -
    - -
    -
    Card title
    -

    This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.

    -
    -
    -
    - `} /> - -Just like with card groups, card footers will automatically line up. - - -
    -
    - -
    -
    Card title
    -

    This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.

    -
    - -
    -
    -
    -
    - -
    -
    Card title
    -

    This card has supporting text below as a natural lead-in to additional content.

    -
    - -
    -
    -
    -
    - -
    -
    Card title
    -

    This is a wider card with supporting text below as a natural lead-in to additional content. This card has even longer content than the first to show that equal height action.

    -
    - -
    -
    - `} /> - -### Masonry - -In `v4` we used a CSS-only technique to mimic the behavior of [Masonry](https://masonry.desandro.com/)-like columns, but this technique came with lots of unpleasant [side effects](https://github.com/twbs/bootstrap/pull/28922). If you want to have this type of layout in `v5`, you can just make use of Masonry plugin. **Masonry is not included in Bootstrap**, but we’ve made a [demo example]([[docsref:/examples/masonry]]) to help you get started. - -## CSS - -### Variables - - - -As part of Bootstrap’s evolving CSS variables approach, cards now use local CSS variables on `.card` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too. - - - -### Sass variables - - diff --git a/site/src/content/docs/components/carousel.mdx b/site/src/content/docs/components/carousel.mdx deleted file mode 100644 index 397f7da..0000000 --- a/site/src/content/docs/components/carousel.mdx +++ /dev/null @@ -1,420 +0,0 @@ ---- -title: Carousel -description: A slideshow component for cycling through elements—images or slides of text—like a carousel. -toc: true ---- - -## How it works - -- The carousel is a slideshow for cycling through a series of content, built with CSS 3D transforms and a bit of JavaScript. It works with a series of images, text, or custom markup. It also includes support for previous/next controls and indicators. - -- For performance reasons, **carousels must be manually initialized** using the [carousel constructor method](#methods). Without initialization, some of the event listeners (specifically, the events needed touch/swipe support) will not be registered until a user has explicitly activated a control or indicator. - - The only exception are [autoplaying carousels](#autoplaying-carousels) with the `data-bs-ride="carousel"` attribute as these are initialized automatically on page load. If you’re using autoplaying carousels with the data attribute, **don’t explicitly initialize the same carousels with the constructor method.** - -- Nested carousels are not supported. You should also be aware that carousels in general can often cause usability and accessibility challenges. - - - -## Basic examples - -Here is a basic example of a carousel with three slides. Note the previous/next controls. We recommend using ` - - `} /> - -Carousels don’t automatically normalize slide dimensions. As such, you may need to use additional utilities or custom styles to appropriately size content. While carousels support previous/next controls and indicators, they’re not explicitly required. Add and customize as you see fit. - -**You must add the `.active` class to one of the slides**, otherwise the carousel will not be visible. Also be sure to set a unique `id` on the `.carousel` for optional controls, especially if you’re using multiple carousels on a single page. Control and indicator elements must have a `data-bs-target` attribute (or `href` for links) that matches the `id` of the `.carousel` element. - -### Indicators - -You can add indicators to the carousel, alongside the previous/next controls. The indicators let users jump directly to a particular slide. - - - - - - - `} /> - -### Captions - -You can add captions to your slides with the `.carousel-caption` element within any `.carousel-item`. They can be easily hidden on smaller viewports, as shown below, with optional [display utilities]([[docsref:/utilities/display]]). We hide them initially with `.d-none` and bring them back on medium-sized devices with `.d-md-block`. - - - - - - - `} /> - -### Crossfade - -Add `.carousel-fade` to your carousel to animate slides with a fade transition instead of a slide. Depending on your carousel content (e.g., text only slides), you may want to add `.bg-body` or some custom CSS to the `.carousel-item`s for proper crossfading. - - - - - - `} /> - -## Autoplaying carousels - -You can make your carousels autoplay on page load by setting the `ride` option to `carousel`. Autoplaying carousels automatically pause while hovered with the mouse. This behavior can be controlled with the `pause` option. In browsers that support the [Page Visibility API](https://www.w3.org/TR/page-visibility/), the carousel will stop cycling when the webpage is not visible to the user (such as when the browser tab is inactive, or when the browser window is minimized). - - -For accessibility reasons, we recommend avoiding the use of autoplaying carousels. If your page does include an autoplaying carousel, we recommend providing an additional button or control to explicitly pause/stop the carousel. - -See [WCAG 2.2 Success Criterion 2.2.2 Pause, Stop, Hide](https://www.w3.org/TR/WCAG/#pause-stop-hide). - - - - - - - `} /> - -When the `ride` option is set to `true`, rather than `carousel`, the carousel won’t automatically start to cycle on page load. Instead, it will only start after the first user interaction. - - - - - - `} /> - -### Individual `.carousel-item` interval - -Add `data-bs-interval=""` to a `.carousel-item` to change the amount of time to delay between automatically cycling to the next item. - - - - - - `} /> - -### Autoplaying carousels without controls - -Here’s a carousel with slides only. Note the presence of the `.d-block` and `.w-100` on carousel images to prevent browser default image alignment. - - - - `} /> - -## Disable touch swiping - -Carousels support swiping left/right on touchscreen devices to move between slides. This can be disabled by setting the `touch` option to `false`. - - - - - - `} /> - -## Dark variant - - - -Add `.carousel-dark` to the `.carousel` for darker controls, indicators, and captions. Controls are inverted compared to their default white fill with the `filter` CSS property. Captions and controls have additional Sass variables that customize the `color` and `background-color`. - - - - - - - - - `} /> - -## Custom transition - -The transition duration of `.carousel-item` can be changed with the `$carousel-transition-duration` Sass variable before compiling or custom styles if you’re using the compiled CSS. If multiple transitions are applied, make sure the transform transition is defined first (e.g. `transition: transform 2s ease, opacity .5s ease-out`). - -## CSS - -### Sass variables - -Variables for all carousels: - - - -Variables for the [dark carousel](#dark-variant): - - - -## Usage - -### Via data attributes - -Use data attributes to easily control the position of the carousel. `data-bs-slide` accepts the keywords `prev` or `next`, which alters the slide position relative to its current position. Alternatively, use `data-bs-slide-to` to pass a raw slide index to the carousel `data-bs-slide-to="2"`, which shifts the slide position to a particular index beginning with `0`. - -### Via JavaScript - -Call carousel manually with: - -```js -const carousel = new bootstrap.Carousel('#myCarousel') -``` - -### Options - - - - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `interval` | number | `5000` | The amount of time to delay between automatically cycling an item. | -| `keyboard` | boolean | `true` | Whether the carousel should react to keyboard events. | -| `pause` | string, boolean | `"hover"` | If set to `"hover"`, pauses the cycling of the carousel on `mouseenter` and resumes the cycling of the carousel on `mouseleave`. If set to `false`, hovering over the carousel won’t pause it. On touch-enabled devices, when set to `"hover"`, cycling will pause on `touchend` (once the user finished interacting with the carousel) for two intervals, before automatically resuming. This is in addition to the mouse behavior. | -| `ride` | string, boolean | `false` | If set to `true`, autoplays the carousel after the user manually cycles the first item. If set to `"carousel"`, autoplays the carousel on load. | -| `touch` | boolean | `true` | Whether the carousel should support left/right swipe interactions on touchscreen devices. | -| `wrap` | boolean | `true` | Whether the carousel should cycle continuously or have hard stops. | - - -### Methods - - - -You can create a carousel instance with the carousel constructor, and pass on any additional options. For example, to manually initialize an autoplaying carousel (assuming you’re not using the `data-bs-ride="carousel"` attribute in the markup itself) with a specific interval and with touch support disabled, you can use: - -```js -const myCarouselElement = document.querySelector('#myCarousel') - -const carousel = new bootstrap.Carousel(myCarouselElement, { - interval: 2000, - touch: false -}) -``` - - -| Method | Description | -| --- | --- | -| `cycle` | Starts cycling through the carousel items from left to right. | -| `dispose` | Destroys an element’s carousel. (Removes stored data on the DOM element) | -| `getInstance` | Static method which allows you to get the carousel instance associated to a DOM element. You can use it like this: `bootstrap.Carousel.getInstance(element)`. | -| `getOrCreateInstance` | Static method which returns a carousel instance associated to a DOM element, or creates a new one in case it wasn’t initialized. You can use it like this: `bootstrap.Carousel.getOrCreateInstance(element)`. | -| `next` | Cycles to the next item. **Returns to the caller before the next item has been shown** (e.g., before the `slid.bs.carousel` event occurs). | -| `nextWhenVisible` | Don’t cycle carousel to next when the page, the carousel, or the carousel’s parent aren’t visible. **Returns to the caller before the target item has been shown**. | -| `pause` | Stops the carousel from cycling through items. | -| `prev` | Cycles to the previous item. **Returns to the caller before the previous item has been shown** (e.g., before the `slid.bs.carousel` event occurs). | -| `to` | Cycles the carousel to a particular frame (0 based, similar to an array). **Returns to the caller before the target item has been shown** (e.g., before the `slid.bs.carousel` event occurs). | - - -### Events - -Bootstrap’s carousel class exposes two events for hooking into carousel functionality. Both events have the following additional properties: - -- `direction`: The direction in which the carousel is sliding (either `"left"` or `"right"`). -- `relatedTarget`: The DOM element that is being slid into place as the active item. -- `from`: The index of the current item -- `to`: The index of the next item - -All carousel events are fired at the carousel itself (i.e. at the ``} /> - -## CSS - -### Variables - - - -As part of Bootstrap’s evolving CSS variables approach, close button now uses local CSS variables on `.btn-close` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too. - - - -### Sass variables - - diff --git a/site/src/content/docs/components/collapse.mdx b/site/src/content/docs/components/collapse.mdx deleted file mode 100644 index 8124c49..0000000 --- a/site/src/content/docs/components/collapse.mdx +++ /dev/null @@ -1,184 +0,0 @@ ---- -title: Collapse -description: Toggle the visibility of content across your project with a few classes and our JavaScript plugins. -toc: true ---- - -## How it works - -The collapse JavaScript plugin is used to show and hide content. Buttons or anchors are used as triggers that are mapped to specific elements you toggle. Collapsing an element will animate the `height` from its current value to `0`. Given how CSS handles animations, you cannot use `padding` on a `.collapse` element. Instead, use the class as an independent wrapping element. - - - -## Example - -Click the buttons below to show and hide another element via class changes: - -- `.collapse` hides content -- `.collapsing` is applied during transitions -- `.collapse.show` shows content - -Generally, we recommend using a ` -

    -
    -
    - Some placeholder content for the collapse component. This panel is hidden by default but revealed when the user activates the relevant trigger. -
    -
    `} /> - -## Horizontal - -The collapse plugin supports horizontal collapsing. Add the `.collapse-horizontal` modifier class to transition the `width` instead of `height` and set a `width` on the immediate child element. Feel free to write your own custom Sass, use inline styles, or use our [width utilities]([[docsref:/utilities/sizing]]). - - -Please note that while the example below has a `min-height` set to avoid excessive repaints in our docs, this is not explicitly required. **Only the `width` on the child element is required.** - - - - -

    -
    -
    -
    - This is some placeholder content for a horizontal collapse. It’s hidden by default and shown when triggered. -
    -
    -
    `} /> - -## Multiple toggles and targets - -A ` - -

    -
    -
    -
    -
    - Some placeholder content for the first collapse component of this multi-collapse example. This panel is hidden by default but revealed when the user activates the relevant trigger. -
    -
    -
    -
    -
    -
    - Some placeholder content for the second collapse component of this multi-collapse example. This panel is hidden by default but revealed when the user activates the relevant trigger. -
    -
    -
    -
    `} /> - -## Accessibility - -Be sure to add `aria-expanded` to the control element. This attribute explicitly conveys the current state of the collapsible element tied to the control to screen readers and similar assistive technologies. If the collapsible element is closed by default, the attribute on the control element should have a value of `aria-expanded="false"`. If you’ve set the collapsible element to be open by default using the `show` class, set `aria-expanded="true"` on the control instead. The plugin will automatically toggle this attribute on the control based on whether or not the collapsible element has been opened or closed (via JavaScript, or because the user triggered another control element also tied to the same collapsible element). If the control element’s HTML element is not a button (e.g., an `` or `
    `), the attribute `role="button"` should be added to the element. - -If your control element is targeting a single collapsible element – i.e. the `data-bs-target` attribute is pointing to an `id` selector – you should add the `aria-controls` attribute to the control element, containing the `id` of the collapsible element. Modern screen readers and similar assistive technologies make use of this attribute to provide users with additional shortcuts to navigate directly to the collapsible element itself. - -Note that Bootstrap’s current implementation does not cover the various *optional* keyboard interactions described in the [ARIA Authoring Practices Guide accordion pattern](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/) - you will need to include these yourself with custom JavaScript. - -## CSS - -### Sass variables - - - -### Classes - -Collapse transition classes can be found in `scss/_transitions.scss` as these are shared across multiple components (collapse and accordion). - - - -## Usage - -The collapse plugin utilizes a few classes to handle the heavy lifting: - -- `.collapse` hides the content -- `.collapse.show` shows the content -- `.collapsing` is added when the transition starts, and removed when it finishes - -These classes can be found in `_transitions.scss`. - -### Via data attributes - -Just add `data-bs-toggle="collapse"` and a `data-bs-target` to the element to automatically assign control of one or more collapsible elements. The `data-bs-target` attribute accepts a CSS selector to apply the collapse to. Be sure to add the class `collapse` to the collapsible element. If you’d like it to default open, add the additional class `show`. - -To add accordion-like group management to a collapsible area, add the data attribute `data-bs-parent="#selector"`. Refer to the [accordion page]([[docsref:/components/accordion]]) for more information. - -### Via JavaScript - -Enable manually with: - -```js -const collapseElementList = document.querySelectorAll('.collapse') -const collapseList = [...collapseElementList].map(collapseEl => new bootstrap.Collapse(collapseEl)) -``` - -### Options - - - - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -`parent` | selector, DOM element | `null` | If parent is provided, then all collapsible elements under the specified parent will be closed when this collapsible item is shown. (similar to traditional accordion behavior - this is dependent on the `card` class). The attribute has to be set on the target collapsible area. | -`toggle` | boolean | `true` | Toggles the collapsible element on invocation. | - - -### Methods - - - -Activates your content as a collapsible element. Accepts an optional options `object`. - -You can create a collapse instance with the constructor, for example: - -```js -const bsCollapse = new bootstrap.Collapse('#myCollapse', { - toggle: false -}) -``` - - -| Method | Description | -| --- | --- | -| `dispose` | Destroys an element’s collapse. (Removes stored data on the DOM element) | -| `getInstance` | Static method which allows you to get the collapse instance associated to a DOM element, you can use it like this: `bootstrap.Collapse.getInstance(element)`. | -| `getOrCreateInstance` | Static method which returns a collapse instance associated to a DOM element or create a new one in case it wasn’t initialized. You can use it like this: `bootstrap.Collapse.getOrCreateInstance(element)`. | -| `hide` | Hides a collapsible element. **Returns to the caller before the collapsible element has actually been hidden** (e.g., before the `hidden.bs.collapse` event occurs). | -| `show` | Shows a collapsible element. **Returns to the caller before the collapsible element has actually been shown** (e.g., before the `shown.bs.collapse` event occurs). | -| `toggle` | Toggles a collapsible element to shown or hidden. **Returns to the caller before the collapsible element has actually been shown or hidden** (i.e. before the `shown.bs.collapse` or `hidden.bs.collapse` event occurs). | - - -### Events - -Bootstrap’s collapse class exposes a few events for hooking into collapse functionality. - - -| Event type | Description | -| --- | --- | -| `hide.bs.collapse` | This event is fired immediately when the `hide` method has been called. | -| `hidden.bs.collapse` | This event is fired when a collapse element has been hidden from the user (will wait for CSS transitions to complete). | -| `show.bs.collapse` | This event fires immediately when the `show` instance method is called. | -| `shown.bs.collapse` | This event is fired when a collapse element has been made visible to the user (will wait for CSS transitions to complete). | - - -```js -const myCollapsible = document.getElementById('myCollapsible') -myCollapsible.addEventListener('hidden.bs.collapse', event => { - // do something... -}) -``` diff --git a/site/src/content/docs/components/navbar.mdx b/site/src/content/docs/components/navbar.mdx deleted file mode 100644 index 2387e83..0000000 --- a/site/src/content/docs/components/navbar.mdx +++ /dev/null @@ -1,761 +0,0 @@ ---- -title: Navbar -description: Documentation and examples for Bootstrap’s powerful, responsive navigation header, the navbar. Includes support for branding, navigation, and more, including support for our collapse plugin. -toc: true ---- - -import { getConfig } from '@libs/config' - -## How it works - -Here’s what you need to know before getting started with the navbar: - -- Navbars require a wrapping `.navbar` with `.navbar-expand{-sm|-md|-lg|-xl|-xxl}` for responsive collapsing and [color scheme](#color-schemes) classes. -- Navbars and their contents are fluid by default. Change the [container](#containers) to limit their horizontal width in different ways. -- Use our [spacing]([[docsref:/utilities/spacing]]) and [flex]([[docsref:/utilities/flex]]) utility classes for controlling spacing and alignment within navbars. -- Navbars are responsive by default, but you can easily modify them to change that. Responsive behavior depends on our Collapse JavaScript plugin. -- Ensure accessibility by using a ``} /> - -This example uses [background]([[docsref:/utilities/background]]) (`bg-body-tertiary`) and [spacing]([[docsref:/utilities/spacing]]) (`me-auto`, `mb-2`, `mb-lg-0`, `me-2`) utility classes. - -### Brand - -The `.navbar-brand` can be applied to most elements, but an anchor works best, as some elements might require utility classes or custom styles. - -#### Text - -Add your text within an element with the `.navbar-brand` class. - - - - - - `} /> - -#### Image - -You can replace the text within the `.navbar-brand` with an ``. - - -
    - - Bootstrap - -
    - `} /> - -#### Image and text - -You can also make use of some additional utilities to add an image and text at the same time. Note the addition of `.d-inline-block` and `.align-text-top` on the ``. - - - - `} /> - -### Nav - -Navbar navigation links build on our `.nav` options with their own modifier class and require the use of [toggler classes](#toggler) for proper responsive styling. **Navigation in navbars will also grow to occupy as much horizontal space as possible** to keep your navbar contents securely aligned. - -Add the `.active` class on `.nav-link` to indicate the current page. - -Please note that you should also add the `aria-current` attribute on the active `.nav-link`. - - -
    - Navbar - - -
    - `} /> - -And because we use classes for our navs, you can avoid the list-based approach entirely if you like. - - -
    - Navbar - - -
    - `} /> - -You can also use dropdowns in your navbar. Dropdown menus require a wrapping element for positioning, so be sure to use separate and nested elements for `.nav-item` and `.nav-link` as shown below. - - -
    - Navbar - - -
    - `} /> - -### Forms - -Place various form controls and components within a navbar: - - -
    - -
    - `} /> - -Immediate child elements of `.navbar` use flex layout and will default to `justify-content: space-between`. Use additional [flex utilities]([[docsref:/utilities/flex]]) as needed to adjust this behavior. - - -
    - Navbar - -
    - `} /> - -Input groups work, too. If your navbar is an entire form, or mostly a form, you can use the `
    ` element as the container and save some HTML. - - - -
    - @ - -
    - - `} /> - -Various buttons are supported as part of these navbar forms, too. This is also a great reminder that vertical alignment utilities can be used to align different sized elements. - - -
    - - -
    - `} /> - -### Text - -Navbars may contain bits of text with the help of `.navbar-text`. This class adjusts vertical alignment and horizontal spacing for strings of text. - - -
    - - Navbar text with an inline element - -
    - `} /> - -Mix and match with other components and utilities as needed. - - -
    - Navbar w/ text - - -
    - `} /> - -## Color schemes - - -**New dark navbars in v5.3.0 —** We’ve deprecated `.navbar-dark` in favor of the new `data-bs-theme="dark"`. Add `data-bs-theme="dark"` to the `.navbar` to enable a component-specific color mode. [Learn more about our color modes.]([[docsref:/customize/color-modes]]) - ---- - -**New in v5.2.0 —** Navbar theming is now powered by CSS variables and `.navbar-light` has been deprecated. CSS variables are applied to `.navbar`, defaulting to the “light” appearance, and can be overridden with `.navbar-dark`. - - -Navbar themes are easier than ever thanks to Bootstrap’s combination of Sass and CSS variables. The default is our “light navbar” for use with light background colors, but you can also apply `data-bs-theme="dark"` to the `.navbar` parent for dark background colors. Then, customize with `.bg-*` and additional utilities. - - -
    - Navbar - - -
    - - - - - -`} /> - -```html - - - - - -``` - -## Containers - -Although it’s not required, you can wrap a navbar in a `.container` to center it on a page–though note that an inner container is still required. Or you can add a container inside the `.navbar` to only center the contents of a [fixed or static top navbar](#placement). - - - -
    `} /> - -Use any of the responsive containers to change how wide the content in your navbar is presented. - - -
    - Navbar -
    - `} /> - -## Placement - -Use our [position utilities]([[docsref:/utilities/position]]) to place navbars in non-static positions. Choose from fixed to the top, fixed to the bottom, stickied to the top (scrolls with the page until it reaches the top, then stays there), or stickied to the bottom (scrolls with the page until it reaches the bottom, then stays there). - -Fixed navbars use `position: fixed`, meaning they’re pulled from the normal flow of the DOM and may require custom CSS (e.g., `padding-top` on the ``) to prevent overlap with other elements. - - -
    - Default -
    - `} /> - - -
    - Fixed top -
    - `} /> - - - - `} /> - - - - `} /> - - - - `} /> - -## Scrolling - -Add `.navbar-nav-scroll` to a `.navbar-nav` (or other navbar sub-component) to enable vertical scrolling within the toggleable contents of a collapsed navbar. By default, scrolling kicks in at `75vh` (or 75% of the viewport height), but you can override that with the local CSS custom property `--bs-navbar-height` or custom styles. At larger viewports when the navbar is expanded, content will appear as it does in a default navbar. - -Please note that this behavior comes with a potential drawback of `overflow`—when setting `overflow-y: auto` (required to scroll the content here), `overflow-x` is the equivalent of `auto`, which will crop some horizontal content. - -Here’s an example navbar using `.navbar-nav-scroll` with `style="--bs-scroll-height: 100px;"`, with some extra margin utilities for optimum spacing. - - -
    - Navbar scroll - - -
    - `} /> - -## Responsive behaviors - -Navbars can use `.navbar-toggler`, `.navbar-collapse`, and `.navbar-expand{-sm|-md|-lg|-xl|-xxl}` classes to determine when their content collapses behind a button. In combination with other utilities, you can easily choose when to show or hide particular elements. - -For navbars that never collapse, add the `.navbar-expand` class on the navbar. For navbars that always collapse, don’t add any `.navbar-expand` class. - -### Toggler - -Navbar togglers are left-aligned by default, but should they follow a sibling element like a `.navbar-brand`, they’ll automatically be aligned to the far right. Reversing your markup will reverse the placement of the toggler. Below are examples of different toggle styles. - -With no `.navbar-brand` shown at the smallest breakpoint: - - -
    - - -
    - `} /> - -With a brand name shown on the left and toggler on the right: - - -
    - Navbar - - -
    - `} /> - -With a toggler on the left and brand name on the right: - - -
    - - Navbar - -
    - `} /> - -### External content - -Sometimes you want to use the collapse plugin to trigger a container element for content that structurally sits outside of the `.navbar` . Because our plugin works on the `id` and `data-bs-target` matching, that’s easily done! - - -
    -
    Collapsed content
    - Toggleable via the navbar brand. -
    - - `} /> - -When you do this, we recommend including additional JavaScript to move the focus programmatically to the container when it is opened. Otherwise, keyboard users and users of assistive technologies will likely have a hard time finding the newly revealed content - particularly if the container that was opened comes *before* the toggler in the document’s structure. We also recommend making sure that the toggler has the `aria-controls` attribute, pointing to the `id` of the content container. In theory, this allows assistive technology users to jump directly from the toggler to the container it controls–but support for this is currently quite patchy. - -### Offcanvas - -Transform your expanding and collapsing navbar into an offcanvas drawer with the [offcanvas component]([[docsref:/components/offcanvas]]). We extend both the offcanvas default styles and use our `.navbar-expand-*` classes to create a dynamic and flexible navigation sidebar. - -In the example below, to create an offcanvas navbar that is always collapsed across all breakpoints, omit the `.navbar-expand-*` class entirely. - - -
    - Offcanvas navbar - -
    -
    -
    Offcanvas
    - -
    -
    - - -
    -
    -
    - `} /> - -To create an offcanvas navbar that expands into a normal navbar at a specific breakpoint like `lg`, use `.navbar-expand-lg`. - -```html - -``` - -When using offcanvas in a dark navbar, be aware that you may need to have a dark background on the offcanvas content to avoid the text becoming illegible. In the example below, we add `.navbar-dark` and `.bg-dark` to the `.navbar`, `.text-bg-dark` to the `.offcanvas`, `.dropdown-menu-dark` to `.dropdown-menu`, and `.btn-close-white` to `.btn-close` for proper styling with a dark offcanvas. - - -
    - Offcanvas dark navbar - -
    -
    -
    Dark offcanvas
    - -
    -
    - - -
    -
    -
    - `} /> - -## CSS - -### Variables - - - -As part of Bootstrap’s evolving CSS variables approach, navbars now use local CSS variables on `.navbar` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too. - - - -Some additional CSS variables are also present on `.navbar-nav`: - - - -Customization through CSS variables can be seen on the `.navbar-dark` class where we override specific values without adding duplicate CSS selectors. - - - -### Sass variables - -Variables for all navbars: - - - -Variables for the [dark navbar](#color-schemes): - - - -### Sass loops - -[Responsive navbar expand/collapse classes](#responsive-behaviors) (e.g., `.navbar-expand-lg`) are combined with the `$breakpoints` map and generated through a loop in `scss/_navbar.scss`. - - diff --git a/site/src/content/docs/components/offcanvas.mdx b/site/src/content/docs/components/offcanvas.mdx deleted file mode 100644 index eee3543..0000000 --- a/site/src/content/docs/components/offcanvas.mdx +++ /dev/null @@ -1,330 +0,0 @@ ---- -title: Offcanvas -description: Build hidden sidebars into your project for navigation, shopping carts, and more with a few classes and our JavaScript plugin. -toc: true ---- - -## How it works - -Offcanvas is a sidebar component that can be toggled via JavaScript to appear from the left, right, top, or bottom edge of the viewport. Buttons or anchors are used as triggers that are attached to specific elements you toggle, and `data` attributes are used to invoke our JavaScript. - -- Offcanvas shares some of the same JavaScript code as modals. Conceptually, they are quite similar, but they are separate plugins. -- Similarly, some [source Sass](#sass-variables) variables for offcanvas’s styles and dimensions are inherited from the modal’s variables. -- When shown, offcanvas includes a default backdrop that can be clicked to hide the offcanvas. -- Similar to modals, only one offcanvas can be shown at a time. - -**Heads up!** Given how CSS handles animations, you cannot use `margin` or `translate` on an `.offcanvas` element. Instead, use the class as an independent wrapping element. - - - -## Examples - -### Offcanvas components - -Below is an offcanvas example that is shown by default (via `.show` on `.offcanvas`). Offcanvas includes support for a header with a close button and an optional body class for some initial `padding`. We suggest that you include offcanvas headers with dismiss actions whenever possible, or provide an explicit dismiss action. - - -
    -
    Offcanvas
    - -
    -
    - Content for the offcanvas goes here. You can place just about any Bootstrap component or custom elements here. -
    - `} /> - -### Live demo - -Use the buttons below to show and hide an offcanvas element via JavaScript that toggles the `.show` class on an element with the `.offcanvas` class. - -- `.offcanvas` hides content (default) -- `.offcanvas.show` shows content - -You can use a link with the `href` attribute, or a button with the `data-bs-target` attribute. In both cases, the `data-bs-toggle="offcanvas"` is required. - - - Link with href - - - -
    -
    -
    Offcanvas
    - -
    -
    -
    - Some text as placeholder. In real life you can have the elements you have chosen. Like, text, images, lists, etc. -
    - -
    -
    `} /> - -### Body scrolling - -Scrolling the `` element is disabled when an offcanvas and its backdrop are visible. Use the `data-bs-scroll` attribute to enable `` scrolling. - -Enable body scrolling - -
    -
    -
    Offcanvas with body scrolling
    - -
    -
    -

    Try scrolling the rest of the page to see this option in action.

    -
    -
    `} /> - -### Body scrolling and backdrop - -You can also enable `` scrolling with a visible backdrop. - -Enable both scrolling & backdrop - -
    -
    -
    Backdrop with scrolling
    - -
    -
    -

    Try scrolling the rest of the page to see this option in action.

    -
    -
    `} /> - -### Static backdrop - -When backdrop is set to static, the offcanvas will not close when clicking outside of it. - - - Toggle static offcanvas - - -
    -
    -
    Offcanvas
    - -
    -
    -
    - I will not close if you click outside of me. -
    -
    -
    `} /> - -## Dark offcanvas - - - -Change the appearance of offcanvases with utilities to better match them to different contexts like dark navbars. Here we add `.text-bg-dark` to the `.offcanvas` and `.btn-close-white` to `.btn-close` for proper styling with a dark offcanvas. If you have dropdowns within, consider also adding `.dropdown-menu-dark` to `.dropdown-menu`. - - -**Heads up!** Dark variants for components were deprecated in v5.3.0 with the introduction of color modes. Instead of manually adding classes mentioned above, set `data-bs-theme="dark"` on the root element, a parent wrapper, or the component itself. - - - -
    -
    Offcanvas
    - -
    -
    -

    Place offcanvas content here.

    -
    - `} /> - -## Responsive - - - -Responsive offcanvas classes hide content outside the viewport from a specified breakpoint and down. Above that breakpoint, the contents within will behave as usual. For example, `.offcanvas-lg` hides content in an offcanvas below the `lg` breakpoint, but shows the content above the `lg` breakpoint. Responsive offcanvas classes are available for each breakpoint. - -- `.offcanvas` -- `.offcanvas-sm` -- `.offcanvas-md` -- `.offcanvas-lg` -- `.offcanvas-xl` -- `.offcanvas-xxl` - -To make a responsive offcanvas, replace the `.offcanvas` base class with a responsive variant and ensure your close button has an explicit `data-bs-target`. - -Toggle offcanvas - -
    Resize your browser to show the responsive offcanvas toggle.
    - -
    -
    -
    Responsive offcanvas
    - -
    -
    -

    This is content within an .offcanvas-lg.

    -
    -
    `} /> - -## Placement - -There’s no default placement for offcanvas components, so you must add one of the modifier classes below. - -- `.offcanvas-start` places offcanvas on the left of the viewport (shown above) -- `.offcanvas-end` places offcanvas on the right of the viewport -- `.offcanvas-top` places offcanvas on the top of the viewport -- `.offcanvas-bottom` places offcanvas on the bottom of the viewport - -Try the top, right, and bottom examples out below. - -Toggle top offcanvas - -
    -
    -
    Offcanvas top
    - -
    -
    - ... -
    -
    `} /> - -Toggle right offcanvas - -
    -
    -
    Offcanvas right
    - -
    -
    - ... -
    -
    `} /> - -Toggle bottom offcanvas - -
    -
    -
    Offcanvas bottom
    - -
    -
    - ... -
    -
    `} /> - -## Accessibility - -Since the offcanvas panel is conceptually a modal dialog, be sure to add `aria-labelledby="..."`—referencing the offcanvas title—to `.offcanvas`. Note that you don’t need to add `role="dialog"` since we already add it via JavaScript. - -## CSS - -### Variables - - - -As part of Bootstrap’s evolving CSS variables approach, offcanvas now uses local CSS variables on `.offcanvas` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too. - - - -### Sass variables - - - -## Usage - -The offcanvas plugin utilizes a few classes and attributes to handle the heavy lifting: - -- `.offcanvas` hides the content -- `.offcanvas.show` shows the content -- `.offcanvas-start` hides the offcanvas on the left -- `.offcanvas-end` hides the offcanvas on the right -- `.offcanvas-top` hides the offcanvas on the top -- `.offcanvas-bottom` hides the offcanvas on the bottom - -Add a dismiss button with the `data-bs-dismiss="offcanvas"` attribute, which triggers the JavaScript functionality. Be sure to use the ``} /> - -### Four directions - -Four options are available: top, right, bottom, and left. Directions are mirrored when using Bootstrap in RTL. Set `data-bs-placement` to change the direction. - - - Popover on top - - - - `} /> - -### Custom `container` - -When you have some styles on a parent element that interfere with a popover, you’ll want to specify a custom `container` so that the popover’s HTML appears within that element instead. This is common in responsive tables, input groups, and the like. - -```js -const popover = new bootstrap.Popover('.example-popover', { - container: 'body' -}) -``` - -Another situation where you’ll want to set an explicit custom `container` are popovers inside a [modal dialog]([[docsref:/components/modal]]), to make sure that the popover itself is appended to the modal. This is particularly important for popovers that contain interactive elements – modal dialogs will trap focus, so unless the popover is a child element of the modal, users won’t be able to focus or activate these interactive elements. - -```js -const popover = new bootstrap.Popover('.example-popover', { - container: '.modal-body' -}) -``` - -### Custom popovers - - - -You can customize the appearance of popovers using [CSS variables](#variables). We set a custom class with `data-bs-custom-class="custom-popover"` to scope our custom appearance and use it to override some of the local CSS variables. - - - - - Custom popover - `} /> - -### Dismiss on next click - -Use the `focus` trigger to dismiss popovers on the user’s next click of an element other than the toggle element. - - -**Dismissing on next click requires specific HTML for proper cross-browser and cross-platform behavior.** You can only use `` elements, not ` - `} /> - -## CSS - -### Variables - - - -As part of Bootstrap’s evolving CSS variables approach, popovers now use local CSS variables on `.popover` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too. - - - -### Sass variables - - - -## Usage - -Enable popovers via JavaScript: - -```js -const exampleEl = document.getElementById('example') -const popover = new bootstrap.Popover(exampleEl, options) -``` - - -**Keep popovers accessible to keyboard and assistive technology users** by only adding them to HTML elements that are traditionally keyboard-focusable and interactive (such as links or form controls). While other HTML elements can be made focusable by adding `tabindex="0"`, this can create annoying and confusing tab stops on non-interactive elements for keyboard users, and most assistive technologies currently do not announce popovers in this situation. Additionally, do not rely solely on `hover` as the trigger for your popovers as this will make them impossible to trigger for keyboard users. - -Avoid adding an excessive amount of content in popovers with the `html` option. Once popovers are displayed, their content is tied to the trigger element with the `aria-describedby` attribute, causing all of the popover’s content to be announced to assistive technology users as one long, uninterrupted stream. - -Popovers do not manage keyboard focus order, and their placement can be random in the DOM, so be careful when adding interactive elements (like forms or links), as it may lead to an illogical focus order or make the popover content itself completely unreachable for keyboard users. In cases where you must use these elements, consider using a modal dialog instead. - - -### Options - - - - -Note that for security reasons the `sanitize`, `sanitizeFn`, and `allowList` options cannot be supplied using data attributes. - - - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `allowList` | object | [Default value]([[docsref:/getting-started/javascript#sanitizer]]) | An object containing allowed tags and attributes. Those not explicitly allowed will be removed by [the content sanitizer]([[docsref:/getting-started/javascript#sanitizer]]). **Exercise caution when adding to this list.** Refer to [OWASP’s Cross Site Scripting Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) for more information. | -| `animation` | boolean | `true` | Apply a CSS fade transition to the popover. | -| `boundary` | string, element | `'clippingParents'` | Overflow constraint boundary of the popover (applies only to Popper’s preventOverflow modifier). By default, it’s `'clippingParents'` and can accept an HTMLElement reference (via JavaScript only). For more information refer to Popper’s [detectOverflow docs](https://popper.js.org/docs/v2/utils/detect-overflow/#boundary). | -| `container` | string, element, false | `false` | Appends the popover to a specific element. Example: `container: 'body'`. This option is particularly useful in that it allows you to position the popover in the flow of the document near the triggering element - which will prevent the popover from floating away from the triggering element during a window resize. | -| `content` | string, element, function | `''` | The popover’s text content. If a function is given, it will be called with its `this` reference set to the element that the popover is attached to. | -| `customClass` | string, function | `''` | Add classes to the popover when it is shown. Note that these classes will be added in addition to any classes specified in the template. To add multiple classes, separate them with spaces: `'class-1 class-2'`. You can also pass a function that should return a single string containing additional class names. | -| `delay` | number, object | `0` | Delay showing and hiding the popover (ms)—doesn’t apply to manual trigger type. If a number is supplied, delay is applied to both hide/show. Object structure is: `delay: { "show": 500, "hide": 100 }`. | -| `fallbackPlacements` | string, array | `['top', 'right', 'bottom', 'left']` | Define fallback placements by providing a list of placements in array (in order of preference). For more information refer to Popper’s [behavior docs](https://popper.js.org/docs/v2/modifiers/flip/#fallbackplacements). | -| `html` | boolean | `false` | Allow HTML in the popover. If true, HTML tags in the popover’s `title` will be rendered in the popover. If false, `innerText` property will be used to insert content into the DOM. Prefer text when dealing with user-generated input to [prevent XSS attacks](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html). | -| `offset` | number, string, function | `[0, 8]` | Offset of the popover relative to its target. You can pass a string in data attributes with comma separated values like: `data-bs-offset="10,20"`. When a function is used to determine the offset, it is called with an object containing the popper placement, the reference, and popper rects as its first argument. The triggering element DOM node is passed as the second argument. The function must return an array with two numbers: [skidding](https://popper.js.org/docs/v2/modifiers/offset/#skidding-1), [distance](https://popper.js.org/docs/v2/modifiers/offset/#distance-1). For more information refer to Popper’s [offset docs](https://popper.js.org/docs/v2/modifiers/offset/#options). | -| `placement` | string, function | `'right'` | How to position the popover: auto, top, bottom, left, right. When `auto` is specified, it will dynamically reorient the popover. When a function is used to determine the placement, it is called with the popover DOM node as its first argument and the triggering element DOM node as its second. The `this` context is set to the popover instance. | -| `popperConfig` | null, object, function | `null` | To change Bootstrap’s default Popper config, see [Popper’s configuration](https://popper.js.org/docs/v2/constructors/#options). When a function is used to create the Popper configuration, it’s called with an object that contains the Bootstrap’s default Popper configuration. It helps you use and merge the default with your own configuration. The function must return a configuration object for Popper. | -| `sanitize` | boolean | `true` | Enable [content sanitization]([[docsref:/getting-started/javascript#sanitizer]]). If true, the `template`, `content` and `title` options will be sanitized. **Exercise caution when disabling content sanitization.** Refer to [OWASP’s Cross Site Scripting Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) for more information. Vulnerabilities caused solely by disabling content sanitization are not considered within scope for Bootstrap’s security model. | -| `sanitizeFn` | null, function | `null` | Provide an alternative [content sanitization]([[docsref:/getting-started/javascript#sanitizer]]) function. This can be useful if you prefer to use a dedicated library to perform sanitization. | -| `selector` | string, false | `false` | If a selector is provided, popover objects will be delegated to the specified targets. In practice, this is used to also apply popovers to dynamically added DOM elements (`jQuery.on` support). See [this issue]([[config:repo]]/issues/4215) and [an informative example](https://codepen.io/Johann-S/pen/djJYPb). **Note**: `title` attribute must not be used as a selector. | -| `template` | string | `''` | Base HTML to use when creating the popover. The popover’s `title` will be injected into the `.popover-header`. The popover’s `content` will be injected into the `.popover-body`. `.popover-arrow` will become the popover’s arrow. The outermost wrapper element should have the `.popover` class and `role="tooltip"`. | -| `title` | string, element, function | `''` | The popover title. If a function is given, it will be called with its `this` reference set to the element that the popover is attached to. | -| `trigger` | string | `'click'` | How popover is triggered: click, hover, focus, manual. You may pass multiple triggers; separate them with a space. `'manual'` indicates that the popover will be triggered programmatically via the `.popover('show')`, `.popover('hide')` and `.popover('toggle')` methods; this value cannot be combined with any other trigger. `'hover'` on its own will result in popovers that cannot be triggered via the keyboard, and should only be used if alternative methods for conveying the same information for keyboard users is present. | - - - -#### Data attributes for individual popovers - -Options for individual popovers can alternatively be specified through the use of data attributes, as explained above. - - -#### Using function with `popperConfig` - -```js -const popover = new bootstrap.Popover(element, { - popperConfig(defaultBsPopperConfig) { - // const newPopperConfig = {...} - // use defaultBsPopperConfig if needed... - // return newPopperConfig - } -}) -``` - -### Methods - - - - -| Method | Description | -| --- | --- | -| `disable` | Removes the ability for an element’s popover to be shown. The popover will only be able to be shown if it is re-enabled. | -| `dispose` | Hides and destroys an element’s popover (Removes stored data on the DOM element). Popovers that use delegation (which are created using [the `selector` option](#options)) cannot be individually destroyed on descendant trigger elements. | -| `enable` | Gives an element’s popover the ability to be shown. **Popovers are enabled by default.** | -| `getInstance` | _Static_ method which allows you to get the popover instance associated with a DOM element. | -| `getOrCreateInstance` | _Static_ method which allows you to get the popover instance associated with a DOM element, or create a new one in case it wasn’t initialized. | -| `hide` | Hides an element’s popover. **Returns to the caller before the popover has actually been hidden** (i.e. before the `hidden.bs.popover` event occurs). This is considered a “manual” triggering of the popover. | -| `setContent` | Gives a way to change the popover’s content after its initialization. | -| `show` | Reveals an element’s popover. **Returns to the caller before the popover has actually been shown** (i.e. before the `shown.bs.popover` event occurs). This is considered a “manual” triggering of the popover. Popovers whose title and content are both zero-length are never displayed. | -| `toggle` | Toggles an element’s popover. **Returns to the caller before the popover has actually been shown or hidden** (i.e. before the `shown.bs.popover` or `hidden.bs.popover` event occurs). This is considered a “manual” triggering of the popover. | -| `toggleEnabled` | Toggles the ability for an element’s popover to be shown or hidden. | -| `update` | Updates the position of an element’s popover. | - - - -```js -// getOrCreateInstance example -const popover = bootstrap.Popover.getOrCreateInstance('#example') // Returns a Bootstrap popover instance - -// setContent example -popover.setContent({ - '.popover-header': 'another title', - '.popover-body': 'another content' -}) -``` - - -The `setContent` method accepts an `object` argument, where each property-key is a valid `string` selector within the popover template, and each related property-value can be `string` | `element` | `function` | `null` - - -### Events - - -| Event | Description | -| --- | --- | -| `hide.bs.popover` | This event is fired immediately when the `hide` instance method has been called. | -| `hidden.bs.popover` | This event is fired when the popover has finished being hidden from the user (will wait for CSS transitions to complete). | -| `inserted.bs.popover` | This event is fired after the `show.bs.popover` event when the popover template has been added to the DOM. | -| `show.bs.popover` | This event fires immediately when the `show` instance method is called. | -| `shown.bs.popover` | This event is fired when the popover has been made visible to the user (will wait for CSS transitions to complete). | - - -```js -const myPopoverTrigger = document.getElementById('myPopover') -myPopoverTrigger.addEventListener('hidden.bs.popover', () => { - // do something... -}) -``` diff --git a/site/src/content/docs/components/progress.mdx b/site/src/content/docs/components/progress.mdx deleted file mode 100644 index 89fa322..0000000 --- a/site/src/content/docs/components/progress.mdx +++ /dev/null @@ -1,174 +0,0 @@ ---- -title: Progress -description: Documentation and examples for using Bootstrap custom progress bars featuring support for stacked bars, animated backgrounds, and text labels. -toc: true ---- - - -**New markup in v5.3.0 —** We’ve deprecated the previous HTML structure for progress bars and replaced it with a more accessible one. The previous structure will continue to work until v6. [See what’s changed in our migration guide.]([[docsref:/migration#improved-markup-for-progress-bars]]) - - -## How it works - -Progress components are built with two HTML elements, some CSS to set the width, and a few attributes. We don’t use [the HTML5 `` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress), ensuring you can stack progress bars, animate them, and place text labels over them. - -- We use the `.progress` as a wrapper to indicate the max value of the progress bar. -- The `.progress` wrapper also requires a `role="progressbar"` and `aria` attributes to make it accessible, including an accessible name (using `aria-label`, `aria-labelledby`, or similar). -- We use the inner `.progress-bar` purely for the visual bar and label. -- The `.progress-bar` requires an inline style, utility class, or custom CSS to set its width. -- We provide a special `.progress-stacked` class to create multiple/stacked progress bars. - -Put that all together, and you have the following examples. - - -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    `} /> - -## Bar sizing - -### Width - -Bootstrap provides a handful of [utilities for setting width]([[docsref:/utilities/sizing]]). Depending on your needs, these may help with quickly configuring the width of the `.progress-bar`. - - -
    - `} /> - -### Height - -You only set a `height` value on the `.progress` container, so if you change that value, the inner `.progress-bar` will automatically resize accordingly. - - -
    - -
    -
    -
    `} /> - -## Labels - -Add labels to your progress bars by placing text within the `.progress-bar`. - - -
    25%
    - `} /> - -### Long labels - -Note that by default, the content inside the `.progress-bar` is controlled with `overflow: hidden`, so it doesn’t bleed out of the bar. If your progress bar is shorter than its label, the content will be capped and may become unreadable. To change this behavior, you can use `.overflow-visible` from the [overflow utilities]([[docsref:/utilities/overflow]]). - - -Labels longer than the progress bar within may not be fully accessible using this method because it relies on the text color having the correct contrast ratio with both the `.progress` and `.progress-bar` background colors. Use caution when implementing this example. - -If the text can overlap the progress bar, we often recommend displaying the label outside of the progress bar for better accessibility. - - -## Backgrounds - -Use background utility classes to change the appearance of individual progress bars. - - -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    `} /> - - - -If you’re adding labels to progress bars with a custom background color, make sure to also set an appropriate [text color]([[docsref:/utilities/colors#colors]]), so the labels remain readable and have sufficient contrast. We recommend using the [color and background]([[docsref:/helpers/color-background]]) helper classes. - - -
    25%
    - -
    -
    50%
    -
    -
    -
    75%
    -
    -
    -
    100%
    -
    `} /> - -## Multiple bars - -You can include multiple progress components inside a container with `.progress-stacked` to create a single stacked progress bar. Note that in this case, the styling to set the visual width of the progress bar *must* be applied to the `.progress` elements, rather than the `.progress-bar`s. - - -
    -
    -
    -
    -
    -
    -
    -
    -
    - `} /> - -## Striped - -Add `.progress-bar-striped` to any `.progress-bar` to apply a stripe via CSS gradient over the progress bar’s background color. - - -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    `} /> - -## Animated stripes - -The striped gradient can also be animated. Add `.progress-bar-animated` to `.progress-bar` to animate the stripes right to left via CSS3 animations. - - -
    - `} /> - -## CSS - -### Variables - - - -As part of Bootstrap’s evolving CSS variables approach, progress bars now use local CSS variables on `.progress` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too. - - - -### Sass variables - - - -### Keyframes - -Used for creating the CSS animations for `.progress-bar-animated`. Included in `scss/_progress-bar.scss`. - - diff --git a/site/src/content/docs/components/spinners.mdx b/site/src/content/docs/components/spinners.mdx deleted file mode 100644 index 354c001..0000000 --- a/site/src/content/docs/components/spinners.mdx +++ /dev/null @@ -1,175 +0,0 @@ ---- -title: Spinners -description: Indicate the loading state of a component or page with Bootstrap spinners, built entirely with HTML, CSS, and no JavaScript. -toc: true ---- - -import { getData } from '@libs/data' - -## About - -Bootstrap “spinners” can be used to show the loading state in your projects. They’re built only with HTML and CSS, meaning you don’t need any JavaScript to create them. You will, however, need some custom JavaScript to toggle their visibility. Their appearance, alignment, and sizing can be easily customized with our amazing utility classes. - -For accessibility purposes, each loader here includes `role="status"` and a nested `Loading...`. - - - -## Border spinner - -Use the border spinners for a lightweight loading indicator. - - - Loading... - `} /> - -### Colors - -The border spinner uses `currentColor` for its `border-color`, meaning you can customize the color with [text color utilities][color]. You can use any of our text color utilities on the standard spinner. - - `
    - Loading... -
    `)} /> - - -**Why not use `border-color` utilities?** Each border spinner specifies a `transparent` border for at least one side, so `.border-{color}` utilities would override that. - - -## Growing spinner - -If you don’t fancy a border spinner, switch to the grow spinner. While it doesn’t technically spin, it does repeatedly grow! - - - Loading... - `} /> - -Once again, this spinner is built with `currentColor`, so you can easily change its appearance with [text color utilities][color]. Here it is in blue, along with the supported variants. - - `
    - Loading... -
    `)} /> - -## Alignment - -Spinners in Bootstrap are built with `rem`s, `currentColor`, and `display: inline-flex`. This means they can easily be resized, recolored, and quickly aligned. - -### Margin - -Use [margin utilities][margin] like `.m-5` for easy spacing. - - - Loading... - `} /> - -### Placement - -Use [flexbox utilities][flex], [float utilities][float], or [text alignment][text] utilities to place spinners exactly where you need them in any situation. - -#### Flex - - -
    - Loading... -
    - `} /> - - - Loading... - - `} /> - -#### Floats - - -
    - Loading... -
    - `} /> - -#### Text align - - -
    - Loading... -
    - `} /> - -## Size - -Add `.spinner-border-sm` and `.spinner-grow-sm` to make a smaller spinner that can quickly be used within other components. - - - Loading... - -
    - Loading... -
    `} /> - -Or, use custom CSS or inline styles to change the dimensions as needed. - - - Loading... - -
    - Loading... -
    `} /> - -## Buttons - -Use spinners within buttons to indicate an action is currently processing or taking place. You may also swap the text out of the spinner element and utilize button text as needed. - - - - Loading... - - `} /> - - - - Loading... - - `} /> - -## CSS - -### Variables - - - -As part of Bootstrap’s evolving CSS variables approach, spinners now use local CSS variables on `.spinner-border` and `.spinner-grow` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too. - -Border spinner variables: - - - -Growing spinner variables: - - - -For both spinners, small spinner modifier classes are used to update the values of these CSS variables as needed. For example, the `.spinner-border-sm` class does the following: - - - -### Sass variables - - - -### Keyframes - -Used for creating the CSS animations for our spinners. Included in `scss/_spinners.scss`. - - - - - - -[color]: [[docsref:/utilities/colors]] -[flex]: [[docsref:/utilities/flex]] -[float]: [[docsref:/utilities/float]] -[margin]: [[docsref:/utilities/spacing]] -[text]: [[docsref:/utilities/text]] diff --git a/site/src/content/docs/components/toasts.mdx b/site/src/content/docs/components/toasts.mdx deleted file mode 100644 index cb6afb9..0000000 --- a/site/src/content/docs/components/toasts.mdx +++ /dev/null @@ -1,363 +0,0 @@ ---- -title: Toasts -description: Push notifications to your visitors with a toast, a lightweight and easily customizable alert message. -toc: true ---- - -Toasts are lightweight notifications designed to mimic the push notifications that have been popularized by mobile and desktop operating systems. They’re built with flexbox, so they’re easy to align and position. - -## Overview - -Things to know when using the toast plugin: - -- Toasts are opt-in for performance reasons, so **you must initialize them yourself**. -- Toasts will automatically hide if you do not specify `autohide: false`. - - - -## Examples - -### Basic - -To encourage extensible and predictable toasts, we recommend a header and body. Toast headers use `display: flex`, allowing easy alignment of content thanks to our margin and flexbox utilities. - -Toasts are as flexible as you need and have very little required markup. At a minimum, we require a single element to contain your “toasted” content and strongly encourage a dismiss button. - - -
    - - Bootstrap - 11 mins ago - -
    -
    - Hello, world! This is a toast message. -
    - `} /> - - -Previously, our scripts dynamically added the `.hide` class to completely hide a toast (with `display:none`, rather than just with `opacity:0`). This is now not necessary anymore. However, for backwards compatibility, our script will continue to toggle the class (even though there is no practical need for it) until the next major version. - - -### Live example - -Click the button below to show a toast (positioned with our utilities in the lower right corner) that has been hidden by default. - -
    - -
    - -Show live toast -`} /> - -```html - - -
    - -
    -``` - -We use the following JavaScript to trigger our live toast demo: - - - -### Translucent - -Toasts are slightly translucent to blend in with what’s below them. - - -
    - - Bootstrap - 11 mins ago - -
    -
    - Hello, world! This is a toast message. -
    - `} /> - -### Stacking - -You can stack toasts by wrapping them in a toast container, which will vertically add some spacing. - - - - - - `} /> - -### Custom content - -Customize your toasts by removing sub-components, tweaking them with [utilities]([[docsref:/utilities/api]]), or by adding your own markup. Here we’ve created a simpler toast by removing the default `.toast-header`, adding a custom hide icon from [Bootstrap Icons]([[config:icons]]), and using some [flexbox utilities]([[docsref:/utilities/flex]]) to adjust the layout. - - -
    -
    - Hello, world! This is a toast message. -
    - -
    - `} /> - -Alternatively, you can also add additional controls and components to toasts. - - -
    - Hello, world! This is a toast message. -
    - - -
    -
    - `} /> - -### Color schemes - -Building on the above example, you can create different toast color schemes with our [color]([[docsref:/utilities/colors]]) and [background]([[docsref:/utilities/background]]) utilities. Here we’ve added `.text-bg-primary` 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`. - - -
    -
    - Hello, world! This is a toast message. -
    - -
    - `} /> - -## Placement - -Place toasts with custom CSS as you need them. The top right is often used for notifications, as is the top middle. If you’re only ever going to show one toast at a time, put the positioning styles right on the `.toast`. - - -
    - - -
    - -
    -
    -
    -
    - - Bootstrap - 11 mins ago -
    -
    - Hello, world! This is a toast message. -
    -
    -
    -
    `} /> - -For systems that generate more notifications, consider using a wrapping element so they can easily stack. - - - - - - -
    - - - - - -
    - `} /> - -You can also get fancy with flexbox utilities to align toasts horizontally and/or vertically. - - -
    - - - -
    `} /> - -## Accessibility - -Toasts are intended to be small interruptions to your visitors or users, so to help those with screen readers and similar assistive technologies, you should wrap your toasts in an [`aria-live` region](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions). Changes to live regions (such as injecting/updating a toast component) are automatically announced by screen readers without needing to move the user’s focus or otherwise interrupt the user. Additionally, include `aria-atomic="true"` to ensure that the entire toast is always announced as a single (atomic) unit, rather than just announcing what was changed (which could lead to problems if you only update part of the toast’s content, or if displaying the same toast content at a later point in time). If the information needed is important for the process, e.g. for a list of errors in a form, then use the [alert component]([[docsref:/components/alerts]]) instead of toast. - -Note that the live region needs to be present in the markup *before* the toast is generated or updated. If you dynamically generate both at the same time and inject them into the page, they will generally not be announced by assistive technologies. - -You also need to adapt the `role` and `aria-live` level depending on the content. If it’s an important message like an error, use `role="alert" aria-live="assertive"`, otherwise use `role="status" aria-live="polite"` attributes. - -As the content you’re displaying changes, be sure to update the [`delay` timeout](#options) so that users have enough time to read the toast. - -```html - -``` - -When using `autohide: false`, you must add a close button to allow users to dismiss the toast. - - -
    - - Bootstrap - 11 mins ago - -
    -
    - Hello, world! This is a toast message. -
    - `} /> - -While technically it’s possible to add focusable/actionable controls (such as additional buttons or links) in your toast, you should avoid doing this for autohiding toasts. Even if you give the toast a long [`delay` timeout](#options), keyboard and assistive technology users may find it difficult to reach the toast in time to take action (since toasts don’t receive focus when they are displayed). If you absolutely must have further controls, we recommend using a toast with `autohide: false`. - -## CSS - -### Variables - - - -As part of Bootstrap’s evolving CSS variables approach, toasts now use local CSS variables on `.toast` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too. - - - -### Sass variables - - - -## Usage - -Initialize toasts via JavaScript: - -```js -const toastElList = document.querySelectorAll('.toast') -const toastList = [...toastElList].map(toastEl => new bootstrap.Toast(toastEl, option)) -``` - -### Triggers - - - -### Options - - - - -| Name | Type | Default | Description | -| --- | --- | --- | --- | -| `animation` | boolean | `true` | Apply a CSS fade transition to the toast. | -| `autohide` | boolean | `true` | Automatically hide the toast after the delay. | -| `delay` | number | `5000` | Delay in milliseconds before hiding the toast. | - - -### Methods - - - - -| Method | Description | -| --- | --- | -| `dispose` | Hides an element’s toast. Your toast will remain on the DOM but won’t show anymore. | -| `getInstance` | *Static* method which allows you to get the toast instance associated with a DOM element.
    For example: `const myToastEl = document.getElementById('myToastEl')` `const myToast = bootstrap.Toast.getInstance(myToastEl)` Returns a Bootstrap toast instance. | -| `getOrCreateInstance` | *Static* method which allows you to get the toast instance associated with a DOM element, or create a new one, in case it wasn’t initialized.
    `const myToastEl = document.getElementById('myToastEl')` `const myToast = bootstrap.Toast.getOrCreateInstance(myToastEl)` Returns a Bootstrap toast instance. | -| `hide` | Hides an element’s toast. **Returns to the caller before the toast has actually been hidden** (i.e. before the `hidden.bs.toast` event occurs). You have to manually call this method if you made `autohide` to `false`. | -| `isShown` | Returns a boolean according to toast’s visibility state. | -| `show` | Reveals an element’s toast. **Returns to the caller before the toast has actually been shown** (i.e. before the `shown.bs.toast` event occurs). You have to manually call this method, instead your toast won’t show. | -
    - -### Events - - -| Event | Description | -| --- | --- | -| `hide.bs.toast` | This event is fired immediately when the `hide` instance method has been called. | -| `hidden.bs.toast` | This event is fired when the toast has finished being hidden from the user. | -| `show.bs.toast` | This event fires immediately when the `show` instance method is called. | -| `shown.bs.toast` | This event is fired when the toast has been made visible to the user. | - - -```js -const myToastEl = document.getElementById('myToast') -myToastEl.addEventListener('hidden.bs.toast', () => { - // do something... -}) -``` diff --git a/site/src/content/docs/content/figures.mdx b/site/src/content/docs/content/figures.mdx deleted file mode 100644 index 31cb30f..0000000 --- a/site/src/content/docs/content/figures.mdx +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Figures -description: Documentation and examples for displaying related images and text with the figure component in Bootstrap. -toc: true ---- - -Anytime you need to display a piece of content—like an image with an optional caption, consider using a `
    `. - -Use the included `.figure`, `.figure-img` and `.figure-caption` classes to provide some baseline styles for the HTML5 `
    ` and `
    ` elements. Images in figures have no explicit size, so be sure to add the `.img-fluid` class to your `` to make it responsive. - - - -
    A caption for the above image.
    -
    `} /> - -Aligning the figure’s caption is easy with our [text utilities]([[docsref:/utilities/text#text-alignment]]). - - - -
    A caption for the above image.
    -
    `} /> - -## CSS - -### Sass variables - - diff --git a/site/src/content/docs/content/images.mdx b/site/src/content/docs/content/images.mdx deleted file mode 100644 index 61cc42d..0000000 --- a/site/src/content/docs/content/images.mdx +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: Images -description: Documentation and examples for opting images into responsive behavior (so they never become wider than their parent) and add lightweight styles to them—all via classes. -toc: true ---- - -## Responsive images - -Images in Bootstrap are made responsive with `.img-fluid`. This applies `max-width: 100%;` and `height: auto;` to the image so that it scales with the parent width. - -`} /> - -## Image thumbnails - -In addition to our [border-radius utilities]([[docsref:/utilities/borders]]), you can use `.img-thumbnail` to give an image a rounded 1px border appearance. - -`} /> - -## Aligning images - -Align images with the [helper float classes]([[docsref:/utilities/float]]) or [text alignment classes]([[docsref:/utilities/text#text-alignment]]). `block`-level images can be centered using [the `.mx-auto` margin utility class]([[docsref:/utilities/spacing#horizontal-centering]]). - - -`} /> - - -`} /> - - - - `} /> - - -## Picture - -If you are using the `` element to specify multiple `` elements for a specific ``, make sure to add the `.img-*` classes to the `` and not to the `` tag. - -```html - - - ... - -``` - -## CSS - -### Sass variables - -Variables are available for image thumbnails. - - diff --git a/site/src/content/docs/customize/color-modes.mdx b/site/src/content/docs/customize/color-modes.mdx deleted file mode 100644 index 79dd7f6..0000000 --- a/site/src/content/docs/customize/color-modes.mdx +++ /dev/null @@ -1,253 +0,0 @@ ---- -title: Color modes -description: Bootstrap now supports color modes, or themes, as of v5.3.0. Explore our default light color mode and the new dark mode, or create your own using our styles as your template. -toc: true -added: - version: "5.3" ---- - -import { getDocsRelativePath } from '@libs/path' - - -**Try it yourself!** Download the source code and working demo for using Bootstrap with Stylelint, and the color modes from the [twbs/examples repository](https://github.com/twbs/examples/tree/main/color-modes). You can also [open the example in StackBlitz](https://stackblitz.com/github/twbs/examples/tree/main/color-modes?file=index.html). - - -## Dark mode - -**Bootstrap now supports color modes, starting with dark mode!** With v5.3.0 you can implement your own color mode toggler (see below for an example from Bootstrap’s docs) and apply the different color modes as you see fit. We support a light mode (default) and now dark mode. Color modes can be toggled globally on the `` element, or on specific components and elements, thanks to the `data-bs-theme` attribute. - -Alternatively, you can also switch to a media query implementation thanks to our color mode mixin—see [the usage section for details](#building-with-sass). Heads up though—this eliminates your ability to change themes on a per-component basis as shown below. - -## Example - -For example, to change the color mode of a dropdown menu, add `data-bs-theme="light"` or `data-bs-theme="dark"` to the parent `.dropdown`. Now, no matter the global color mode, these dropdowns will display with the specified theme value. - - - - - - - `} /> - -## How it works - -- As shown above, color mode styles are controlled by the `data-bs-theme` attribute. This attribute can be applied to the `` element, or to any other element or Bootstrap component. If applied to the `` element, it will apply to everything. If applied to a component or element, it will be scoped to that specific component or element. - -- For each color mode you wish to support, you’ll need to add new overrides for the shared global CSS variables. We do this already in our `_root.scss` stylesheet for dark mode, with light mode being the default values. In writing color mode specific styles, use the mixin: - - ```scss - // Color mode variables in _root.scss - @include color-mode(dark) { - // CSS variable overrides here... - } - ``` - -- We use a custom `_variables-dark.scss` to power those shared global CSS variable overrides for dark mode. This file isn’t required for your own custom color modes, but it’s required for our dark mode for two reasons. First, it’s better to have a single place to reset global colors. Second, some Sass variables had to be overridden for background images embedded in our CSS for accordions, form components, and more. - -## Usage - -### Enable dark mode - -Enable the built in dark color mode across your entire project by adding the `data-bs-theme="dark"` attribute to the `` element. This will apply the dark color mode to all components and elements, other than those with a specific `data-bs-theme` attribute applied. Building on the [quick start template]([[docsref:/getting-started/introduction#quick-start]]): - -```html - - - - - - Bootstrap demo - - - -

    Hello, world!

    - - - -``` - -Bootstrap does not yet ship with a built-in color mode picker, but you can use the one from our own documentation if you like. [Learn more in the JavaScript section.](#javascript) - -### Building with Sass - -Our new dark mode option is available to use for all users of Bootstrap, but it’s controlled via data attributes instead of media queries and does not automatically toggle your project’s color mode. You can disable our dark mode entirely via Sass by changing `$enable-dark-mode` to `false`. - -We use a custom Sass mixin, `color-mode()`, to help you control _how_ color modes are applied. By default, we use a `data` attribute approach, allowing you to create more user-friendly experiences where your visitors can choose to have an automatic dark mode or control their preference (like in our own docs here). This is also an easy and scalable way to add different themes and more custom color modes beyond light and dark. - -In case you want to use media queries and only make color modes automatic, you can change the mixin’s default type via Sass variable. Consider the following snippet and its compiled CSS output. - -```scss -$color-mode-type: data; - -@include color-mode(dark) { - .element { - color: var(--bs-primary-text-emphasis); - background-color: var(--bs-primary-bg-subtle); - } -} -``` - -Outputs to: - -```css -[data-bs-theme=dark] .element { - color: var(--bs-primary-text-emphasis); - background-color: var(--bs-primary-bg-subtle); -} -``` - -And when setting to `media-query`: - -```scss -$color-mode-type: media-query; - -@include color-mode(dark) { - .element { - color: var(--bs-primary-text-emphasis); - background-color: var(--bs-primary-bg-subtle); - } -} -``` - -Outputs to: - -```css -@media (prefers-color-scheme: dark) { - .element { - color: var(--bs-primary-text-emphasis); - background-color: var(--bs-primary-bg-subtle); - } -} -``` - -## Custom color modes - -While the primary use case for color modes is light and dark mode, custom color modes are also possible. Create your own `data-bs-theme` selector with a custom value as the name of your color mode, then modify our Sass and CSS variables as needed. We opted to create a separate `_variables-dark.scss` stylesheet to house Bootstrap’s dark mode specific Sass variables, but that’s not required for you. - -For example, you can create a “blue theme” with the selector `data-bs-theme="blue"`. In your custom Sass or CSS file, add the new selector and override any global or component CSS variables as needed. If you’re using Sass, you can also use Sass’s functions within your CSS variable overrides. - - - - -
    Example blue theme
    -

    Some paragraph text to show how the blue theme might look with written copy.

    - -
    - - -`} /> - -```html -
    - ... -
    -``` - -## JavaScript - -To allow visitors or users to toggle color modes, you’ll need to create a toggle element to control the `data-bs-theme` attribute on the root element, ``. We’ve built a toggler in our documentation that initially defers to a user’s current system color mode, but provides an option to override that and pick a specific color mode. - -Here’s a look at the JavaScript that powers it. Feel free to inspect our own documentation navbar to see how it’s implemented using HTML and CSS from our own components. It is suggested to include the JavaScript at the top of your page to reduce potential screen flickering during reloading of your site. Note that if you decide to use media queries for your color modes, your JavaScript may need to be modified or removed if you prefer an implicit control. - - - -## Adding theme colors - -Adding a new color in `$theme-colors` is not enough for some of our components like [alerts]([[docsref:/components/alerts]]) and [list groups]([[docsref:/components/list-group]]). New colors must also be defined in `$theme-colors-text`, `$theme-colors-bg-subtle`, and `$theme-colors-border-subtle` for light theme; but also in `$theme-colors-text-dark`, `$theme-colors-bg-subtle-dark`, and `$theme-colors-border-subtle-dark` for dark theme. - -This is a manual process because Sass cannot generate its own Sass variables from an existing variable or map. In future versions of Bootstrap, we'll revisit this setup to reduce the duplication. - -```scss -// Required -@import "functions"; -@import "variables"; -@import "variables-dark"; - -// Add a custom color to $theme-colors -$custom-colors: ( - "custom-color": #712cf9 -); -$theme-colors: map-merge($theme-colors, $custom-colors); - -@import "maps"; -@import "mixins"; -@import "utilities"; - -// Add a custom color to new theme maps - -// Light mode -$custom-colors-text: ("custom-color": #712cf9); -$custom-colors-bg-subtle: ("custom-color": #e1d2fe); -$custom-colors-border-subtle: ("custom-color": #bfa1fc); - -$theme-colors-text: map-merge($theme-colors-text, $custom-colors-text); -$theme-colors-bg-subtle: map-merge($theme-colors-bg-subtle, $custom-colors-bg-subtle); -$theme-colors-border-subtle: map-merge($theme-colors-border-subtle, $custom-colors-border-subtle); - -// Dark mode -$custom-colors-text-dark: ("custom-color": #e1d2f2); -$custom-colors-bg-subtle-dark: ("custom-color": #8951fa); -$custom-colors-border-subtle-dark: ("custom-color": #e1d2f2); - -$theme-colors-text-dark: map-merge($theme-colors-text-dark, $custom-colors-text-dark); -$theme-colors-bg-subtle-dark: map-merge($theme-colors-bg-subtle-dark, $custom-colors-bg-subtle-dark); -$theme-colors-border-subtle-dark: map-merge($theme-colors-border-subtle-dark, $custom-colors-border-subtle-dark); - -// Remainder of Bootstrap imports -@import "root"; -@import "reboot"; -// etc -``` - -## CSS - -### Variables - -Dozens of root level CSS variables are repeated as overrides for dark mode. These are scoped to the color mode selector, which defaults to `data-bs-theme` but [can be configured](#building-with-sass) to use a `prefers-color-scheme` media query. Use these variables as a guideline for generating your own new color modes. - - - -### Sass variables - -CSS variables for our dark color mode are partially generated from dark mode specific Sass variables in `_variables-dark.scss`. This also includes some custom overrides for changing the colors of embedded SVGs used throughout our components. - - - -### Sass mixins - -Styles for dark mode, and any custom color modes you create, can be scoped appropriately to the `data-bs-theme` attribute selector or media query with the customizable `color-mode()` mixin. See the [Sass usage section](#building-with-sass) for more details. - - diff --git a/site/src/content/docs/customize/color.mdx b/site/src/content/docs/customize/color.mdx deleted file mode 100644 index b24f14d..0000000 --- a/site/src/content/docs/customize/color.mdx +++ /dev/null @@ -1,513 +0,0 @@ ---- -title: Color -description: Bootstrap is supported by an extensive color system that themes our styles and components. This enables more comprehensive customization and extension for any project. -toc: true ---- - -import { getData } from '@libs/data' -import { getSequence } from '@libs/utils' - -## Colors - - - -Bootstrap’s color palette has continued to expand and become more nuanced in v5.3.0. We’ve added new variables for `secondary` and `tertiary` text and background colors, plus `{color}-bg-subtle`, `{color}-border-subtle`, and `{color}-text-emphasis` for our theme colors. These new colors are available through Sass and CSS variables (but not our color maps or utility classes) with the express goal of making it easier to customize across multiple colors modes like light and dark. These new variables are globally set on `:root` and are adapted for our new dark color mode while our original theme colors remain unchanged. - -Colors ending in `-rgb` provide the `red, green, blue` values for use in `rgb()` and `rgba()` color modes. For example, `rgba(var(--bs-secondary-bg-rgb), .5)`. - - -**Heads up!** There’s some potential confusion with our new secondary and tertiary colors, and our existing secondary theme color, as well as our light and dark theme colors. Expect this to be ironed out in v6. - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    DescriptionSwatchVariables
    - **Body —** Default foreground (color) and background, including components. - -
     
    -
    - `--bs-body-color`
    `--bs-body-color-rgb` -
    -
     
    -
    - `--bs-body-bg`
    `--bs-body-bg-rgb` -
    - **Secondary —** Use the `color` option for lighter text. Use the `bg` option for dividers and to indicate disabled component states. - -
     
    -
    - `--bs-secondary-color`
    `--bs-secondary-color-rgb` -
    -
     
    -
    - `--bs-secondary-bg`
    `--bs-secondary-bg-rgb` -
    - **Tertiary —** Use the `color` option for even lighter text. Use the `bg` option to style backgrounds for hover states, accents, and wells. - -
     
    -
    - `--bs-tertiary-color`
    `--bs-tertiary-color-rgb` -
    -
     
    -
    - `--bs-tertiary-bg`
    `--bs-tertiary-bg-rgb` -
    - **Emphasis —** For higher contrast text. Not applicable for backgrounds. - -
     
    -
    - `--bs-emphasis-color`
    `--bs-emphasis-color-rgb` -
    - **Border —** For component borders, dividers, and rules. Use `--bs-border-color-translucent` to blend with backgrounds with an `rgba()` value. - -
     
    -
    - `--bs-border-color`
    `--bs-border-color-rgb` -
    - **Primary —** Main theme color, used for hyperlinks, focus styles, and component and form active states. - -
     
    -
    - `--bs-primary`
    `--bs-primary-rgb` -
    -
     
    -
    - `--bs-primary-bg-subtle` -
    -
     
    -
    - `--bs-primary-border-subtle` -
    -
    Text
    -
    - `--bs-primary-text-emphasis` -
    - **Success —** Theme color used for positive or successful actions and information. - -
     
    -
    - `--bs-success`
    `--bs-success-rgb` -
    -
     
    -
    - `--bs-success-bg-subtle` -
    -
     
    -
    - `--bs-success-border-subtle` -
    -
    Text
    -
    - `--bs-success-text-emphasis` -
    - **Danger —** Theme color used for errors and dangerous actions. - -
     
    -
    - `--bs-danger`
    `--bs-danger-rgb` -
    -
     
    -
    - `--bs-danger-bg-subtle` -
    -
     
    -
    - `--bs-danger-border-subtle` -
    -
    Text
    -
    - `--bs-danger-text-emphasis` -
    - **Warning —** Theme color used for non-destructive warning messages. - -
     
    -
    - `--bs-warning`
    `--bs-warning-rgb` -
    -
     
    -
    - `--bs-warning-bg-subtle` -
    -
     
    -
    - `--bs-warning-border-subtle` -
    -
    Text
    -
    - `--bs-warning-text-emphasis` -
    - **Info —** Theme color used for neutral and informative content. - -
     
    -
    - `--bs-info`
    `--bs-info-rgb` -
    -
     
    -
    - `--bs-info-bg-subtle` -
    -
     
    -
    - `--bs-info-border-subtle` -
    -
    Text
    -
    - `--bs-info-text-emphasis` -
    - **Light —** Additional theme option for less contrasting colors. - -
     
    -
    - `--bs-light`
    `--bs-light-rgb` -
    -
     
    -
    - `--bs-light-bg-subtle` -
    -
     
    -
    - `--bs-light-border-subtle` -
    -
    Text
    -
    - `--bs-light-text-emphasis` -
    - **Dark —** Additional theme option for higher contrasting colors. - -
     
    -
    - `--bs-dark`
    `--bs-dark-rgb` -
    -
     
    -
    - `--bs-dark-bg-subtle` -
    -
     
    -
    - `--bs-dark-border-subtle` -
    -
    Text
    -
    - `--bs-dark-text-emphasis` -
    -
    - -### Using the new colors - -These new colors are accessible via CSS variables and utility classes—like `--bs-primary-bg-subtle` and `.bg-primary-subtle`—allowing you to compose your own CSS rules with the variables, or to quickly apply styles via classes. The utilities are built with the color’s associated CSS variables, and since we customize those CSS variables for dark mode, they are also adaptive to color mode by default. - - - Example element with utilities - `} /> - -### Theme colors - -We use a subset of all colors to create a smaller color palette for generating color schemes, also available as Sass variables and a Sass map in Bootstrap’s `scss/_variables.scss` file. - -
    - {getData('theme-colors').map((themeColor) => { - return ( -
    -
    {themeColor.title}
    -
    - ) - })} -
    - -All these colors are available as a Sass map, `$theme-colors`. - - - -Check out [our Sass maps and loops docs]([[docsref:/customize/sass#maps-and-loops]]) for how to modify these colors. - -### All colors - -All Bootstrap colors are available as Sass variables and a Sass map in `scss/_variables.scss` file. To avoid increased file sizes, we don’t create text or background color classes for each of these variables. Instead, we choose a subset of these colors for a [theme palette](#theme-colors). - -Be sure to monitor contrast ratios as you customize colors. As shown below, we’ve added three contrast ratios to each of the main colors—one for the swatch’s current colors, one for against white, and one for against black. - -
    - {getData('colors').map((color) => { - if ((color.name !== "white") && (color.name !== "gray") && (color.name !== "gray-dark")) { - return ( -
    -
    - ${color.name} - {color.hex} -
    - - {getSequence(100, 900, 100).map((value) => { - return ( -
    ${color.name}-{value}
    - ) - })} -
    - ) - } - })} - -
    -
    $gray-500#adb5bd
    - {getData('grays').map((gray) => { - return ( -
    $gray-{gray.name}
    - ) - })} -
    - -
    -
    - $black - #000 -
    -
    - $white - #fff -
    -
    -
    - -### Notes on Sass - -Sass cannot programmatically generate variables, so we manually created variables for every tint and shade ourselves. We specify the midpoint value (e.g., `$blue-500`) and use custom color functions to tint (lighten) or shade (darken) our colors via Sass’s `mix()` color function. - -Using `mix()` is not the same as `lighten()` and `darken()`—the former blends the specified color with white or black, while the latter only adjusts the lightness value of each color. The result is a much more complete suite of colors, as [shown in this CodePen demo](https://codepen.io/emdeoh/pen/zYOQOPB). - -Our `tint-color()` and `shade-color()` functions use `mix()` alongside our `$theme-color-interval` variable, which specifies a stepped percentage value for each mixed color we produce. See the `scss/_functions.scss` and `scss/_variables.scss` files for the full source code. - -## Color Sass maps - -Bootstrap’s source Sass files include three maps to help you quickly and easily loop over a list of colors and their hex values. - -- `$colors` lists all our available base (`500`) colors -- `$theme-colors` lists all semantically named theme colors (shown below) -- `$grays` lists all tints and shades of gray - -Within `scss/_variables.scss`, you’ll find Bootstrap’s color variables and Sass map. Here’s an example of the `$colors` Sass map: - - - -Add, remove, or modify values within the map to update how they’re used in many other components. Unfortunately at this time, not _every_ component utilizes this Sass map. Future updates will strive to improve upon this. Until then, plan on making use of the `${color}` variables and this Sass map. - -### Example - -Here’s how you can use these in your Sass: - -```scss -.alpha { color: $purple; } -.beta { - color: $yellow-300; - background-color: $indigo-900; -} -``` - -[Color]([[docsref:/utilities/colors]]) and [background]([[docsref:/utilities/background]]) utility classes are also available for setting `color` and `background-color` using the `500` color values. - -## Generating utilities - - - -Bootstrap doesn’t include `color` and `background-color` utilities for every color variable, but you can generate these yourself with our [utility API]([[docsref:/utilities/api]]) and our extended Sass maps added in v5.1.0. - -1. To start, make sure you’ve imported our functions, variables, mixins, and utilities. -2. Use our `map-merge-multiple()` function to quickly merge multiple Sass maps together in a new map. -3. Merge this new combined map to extend any utility with a `{color}-{level}` class name. - -Here’s an example that generates text color utilities (e.g., `.text-purple-500`) using the above steps. - -```scss -@import "bootstrap/scss/functions"; -@import "bootstrap/scss/variables"; -@import "bootstrap/scss/variables-dark"; -@import "bootstrap/scss/maps"; -@import "bootstrap/scss/mixins"; -@import "bootstrap/scss/utilities"; - -$all-colors: map-merge-multiple($blues, $indigos, $purples, $pinks, $reds, $oranges, $yellows, $greens, $teals, $cyans); - -$utilities: map-merge( - $utilities, - ( - "color": map-merge( - map-get($utilities, "color"), - ( - values: map-merge( - map-get(map-get($utilities, "color"), "values"), - ( - $all-colors - ), - ), - ), - ), - ) -); - -@import "bootstrap/scss/utilities/api"; -``` - -This will generate new `.text-{color}-{level}` utilities for every color and level. You can do the same for any other utility and property as well. diff --git a/site/src/content/docs/customize/components.mdx b/site/src/content/docs/customize/components.mdx deleted file mode 100644 index 8d6dead..0000000 --- a/site/src/content/docs/customize/components.mdx +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: Components -description: Learn how and why we build nearly all our components responsively and with base and modifier classes. -toc: true ---- - -## Base classes - -Bootstrap’s components are largely built with a base-modifier nomenclature. We group as many shared properties as possible into a base class, like `.btn`, and then group individual styles for each variant into modifier classes, like `.btn-primary` or `.btn-success`. - -To build our modifier classes, we use Sass’s `@each` loops to iterate over a Sass map. This is especially helpful for generating variants of a component by our `$theme-colors` and creating responsive variants for each breakpoint. As you customize these Sass maps and recompile, you’ll automatically see your changes reflected in these loops. - -Check out [our Sass maps and loops docs]([[docsref:/customize/sass#maps-and-loops]]) for how to customize these loops and extend Bootstrap’s base-modifier approach to your own code. - -## Modifiers - -Many of Bootstrap’s components are built with a base-modifier class approach. This means the bulk of the styling is contained to a base class (e.g., `.btn`) while style variations are confined to modifier classes (e.g., `.btn-danger`). These modifier classes are built from the `$theme-colors` map to make customizing the number and name of our modifier classes. - -Here are two examples of how we loop over the `$theme-colors` map to generate modifiers to the `.alert` and `.list-group` components. - - - - - -## Responsive - -These Sass loops aren’t limited to color maps, either. You can also generate responsive variations of your components. Take for example our responsive alignment of the dropdowns where we mix an `@each` loop for the `$grid-breakpoints` Sass map with a media query include. - - - -Should you modify your `$grid-breakpoints`, your changes will apply to all the loops iterating over that map. - - - -For more information and examples on how to modify our Sass maps and variables, please refer to [the CSS section of the Grid documentation]([[docsref:/layout/grid#css]]). - -## Creating your own - -We encourage you to adopt these guidelines when building with Bootstrap to create your own components. We’ve extended this approach ourselves to the custom components in our documentation and examples. Components like our callouts are built just like our provided components with base and modifier classes. - - - This is a callout. We built it custom for our docs so our messages to you stand out. It has three variants via modifier classes. - -`} /> - -```html -
    ...
    -``` - -In your CSS, you’d have something like the following where the bulk of the styling is done via `.callout`. Then, the unique styles between each variant is controlled via modifier class. - -```scss -// Base class -.callout {} - -// Modifier classes -.callout-info {} -.callout-warning {} -.callout-danger {} -``` - -For the callouts, that unique styling is just a `border-left-color`. When you combine that base class with one of those modifier classes, you get your complete component family: - - -**This is an info callout.** Example text to show it in action. - - - -**This is a warning callout.** Example text to show it in action. - - - -**This is a danger callout.** Example text to show it in action. - diff --git a/site/src/content/docs/customize/css-variables.mdx b/site/src/content/docs/customize/css-variables.mdx deleted file mode 100644 index 20a0b9d..0000000 --- a/site/src/content/docs/customize/css-variables.mdx +++ /dev/null @@ -1,70 +0,0 @@ ---- -title: CSS variables -description: Use Bootstrap’s CSS custom properties for fast and forward-looking design and development. -toc: true ---- - -Bootstrap includes many [CSS custom properties (variables)](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) in its compiled CSS for real-time customization without the need to recompile Sass. These provide easy access to commonly used values like our theme colors, breakpoints, and primary font stacks when working in your browser’s inspector, a code sandbox, or general prototyping. - -**All our custom properties are prefixed with `bs-`** to avoid conflicts with third party CSS. - -## Root variables - -Here are the variables we include (note that the `:root` is required) that can be accessed anywhere Bootstrap’s CSS is loaded. They’re located in our `_root.scss` file and included in our compiled dist files. - -### Default - -These CSS variables are available everywhere, regardless of color mode. - - - -### Dark mode - -These variables are scoped to our built-in dark mode. - - - -## Component variables - -Bootstrap 5 is increasingly making use of custom properties as local variables for various components. This way we reduce our compiled CSS, ensure styles aren’t inherited in places like nested tables, and allow some basic restyling and extending of Bootstrap components after Sass compilation. - -Have a look at our table documentation for some [insight into how we’re using CSS variables]([[docsref:/content/tables#how-do-the-variants-and-accented-tables-work]]). Our [navbars also use CSS variables]([[docsref:/components/navbar#css]]) as of v5.2.0. We’re also using CSS variables across our grids—primarily for gutters the [new opt-in CSS grid]([[docsref:/layout/css-grid]])—with more component usage coming in the future. - -Whenever possible, we'll assign CSS variables at the base component level (e.g., `.navbar` for navbar and its sub-components). This reduces guessing on where and how to customize, and allows for easy modifications by our team in future updates. - -## Prefix - -Most CSS variables use a prefix to avoid collisions with your own codebase. This prefix is in addition to the `--` that’s required on every CSS variable. - -Customize the prefix via the `$prefix` Sass variable. By default, it’s set to `bs-` (note the trailing dash). - -## Examples - -CSS variables offer similar flexibility to Sass’s variables, but without the need for compilation before being served to the browser. For example, here we’re resetting our page’s font and link styles with CSS variables. - -```css -body { - font: 1rem/1.5 var(--bs-font-sans-serif); -} -a { - color: var(--bs-blue); -} -``` - -## Focus variables - - - -Bootstrap provides custom `:focus` styles using a combination of Sass and CSS variables that can be optionally added to specific components and elements. We do not yet globally override all `:focus` styles. - -In our Sass, we set default values that can be customized before compiling. - - - -Those variables are then reassigned to `:root` level CSS variables that can be customized in real-time, including with options for `x` and `y` offsets (which default to their fallback value of `0`). - - - -## Grid breakpoints - -While we include our grid breakpoints as CSS variables (except for `xs`), be aware that **CSS variables do not work in media queries**. This is by design in the CSS spec for variables, but may change in coming years with support for `env()` variables. Check out [this Stack Overflow answer](https://stackoverflow.com/a/47212942) for some helpful links. In the meantime, you can use these variables in other CSS situations, as well as in your JavaScript. diff --git a/site/src/content/docs/customize/overview.mdx b/site/src/content/docs/customize/overview.mdx deleted file mode 100644 index 7acb624..0000000 --- a/site/src/content/docs/customize/overview.mdx +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: Customize -description: Learn how to theme, customize, and extend Bootstrap with Sass, a boatload of global options, an expansive color system, and more. -toc: false -aliases: "/docs/[[config:docs_version]]/customize/" -sections: - - title: Sass - description: Utilize our source Sass files to take advantage of variables, maps, mixins, and functions. - - title: Options - description: Customize Bootstrap with built-in variables to easily toggle global CSS preferences. - - title: Color - description: Learn about and customize the color systems that support the entire toolkit. - - title: Color modes - description: Explore our default light mode and the new dark mode, or create custom color modes yourself. - - title: Components - description: Learn how we build nearly all our components responsively and with base and modifier classes. - - title: CSS variables - description: Use Bootstrap’s CSS custom properties for fast and forward-looking design and development. - - title: Optimize - description: Keep your projects lean, responsive, and maintainable so you can deliver the best experience. ---- - -## Overview - -There are multiple ways to customize Bootstrap. Your best path can depend on your project, the complexity of your build tools, the version of Bootstrap you’re using, browser support, and more. - -Our two preferred methods are: - -1. Using Bootstrap [via package manager]([[docsref:/getting-started/download#package-managers]]) so you can use and extend our source files. -2. Using Bootstrap’s compiled distribution files or [jsDelivr]([[docsref:/getting-started/download#cdn-via-jsdelivr]]) so you can add onto or override Bootstrap’s styles. - -While we cannot go into details here on how to use every package manager, we can give some guidance on [using Bootstrap with your own Sass compiler]([[docsref:/customize/sass]]). - -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 CSP 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]]) -- [Carousel controls]([[docsref:/components/carousel#with-controls]]) -- [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]]) -- [Navbar toggle buttons]([[docsref:/components/navbar#responsive-behaviors]]) -- [Select menus]([[docsref:/forms/select]]) - -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]([[docsref:/getting-started/webpack#extracting-svg-files]]), 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 the best path forward, if necessary. diff --git a/site/src/content/docs/docsref.mdx b/site/src/content/docs/docsref.mdx deleted file mode 100644 index d2311ce..0000000 --- a/site/src/content/docs/docsref.mdx +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: Docs reference -description: Examples of Bootstrap’s documentation-specific components and styles. -aliases: "/docsref/" -toc: true -robots: noindex,follow ---- - -## Buttons - - - - - -## Callouts - - - Default callout - - - - Warning callout - - - - Danger callout - - -## Code example - -```scss -.test { - --color: blue; -} -``` - -HTML abbreviation element.`} /> - -This is a test.`} /> - - - - diff --git a/site/src/content/docs/extend/icons.mdx b/site/src/content/docs/extend/icons.mdx deleted file mode 100644 index 67ac421..0000000 --- a/site/src/content/docs/extend/icons.mdx +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Icons -description: Guidance and suggestions for using external icon libraries with Bootstrap. ---- - -import { getData } from '@libs/data' - -While Bootstrap doesn’t include an icon set by default, we do have our own comprehensive icon library called Bootstrap Icons. Feel free to use them or any other icon set in your project. We’ve included details for Bootstrap Icons and other preferred icon sets below. - -While most icon sets include multiple file formats, we prefer SVG implementations for their improved accessibility and vector support. - -## Bootstrap Icons - -Bootstrap Icons is a growing library of SVG icons that are designed by [@mdo](https://github.com/mdo) and maintained by [the Bootstrap Team](https://github.com/orgs/twbs/people). The beginnings of this icon set come from Bootstrap’s very own components—our forms, carousels, and more. Bootstrap has very few icon needs out of the box, so we didn’t need much. However, once we got going, we couldn’t stop making more. - -Oh, and did we mention they’re completely open source? Licensed under MIT, just like Bootstrap, our icon set is available to everyone. - -[Learn more about Bootstrap Icons]([[config:icons]]), including how to install them and recommended usage. - -## Alternatives - -We’ve tested and used these icon sets ourselves as preferred alternatives to Bootstrap Icons. - -
      -{getData('icons').preferred.map((icon) => { - return ( -
    • {icon.name}
    • - ) -})} -
    - -## More options - -While we haven’t tried these out ourselves, they do look promising and provide multiple formats, including SVG. - -
      -{getData('icons').more.map((icon) => { - return ( -
    • {icon.name}
    • - ) -})} -
    diff --git a/site/src/content/docs/forms/checks-radios.mdx b/site/src/content/docs/forms/checks-radios.mdx deleted file mode 100644 index 8557916..0000000 --- a/site/src/content/docs/forms/checks-radios.mdx +++ /dev/null @@ -1,311 +0,0 @@ ---- -title: Checks and radios -description: Create consistent cross-browser and cross-device checkboxes and radios with our completely rewritten checks component. -aliases: "/docs/[[config:docs_version]]/forms/checks/" -toc: true ---- - -## Approach - -Browser default checkboxes and radios are replaced with the help of `.form-check`, a series of classes for both input types that improves the layout and behavior of their HTML elements, that provide greater customization and cross browser consistency. Checkboxes are for selecting one or several options in a list, while radios are for selecting one option from many. - -Structurally, our ``s and `