diff --git a/.babelrc.js b/.babelrc.js new file mode 100644 index 0000000..5139969 --- /dev/null +++ b/.babelrc.js @@ -0,0 +1,12 @@ +module.exports = { + presets: [ + [ + '@babel/preset-env', + { + loose: true, + bugfixes: true, + modules: false + } + ] + ] +}; diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 0000000..cddd230 --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,12 @@ +# 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 new file mode 100644 index 0000000..235ad54 --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# 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 28fd5e8..7562983 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 dda75ca..fa7c00b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,6 @@ The MIT License (MIT) -Copyright (c) 2011-2022 Twitter, Inc. -Copyright (c) 2011-2022 The Bootstrap Authors +Copyright (c) 2011-2025 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 bd40192..e3618e6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- Bootstrap logo + Bootstrap logo

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

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

@@ -31,7 +29,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) @@ -46,37 +44,38 @@ 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.2.3.zip) +- [Download the latest release](https://github.com/twbs/bootstrap/archive/v5.3.8.zip) - Clone the repo: `git clone https://github.com/twbs/bootstrap.git` -- 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 [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 [NuGet](https://www.nuget.org/): CSS: `Install-Package bootstrap` Sass: `Install-Package bootstrap.sass` -Read the [Getting started page](https://getbootstrap.com/docs/5.2/getting-started/introduction/) for information on the framework contents, templates, examples, and more. +Read the [Getting started page](https://getbootstrap.com/docs/5.3/getting-started/introduction/) for information on the framework contents, templates, examples, and more. ## Status -[![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) +[![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) [![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) -[![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) +![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) -## 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 @@ -132,7 +131,7 @@ Within the download you'll find the following directories and files, logically g ```
-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/). +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/). ## Bugs and feature requests @@ -142,18 +141,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 [Hugo](https://gohugo.io/) 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 [Astro](https://astro.build/) 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/). Working on our search? Be sure to set `debug: true` in `site/assets/js/search.js`. +Documentation search is powered by [Algolia's DocSearch](https://docsearch.algolia.com/). ### Running documentation locally -1. Run `npm install` to install the Node.js dependencies, including Hugo (the site builder). +1. Run `npm install` to install the Node.js dependencies, including Astro (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 `http://localhost:9001/` in your browser, and voilà. +4. Open in your browser, and voilà. -Learn more about using Hugo by reading its [documentation](https://gohugo.io/documentation/). +Learn more about using Astro by reading its [documentation](https://docs.astro.build/en/getting-started/). ### Documentation for previous releases @@ -173,11 +172,12 @@ 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 Twitter](https://twitter.com/getbootstrap). +- Follow [@getbootstrap on X](https://x.com/getbootstrap). - Read and subscribe to [The Official Bootstrap Blog](https://blog.getbootstrap.com/). -- Ask and explore [our GitHub Discussions](https://github.com/twbs/bootstrap/discussions). +- 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/). - 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–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/). +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/). diff --git a/build/.eslintrc.json b/build/.eslintrc.json deleted file mode 100644 index dec6323..0000000 --- a/build/.eslintrc.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "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.js b/build/banner.mjs similarity index 50% rename from build/banner.js rename to build/banner.mjs index df82ff3..3fea93c 100644 --- a/build/banner.js +++ b/build/banner.mjs @@ -1,6 +1,12 @@ -'use strict' +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')) -const pkg = require('../package.json') const year = new Date().getFullYear() function getBanner(pluginFilename) { @@ -11,4 +17,4 @@ function getBanner(pluginFilename) { */` } -module.exports = getBanner +export default getBanner diff --git a/build/build-plugins.js b/build/build-plugins.mjs similarity index 82% rename from build/build-plugins.js rename to build/build-plugins.mjs index a160209..f56cd21 100644 --- a/build/build-plugins.js +++ b/build/build-plugins.mjs @@ -2,21 +2,22 @@ /*! * Script to build our plugins to use them separately. - * Copyright 2020-2022 The Bootstrap Authors - * Copyright 2020-2022 Twitter, Inc. + * Copyright 2020-2025 The Bootstrap Authors * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ -'use strict' +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' -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 __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(fileURLToPath(import.meta.url)) const sourcePath = path.resolve(__dirname, '../js/src/').replace(/\\/g, '/') -const jsFiles = globby.sync(sourcePath + '/**/*.js') +const jsFiles = await globby(`${sourcePath}/**/*.js`) // Array which holds the resolved plugins const resolvedPlugins = [] @@ -27,7 +28,7 @@ const filenameToEntity = filename => filename.replace('.js', '') for (const file of jsFiles) { resolvedPlugins.push({ - src: file.replace('.js', ''), + src: file, dist: file.replace('src', 'dist'), fileName: path.basename(file), className: filenameToEntity(path.basename(file)) @@ -36,9 +37,12 @@ for (const file of jsFiles) { } const build = async plugin => { + /** + * @type {import('rollup').GlobalsOption} + */ const globals = {} - const bundle = await rollup.rollup({ + const bundle = await rollup({ input: plugin.src, plugins: [ babel({ diff --git a/build/change-version.js b/build/change-version.js deleted file mode 100644 index 57c5fde..0000000 --- a/build/change-version.js +++ /dev/null @@ -1,81 +0,0 @@ -#!/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 new file mode 100644 index 0000000..1400274 --- /dev/null +++ b/build/change-version.mjs @@ -0,0 +1,113 @@ +#!/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 new file mode 100755 index 0000000..357768a --- /dev/null +++ b/build/docs-prep.sh @@ -0,0 +1,157 @@ +#!/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.js b/build/generate-sri.mjs similarity index 60% rename from build/generate-sri.js rename to build/generate-sri.mjs index ef1b39f..5622843 100644 --- a/build/generate-sri.js +++ b/build/generate-sri.mjs @@ -5,17 +5,17 @@ * Remember to use the same vendor files as the CDN ones, * otherwise the hashes won't match! * - * Copyright 2017-2022 The Bootstrap Authors - * Copyright 2017-2022 Twitter, Inc. + * Copyright 2017-2025 The Bootstrap Authors * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ -'use strict' +import crypto from 'node:crypto' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import sh from 'shelljs' -const crypto = require('node:crypto') -const fs = require('node:fs') -const path = require('node:path') -const sh = require('shelljs') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) sh.config.fatal = true @@ -47,18 +47,18 @@ const files = [ } ] -for (const file of files) { - fs.readFile(file.file, 'utf8', (error, data) => { +for (const { file, configPropertyName } of files) { + fs.readFile(file, 'utf8', (error, data) => { if (error) { throw error } - const algo = 'sha384' - const hash = crypto.createHash(algo).update(data, 'utf8').digest('base64') - const integrity = `${algo}-${hash}` + const algorithm = 'sha384' + const hash = crypto.createHash(algorithm).update(data, 'utf8').digest('base64') + const integrity = `${algorithm}-${hash}` - console.log(`${file.configPropertyName}: ${integrity}`) + console.log(`${configPropertyName}: ${integrity}`) - sh.sed('-i', new RegExp(`^(\\s+${file.configPropertyName}:\\s+["'])\\S*(["'])`), `$1${integrity}$2`, configFile) + sh.sed('-i', new RegExp(`^(\\s+${configPropertyName}:\\s+["'])\\S*(["'])`), `$1${integrity}$2`, configFile) }) } diff --git a/build/postcss.config.js b/build/postcss.config.mjs similarity index 86% rename from build/postcss.config.js rename to build/postcss.config.mjs index 7f8186d..7717cfc 100644 --- a/build/postcss.config.js +++ b/build/postcss.config.mjs @@ -1,12 +1,10 @@ -'use strict' - const mapConfig = { inline: false, annotation: true, sourcesContent: true } -module.exports = context => { +export default context => { return { map: context.file.dirname.includes('examples') ? false : mapConfig, plugins: { diff --git a/build/rollup.config.js b/build/rollup.config.mjs similarity index 62% rename from build/rollup.config.js rename to build/rollup.config.mjs index 27f12ac..dd6c7d1 100644 --- a/build/rollup.config.js +++ b/build/rollup.config.mjs @@ -1,15 +1,17 @@ -'use strict' +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' -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 __dirname = path.dirname(fileURLToPath(import.meta.url)) const BUNDLE = process.env.BUNDLE === 'true' const ESM = process.env.ESM === 'true' -let fileDestination = `bootstrap${ESM ? '.esm' : ''}` +let destinationFile = `bootstrap${ESM ? '.esm' : ''}` const external = ['@popperjs/core'] const plugins = [ babel({ @@ -24,7 +26,7 @@ const globals = { } if (BUNDLE) { - fileDestination += '.bundle' + destinationFile += '.bundle' // Remove last entry in external array to bundle Popper external.pop() delete globals['@popperjs/core'] @@ -40,8 +42,8 @@ if (BUNDLE) { const rollupConfig = { input: path.resolve(__dirname, `../js/index.${ESM ? 'esm' : 'umd'}.js`), output: { - banner, - file: path.resolve(__dirname, `../dist/js/${fileDestination}.js`), + banner: banner(), + file: path.resolve(__dirname, `../dist/js/${destinationFile}.js`), format: ESM ? 'esm' : 'umd', globals, generatedCode: 'es2015' @@ -54,4 +56,4 @@ if (!ESM) { rollupConfig.output.name = 'bootstrap' } -module.exports = rollupConfig +export default rollupConfig diff --git a/build/vnu-jar.js b/build/vnu-jar.mjs similarity index 64% rename from build/vnu-jar.js rename to build/vnu-jar.mjs index f29eeb7..4eedb1b 100644 --- a/build/vnu-jar.js +++ b/build/vnu-jar.mjs @@ -2,22 +2,22 @@ /*! * Script to run vnu-jar if Java is available. - * Copyright 2017-2022 The Bootstrap Authors - * Copyright 2017-2022 Twitter, Inc. + * Copyright 2017-2025 The Bootstrap Authors * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ -'use strict' - -const { execFile, spawn } = require('node:child_process') -const vnu = require('vnu-jar') +import { execFile, spawn } from 'node:child_process' +import vnu from 'vnu-jar' execFile('java', ['-version'], (error, stdout, stderr) => { if (error) { - console.error('Skipping vnu-jar test; Java is missing.') + console.error('Skipping vnu-jar test; Java is probably missing.') + console.error(error) return } + console.log('Running vnu-jar validation...') + const is32bitJava = !/64-Bit/.test(stderr) // vnu-jar accepts multiple ignores joined with a `|`. @@ -30,7 +30,14 @@ 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.' + '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.' ].join('|') const args = [ @@ -49,6 +56,8 @@ 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.js b/build/zip-examples.mjs similarity index 57% rename from build/zip-examples.js rename to build/zip-examples.mjs index 077901e..6b2e1e0 100644 --- a/build/zip-examples.js +++ b/build/zip-examples.mjs @@ -3,16 +3,20 @@ /*! * Script to create the built examples zip archive; * requires the `zip` command to be present! - * Copyright 2020-2022 The Bootstrap Authors + * Copyright 2020-2025 The Bootstrap Authors * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ -'use strict' +import fs from 'node:fs/promises' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import sh from 'shelljs' +import { format } from 'prettier' -const path = require('node:path') -const sh = require('shelljs') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const pkg = require('../package.json') +const pkgJson = path.join(__dirname, '../package.json') +const pkg = JSON.parse(await fs.readFile(pkgJson, 'utf8')) const versionShort = pkg.config.version_short const distFolder = `bootstrap-${pkg.version}-examples` @@ -34,6 +38,9 @@ const imgFiles = [ 'bootstrap-logo.svg', 'bootstrap-logo-white.svg' ] +const staticJsFiles = [ + 'color-modes.js' +] sh.config.fatal = true @@ -52,7 +59,8 @@ sh.mkdir('-p', [ distFolder, `${distFolder}/assets/brand/`, `${distFolder}/assets/dist/css/`, - `${distFolder}/assets/dist/js/` + `${distFolder}/assets/dist/js/`, + `${distFolder}/assets/js/` ]) sh.cp('-Rf', `${docsDir}/examples/*`, distFolder) @@ -69,22 +77,44 @@ 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 -for (const file of sh.find(`${distFolder}/**/*.html`)) { +const htmlFiles = sh.find(`${distFolder}/**/*.html`) + +const formatPromises = htmlFiles.map(async file => { const fileContents = sh.cat(file) .toString() .replace(new RegExp(`"/docs/${versionShort}/`, 'g'), '"../') .replace(/"..\/dist\//g, '"../assets/dist/') - .replace(/(/g, '$1>') - .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 of Object.keys(storeElementEvent)) { + for (const [keyHandlers, event] of Object.entries(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; @@ -542,7 +486,6 @@ let bubbles = true; let nativeDispatch = true; let defaultPrevented = false; - if (inNamespace && $) { jQueryEvent = $.Event(event, args); $(element).trigger(jQueryEvent); @@ -550,177 +493,103 @@ nativeDispatch = !jQueryEvent.isImmediatePropagationStopped(); defaultPrevented = jQueryEvent.isDefaultPrevented(); } - - let evt = new Event(event, { + const evt = hydrateObj(new Event(event, { bubbles, cancelable: true - }); - evt = hydrateObj(evt, args); - + }), 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 (v5.2.3): dom/data.js + * Bootstrap dom/manipulator.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.length); + pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1); attributes[pureKey] = normalizeData(element.dataset[key]); } - return attributes; }, - getDataAttribute(element, key) { return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`)); } - }; /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/config.js + * Bootstrap util/config.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ + + /** * Class definition */ @@ -730,63 +599,56 @@ 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 of Object.keys(configTypes)) { - const expectedTypes = configTypes[property]; + for (const [property, expectedTypes] of Object.entries(configTypes)) { 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 (v5.2.3): base-component.js + * Bootstrap base-component.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ + + /** * Constants */ - const VERSION = '5.2.3'; + const VERSION = '5.3.8'; + /** * Class definition */ @@ -795,69 +657,147 @@ 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 - + } + // 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 (v5.2.3): util/component-functions.js + * 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 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ @@ -869,24 +809,25 @@ 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); - 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 - + // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method instance[method](); }); }; /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): alert.js + * Bootstrap alert.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ + + /** * Constants */ @@ -898,6 +839,7 @@ const EVENT_CLOSED = `closed${EVENT_KEY$b}`; const CLASS_NAME_FADE$5 = 'fade'; const CLASS_NAME_SHOW$8 = 'show'; + /** * Class definition */ @@ -906,55 +848,47 @@ // 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 - + } + // 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'); + /** * jQuery */ @@ -963,10 +897,12 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): button.js + * Bootstrap button.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ + + /** * Constants */ @@ -978,6 +914,7 @@ 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 */ @@ -986,37 +923,36 @@ // 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 */ @@ -1025,81 +961,12 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): dom/selector-engine.js + * Bootstrap util/swipe.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 */ @@ -1125,6 +992,7 @@ leftCallback: '(function|null)', rightCallback: '(function|null)' }; + /** * Class definition */ @@ -1133,84 +1001,67 @@ 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 - + } + // 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)); @@ -1218,24 +1069,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 (v5.2.3): carousel.js + * Bootstrap carousel.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ + + /** * Constants */ @@ -1295,6 +1146,7 @@ touch: 'boolean', wrap: 'boolean' }; + /** * Class definition */ @@ -1308,32 +1160,27 @@ 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 @@ -1342,101 +1189,80 @@ 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 - + } + // 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 @@ -1444,16 +1270,12 @@ // 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)), @@ -1461,68 +1283,51 @@ }; 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, @@ -1531,25 +1336,19 @@ 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; @@ -1557,7 +1356,6 @@ reflow(nextElement); activeElement.classList.add(directionalClassName); nextElement.classList.add(directionalClassName); - const completeCallBack = () => { nextElement.classList.remove(directionalClassName, orderClassName); nextElement.classList.add(CLASS_NAME_ACTIVE$2); @@ -1565,113 +1363,89 @@ 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 - + } + // 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 = getElementFromSelector(this); - + const target = SelectorEngine.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 */ @@ -1680,10 +1454,12 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): collapse.js + * Bootstrap collapse.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ + + /** * Constants */ @@ -1715,6 +1491,7 @@ parent: '(null|element)', toggle: 'boolean' }; + /** * Class definition */ @@ -1725,41 +1502,34 @@ this._isTransitioning = false; this._triggerArray = []; const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$4); - for (const elem of toggleList) { - const selector = getSelectorFromElement(elem); + const selector = SelectorEngine.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(); @@ -1767,201 +1537,149 @@ this.show(); } } - show() { if (this._isTransitioning || this._isShown()) { return; } + let activeChildren = []; - let activeChildren = []; // find active children - + // 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 = getElementFromSelector(trigger); - + const element = SelectorEngine.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 = getElementFromSelector(element); - + const selected = SelectorEngine.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(); } - - const selector = getSelectorFromElement(this); - const selectorElements = SelectorEngine.find(selector); - - for (const element of selectorElements) { + for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) { Collapse.getOrCreateInstance(element, { toggle: false }).toggle(); } }); + /** * jQuery */ @@ -2131,7 +1849,7 @@ function getUAString() { var uaData = navigator.userAgentData; - if (uaData != null && uaData.brands) { + if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) { return uaData.brands.map(function (item) { return item.brand + "/" + item.version; }).join(' '); @@ -2419,7 +2137,6 @@ } if (!contains(state.elements.popper, arrowElement)) { - return; } @@ -2450,10 +2167,9 @@ // 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) { + function roundOffsetsByDPR(_ref, win) { var x = _ref.x, y = _ref.y; - var win = window; var dpr = win.devicePixelRatio || 1; return { x: round(x * dpr) / dpr || 0, @@ -2536,7 +2252,7 @@ var _ref4 = roundOffsets === true ? roundOffsetsByDPR({ x: x, y: y - }) : { + }, getWindow(popper)) : { x: x, y: y }; @@ -2562,7 +2278,6 @@ 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), @@ -2953,7 +2668,6 @@ var popperOffsets = computeOffsets({ reference: referenceClientRect, element: popperRect, - strategy: 'absolute', placement: placement }); var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets)); @@ -3281,7 +2995,6 @@ 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 @@ -3630,8 +3343,7 @@ state.orderedModifiers = orderedModifiers.filter(function (m) { return m.enabled; - }); // Validate the provided modifiers so that the consumer will get warned - + }); runModifierEffects(); return instance.update(); }, @@ -3651,7 +3363,6 @@ // anymore if (!areValidElements(reference, popper)) { - return; } // Store the reference and popper rects to be read by modifiers @@ -3676,7 +3387,6 @@ }); for (var index = 0; index < state.orderedModifiers.length; index++) { - if (state.reset === true) { state.reset = false; index = -1; @@ -3714,7 +3424,6 @@ }; if (!areValidElements(reference, popper)) { - return instance; } @@ -3729,11 +3438,11 @@ // one. function runModifierEffects() { - state.orderedModifiers.forEach(function (_ref3) { - var name = _ref3.name, - _ref3$options = _ref3.options, - options = _ref3$options === void 0 ? {} : _ref3$options, - effect = _ref3.effect; + state.orderedModifiers.forEach(function (_ref) { + var name = _ref.name, + _ref$options = _ref.options, + options = _ref$options === void 0 ? {} : _ref$options, + effect = _ref.effect; if (typeof effect === 'function') { var cleanupFn = effect({ @@ -3774,52 +3483,54 @@ const Popper = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ __proto__: null, - popperGenerator, - detectOverflow, - createPopperBase: createPopper$2, - createPopper, - createPopperLite: createPopper$1, - top, - bottom, - right, - left, - auto, - basePlacements, - start, - end, - clippingParents, - viewport, - popper, - reference, - variationPlacements, - placements, - beforeRead, - read, - afterRead, - beforeMain, - main, afterMain, - beforeWrite, - write, + afterRead, afterWrite, - modifierPhases, applyStyles: applyStyles$1, arrow: arrow$1, + auto, + basePlacements, + beforeMain, + beforeRead, + beforeWrite, + bottom, + clippingParents, computeStyles: computeStyles$1, + createPopper, + createPopperBase: createPopper$2, + createPopperLite: createPopper$1, + detectOverflow, + end, eventListeners, flip: flip$1, hide: hide$1, + left, + main, + modifierPhases, offset: offset$1, + placements, + popper, + popperGenerator, popperOffsets: popperOffsets$1, - preventOverflow: preventOverflow$1 + preventOverflow: preventOverflow$1, + read, + reference, + right, + start, + top, + variationPlacements, + viewport, + write }, Symbol.toStringTag, { value: 'Module' })); /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): dropdown.js + * Bootstrap dropdown.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ + + /** * Constants */ @@ -3877,6 +3588,7 @@ popperConfig: '(null|object|function)', reference: '(string|element|object)' }; + /** * Class definition */ @@ -3886,143 +3598,112 @@ super(element, config); this._popper = null; this._parent = this._element.parentNode; // dropdown wrapper - // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/ - + // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/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(); - this._createPopper(); // If this is a touch-enabled device we add extra + // 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)'); + throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org/docs/v2/)'); } - let referenceElement = this._element; - if (this._config.reference === 'parent') { referenceElement = this._parent; } else if (isElement$1(this._config.reference)) { @@ -4030,65 +3711,49 @@ } 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(), @@ -4103,121 +3768,101 @@ 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, - ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig) + return { + ...defaultBsPopperConfig, + ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig]) }; } - _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 - + } + // 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(); - event.preventDefault(); // todo: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.2/forms/input-group/ - + // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/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(); @@ -4225,13 +3870,12 @@ 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); @@ -4240,6 +3884,7 @@ event.preventDefault(); Dropdown.getOrCreateInstance(this).toggle(); }); + /** * jQuery */ @@ -4248,129 +3893,12 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/scrollBar.js + * Bootstrap util/backdrop.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 */ @@ -4386,7 +3914,6 @@ 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', @@ -4395,6 +3922,7 @@ isVisible: 'boolean', rootElement: '(element|string)' }; + /** * Class definition */ @@ -4405,118 +3933,96 @@ 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 - + } + // 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 (v5.2.3): util/focustrap.js + * Bootstrap util/focustrap.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ + + /** * Constants */ @@ -4532,12 +4038,12 @@ const Default$7 = { autofocus: true, trapElement: null // The element to trap focus inside of - }; const DefaultType$7 = { autofocus: 'boolean', trapElement: 'element' }; + /** * Class definition */ @@ -4548,59 +4054,49 @@ 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) { @@ -4609,23 +4105,120 @@ elements[0].focus(); } } - _handleKeydown(event) { if (event.key !== TAB_KEY) { return; } - this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD; } - } /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): modal.js + * Bootstrap 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 modal.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + + /** * Constants */ @@ -4663,6 +4256,7 @@ focus: 'boolean', keyboard: 'boolean' }; + /** * Class definition */ @@ -4676,91 +4270,67 @@ this._isShown = false; this._isTransitioning = false; this._scrollBar = new ScrollBarHelper(); - this._addEventListeners(); - } // Getters - + } + // 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() { - for (const htmlElement of [window, this._dialog]) { - EventHandler.off(htmlElement, EVENT_KEY$4); - } - + EventHandler.off(window, EVENT_KEY$4); + EventHandler.off(this._dialog, 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), @@ -4768,64 +4338,47 @@ 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, () => { @@ -4839,157 +4392,124 @@ 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 = getElementFromSelector(this); - + const target = SelectorEngine.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 */ @@ -4998,10 +4518,12 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): offcanvas.js + * Bootstrap offcanvas.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ + + /** * Constants */ @@ -5036,6 +4558,7 @@ keyboard: 'boolean', scroll: 'boolean' }; + /** * Class definition */ @@ -5046,130 +4569,95 @@ this._isShown = false; this._backdrop = this._initializeBackDrop(); this._focustrap = this._initializeFocusTrap(); - this._addEventListeners(); - } // Getters - + } + // 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 - + } + // 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 - + }; + // '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, @@ -5179,75 +4667,63 @@ 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) { - EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED); + if (this._config.keyboard) { + this.hide(); return; } - - 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); + EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED); }); } + // 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 = getElementFromSelector(this); - + const target = SelectorEngine.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); }); @@ -5264,6 +4740,7 @@ } }); enableDismissTrigger(Offcanvas); + /** * jQuery */ @@ -5272,42 +4749,13 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/sanitizer.js + * Bootstrap util/sanitizer.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']); + + // js-docs-start allow-list 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], @@ -5317,7 +4765,10 @@ br: [], col: [], code: [], + dd: [], div: [], + dl: [], + dt: [], em: [], hr: [], h1: [], @@ -5341,46 +4792,64 @@ 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 (v5.2.3): util/template-factory.js + * Bootstrap util/template-factory.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ + + /** * Constants */ @@ -5409,6 +4878,7 @@ entry: '(string|element|function|null)', selector: '(string|element)' }; + /** * Class definition */ @@ -5417,65 +4887,53 @@ 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 - - - _typeCheckConfig(config) { - super._typeCheckConfig(config); - - this._checkContent(config.content); } + // Private + _typeCheckConfig(config) { + super._typeCheckConfig(config); + this._checkContent(config.content); + } _checkContent(arg) { for (const [selector, content] of Object.entries(arg)) { super._typeCheckConfig({ @@ -5484,61 +4942,50 @@ }, 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 typeof arg === 'function' ? arg(this) : arg; + return execute(arg, [undefined, this]); } - _putElementInTemplate(element, templateElement) { if (this._config.html) { templateElement.innerHTML = ''; templateElement.append(element); return; } - templateElement.textContent = element.textContent; } - } /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): tooltip.js + * Bootstrap tooltip.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ + + /** * Constants */ @@ -5581,7 +5028,7 @@ delay: 0, fallbackPlacements: ['top', 'right', 'bottom', 'left'], html: false, - offset: [0, 0], + offset: [0, 6], placement: 'top', popperConfig: null, sanitize: true, @@ -5610,6 +5057,7 @@ title: '(string|element|function)', trigger: 'string' }; + /** * Class definition */ @@ -5617,164 +5065,131 @@ class Tooltip extends BaseComponent { constructor(element, config) { if (typeof Popper === 'undefined') { - throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org)'); + throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org/docs/v2/)'); } + super(element, config); - super(element, config); // Private - + // Private this._isEnabled = true; this._timeout = 0; this._isHovered = null; this._activeTrigger = {}; this._popper = null; this._templateFactory = null; - this._newContent = null; // Protected + this._newContent = null; + // 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 + 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); - tip.classList.remove(CLASS_NAME_SHOW$2); // 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); } } - this._activeTrigger[TRIGGER_CLICK] = false; this._activeTrigger[TRIGGER_FOCUS] = false; this._activeTrigger[TRIGGER_HOVER] = false; @@ -5784,133 +5199,107 @@ 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 on v6 - + const tip = this._getTemplateFactory(content).toHtml(); + // TODO: remove this check in v6 if (!tip) { return null; } - - tip.classList.remove(CLASS_NAME_FADE$2, CLASS_NAME_SHOW$2); // todo: on v6 the following can be achieved with CSS only - + tip.classList.remove(CLASS_NAME_FADE$2, CLASS_NAME_SHOW$2); + // TODO: 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 = typeof this._config.placement === 'function' ? this._config.placement.call(this, tip, this._element) : this._config.placement; + const placement = execute(this._config.placement, [this, tip, this._element]); 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 typeof arg === 'function' ? arg.call(this._element) : arg; + return execute(arg, [this._element, this._element]); } - _getPopperConfig(attachment) { const defaultBsPopperConfig = { placement: attachment, @@ -5945,19 +5334,18 @@ } }] }; - return { ...defaultBsPopperConfig, - ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig) + return { + ...defaultBsPopperConfig, + ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig]) }; } - _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) { @@ -5965,187 +5353,151 @@ 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 in this._config) { - if (this.constructor.Default[key] !== this._config[key]) { - config[key] = this._config[key]; + for (const [key, value] of Object.entries(this._config)) { + if (this.constructor.Default[key] !== value) { + config[key] = value; } } - config.selector = false; - config.trigger = 'manual'; // In the future can be replaced with: + 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 (v5.2.3): popover.js + * Bootstrap popover.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ + + /** * Constants */ @@ -6153,16 +5505,19 @@ 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 */ @@ -6172,63 +5527,58 @@ 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 (v5.2.3): scrollspy.js + * Bootstrap scrollspy.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ + + /** * Constants */ @@ -6267,14 +5617,16 @@ target: 'element', threshold: 'array' }; + /** * Class definition */ class ScrollSpy extends BaseComponent { constructor(element, config) { - super(element, config); // this._element is the observablesContainer and config.target the menu links wrapper + super(element, config); + // 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; @@ -6285,87 +5637,75 @@ 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 - + } + // Private _configAfterMerge(config) { // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case - config.target = getElement(config.target) || document.body; // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only + config.target = getElement(config.target) || document.body; + // 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, @@ -6373,95 +5713,77 @@ 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 - + 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 - + 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 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); - const observableSection = SelectorEngine.findOne(anchor.hash, this._element); // ensure that the observableSection exists & is visible - + // ensure that the observableSection exists & is visible if (isVisible(observableSection)) { - this._targetLinks.set(anchor.hash, anchor); - + this._targetLinks.set(decodeURI(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
  • This may still come in handy in some situations.
  • - - {{< /example >}} + `} /> - {{< example show_markup="false" >}} +
  • This is a list item.
  • And another one.
  • But they're displayed inline.
  • - - {{< /example >}} + `} />
    - {{< example show_markup="false" >}} - {{< placeholder width="100%" height="250" class="bd-placeholder-img-lg img-fluid" text="Responsive image" >}} - {{< /example >}} + `} /> - {{< example show_markup="false" >}} - {{< placeholder width="200" height="200" class="img-thumbnail" title="A generic square placeholder image with a white border around it, making it resemble a photograph taken with an old instant camera" >}} - {{< /example >}} + `} />
    -
    - {{< example show_markup="false" >}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    #FirstLastHandle
    1MarkOtto@mdo
    2JacobThornton@fat
    3Larry the Bird@twitter
    - {{< /example >}} + + + + # + First + Last + Handle + + + + + 1 + Mark + Otto + @mdo + + + 2 + Jacob + Thornton + @fat + + + 3 + John + Doe + @social + + + `} /> - {{< example show_markup="false" >}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    #FirstLastHandle
    1MarkOtto@mdo
    2JacobThornton@fat
    3Larry the Bird@twitter
    - {{< /example >}} + + + + # + First + Last + Handle + + + + + 1 + Mark + Otto + @mdo + + + 2 + Jacob + Thornton + @fat + + + 3 + John + Doe + @social + + + `} /> - {{< example show_markup="false" >}} - - - - - - - - - - - - - - - {{< table.inline >}} - {{- range (index $.Site.Data "theme-colors") }} - - - - - - {{- end -}} - {{< /table.inline >}} - -
    ClassHeadingHeading
    DefaultCellCell
    {{ .name | title }}CellCell
    - {{< /example >}} + + + + Class + Heading + Heading + + + + + Default + Cell + Cell + `, + ...getData('theme-colors').map((themeColor) => ` + ${themeColor.title} + Cell + Cell + `), + ` + `]} /> - {{< example show_markup="false" >}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    #FirstLastHandle
    1MarkOtto@mdo
    2JacobThornton@fat
    3Larry the Bird@twitter
    - {{< /example >}} + + + + # + First + Last + Handle + + + + + 1 + Mark + Otto + @mdo + + + 2 + Jacob + Thornton + @fat + + + 3 + John + Doe + @social + + + `} />
    +
    - {{< example show_markup="false" >}} + - {{< placeholder width="400" height="300" class="figure-img img-fluid rounded" >}} +
    A caption for the above image.
    - - {{< /example >}} + `} />
    @@ -323,11 +309,11 @@ body_class: "bg-light"
    - {{< example show_markup="false" >}} +
    @@ -367,26 +353,25 @@ body_class: "bg-light"
    - - + +
    - - {{< /example >}} + `} />
    - {{< example show_markup="false" >}} +
    @@ -457,10 +441,9 @@ body_class: "bg-light"
    -
    - {{< /example >}} + `} /> - {{< example show_markup="false" >}} + @@ -475,17 +458,17 @@ body_class: "bg-light"
    - {{< /example >}} + `} />
    - {{< example show_markup="false" >}} + @ @@ -508,17 +491,17 @@ body_class: "bg-light" With textarea
    - {{< /example >}} + `} />

    Floating labels

    - }}">Documentation + Documentation
    - {{< example show_markup="false" >}} +
    @@ -529,17 +512,17 @@ body_class: "bg-light"
    - {{< /example >}} + `} />
    - {{< example show_markup="false" >}} +
    @@ -604,7 +587,7 @@ body_class: "bg-light"
    - {{< /example >}} + `} />
    @@ -615,87 +598,84 @@ body_class: "bg-light"
    - {{< example show_markup="false" >}} +
    -

    +

    -
    +
    This is the first item's accordion body. It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the .accordion-body, though the transition does limit overflow.
    -

    +

    -
    +
    This is the second item's accordion body. It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the .accordion-body, though the transition does limit overflow.
    -

    +

    -
    +
    This is the third item's accordion body. It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the .accordion-body, though the transition does limit overflow.
    - {{< /example >}} + `} />
    - {{< example show_markup="false" >}} - {{< alerts.inline >}} - {{- range (index $.Site.Data "theme-colors") }} - + `)} /> - {{< example show_markup="false" >}} +

    Well done!

    Aww yeah, you successfully read this important alert message. This example text is going to run a bit longer so that you can see how spacing within an alert works with this kind of content.


    Whenever you need to, be sure to use margin utilities to keep things nice and tidy.

    - {{< /example >}} + `} />
    - {{< example show_markup="false" >}} + Example heading New

    Example heading New

    Example heading New

    @@ -704,24 +684,21 @@ body_class: "bg-light"

    Example heading New

    Example heading New

    Example heading New

    - {{< /example >}} + `} /> - {{< example show_markup="false" >}} - {{< badge.inline >}} - {{- range (index $.Site.Data "theme-colors") }} - {{ .name | title }}{{- end -}} - {{< /badge.inline >}} - {{< /example >}} + ` + ${themeColor.title} + `)} />
    -
    - {{< example show_markup="false" >}} - {{< buttons.inline >}} - {{- range (index $.Site.Data "theme-colors") }} - - {{- end -}} - {{< /buttons.inline >}} + ` + + `), + ``]} /> - - {{< /example >}} + ` + + `)} /> - {{< example show_markup="false" >}} - {{< buttons.inline >}} - {{- range (index $.Site.Data "theme-colors") }} - - {{- end -}} - {{< /buttons.inline >}} - {{< /example >}} - - {{< example show_markup="false" >}} - - - - {{< /example >}} + Small button + + + `} />

    Button group

    - }}">Documentation + Documentation
    - {{< example show_markup="false" >}} +
    @@ -788,88 +756,88 @@ body_class: "bg-light"
    - {{< /example >}} + `} />
    - {{< example show_markup="false" >}} -
    -
    -
    - {{< placeholder width="100%" height="180" class="card-img-top" text="Image cap" >}} -
    -
    Card title
    -

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

    - Go somewhere -
    -
    -
    -
    -
    -
    - Featured -
    -
    -
    Card title
    -

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

    - Go somewhere -
    - -
    -
    -
    -
    -
    -
    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
    • -
    - -
    -
    -
    -
    -
    -
    - {{< placeholder width="100%" height="250" text="Image" >}} + +
    +
    + +
    +
    Card title
    +

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

    + Go somewhere
    -
    -
    -
    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

    +
    +
    +
    +
    +
    + Featured +
    +
    +
    Card title
    +

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

    + Go somewhere +
    + +
    +
    +
    +
    +
    +
    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
    • +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    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

    +
    -
    - {{< /example >}} + `} />
    - {{< example show_markup="false" >}} +
  • A disabled item
  • A second item
  • @@ -1108,9 +1075,9 @@ body_class: "bg-light"
  • A fourth item
  • And a fifth one
  • - {{< /example >}} + `} /> - {{< example show_markup="false" >}} +
  • An item
  • A second item
  • @@ -1118,28 +1085,26 @@ body_class: "bg-light"
  • A fourth item
  • And a fifth one
  • - {{< /example >}} + `} /> - {{< example show_markup="false" >}} + - A simple default list group item - {{< list.inline >}} - {{- range (index $.Site.Data "theme-colors") }} - A simple {{ .name }} list group item - {{- end -}} - {{< /list.inline >}} -
    - {{< /example >}} + A simple default list group item`, + ...getData('theme-colors').map((themeColor) => ` + A simple ${themeColor.name} list group item + `), + ` + `]} />
    - {{< example show_markup="false" >}} +
    • 1
    • @@ -1318,9 +1281,9 @@ body_class: "bg-light"
    • 3
    - {{< /example >}} + `} /> - {{< example show_markup="false" >}} +
    • @@ -1338,9 +1301,9 @@ body_class: "bg-light"
    - {{< /example >}} + `} /> - {{< example show_markup="false" >}} +
    • @@ -1356,21 +1319,21 @@ body_class: "bg-light"
    - {{< /example >}} + `} />
    - {{< example show_markup="false" >}} + Click to toggle popover - {{< /example >}} + `} /> - {{< example show_markup="false" >}} + Popover on top @@ -1383,153 +1346,149 @@ body_class: "bg-light" - {{< /example >}} + `} />
    - {{< example show_markup="false" >}} -
    -
    0%
    + +
    0%
    -
    -
    25%
    +
    +
    25%
    -
    -
    50%
    +
    +
    50%
    -
    -
    75%
    +
    +
    75%
    -
    -
    100%
    +
    +
    100%
    - {{< /example >}} + `} /> - {{< example show_markup="false" >}} -
    -
    -
    + +
    +
    +
    +
    +
    +
    - {{< /example >}} + `} />
    -
    - + + Navbar + +
    -

    First heading

    -

    This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

    -

    Second heading

    -

    This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

    -

    Third heading

    -

    This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

    -

    Fourth heading

    -

    This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

    -

    Fifth heading

    -

    This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

    -
    +

    First heading

    +

    This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

    +

    Second heading

    +

    This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

    +

    Third heading

    +

    This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

    +

    Fourth heading

    +

    This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

    +

    Fifth heading

    +

    This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

    + `} />
    - {{< example show_markup="false" >}} - {{< spinner.inline >}} - {{- range (index $.Site.Data "theme-colors") }} -
    + ` +
    Loading...
    - {{- end -}} - {{< /spinner.inline >}} - {{< /example >}} + `)} /> - {{< example show_markup="false" >}} - {{< spinner.inline >}} - {{- range (index $.Site.Data "theme-colors") }} -
    + ` +
    Loading...
    - {{- end -}} - {{< /spinner.inline >}} - {{< /example >}} + `)} />
    - {{< example show_markup="false" class="bg-dark p-5 align-items-center" >}} +
    - {{< placeholder width="20" height="20" background="#007aff" class="rounded me-2" text="false" title="false" >}} + Bootstrap - 11 mins ago + 11 mins ago
    Hello, world! This is a toast message.
    - {{< /example >}} + `} />
    - {{< example show_markup="false" class="tooltip-demo" >}} + Tooltip on top - {{< /example >}} + `} />
    - + + diff --git a/site/content/docs/5.2/examples/checkout-rtl/index.html b/site/src/assets/examples/checkout-rtl/index.astro similarity index 86% rename from site/content/docs/5.2/examples/checkout-rtl/index.html rename to site/src/assets/examples/checkout-rtl/index.astro index e0ca621..1b01935 100644 --- a/site/content/docs/5.2/examples/checkout-rtl/index.html +++ b/site/src/assets/examples/checkout-rtl/index.astro @@ -1,51 +1,50 @@ --- -layout: examples -title: مثال إتمام الشراء -direction: rtl -extra_css: - - "../checkout/form-validation.css" -extra_js: - - src: "../checkout/form-validation.js" -body_class: "bg-light" +import { getVersionedDocsPath } from '@libs/path' + +export const title = 'مثال إتمام الشراء' +export const direction = 'rtl' +export const extra_css = ['../checkout/checkout.css'] +export const extra_js = [{ src: '../checkout/checkout.js' }] +export const body_class = 'bg-body-tertiary' ---
    - -

    نموذج إتمام الشراء

    + +

    نموذج إتمام الشراء

    فيما يلي مثال على نموذج تم إنشاؤه بالكامل باستخدام عناصر تحكم النموذج في Bootstrap. لكل مجموعة نماذج مطلوبة حالة تحقق يمكن تشغيلها بمحاولة إرسال النموذج دون استكماله.

    - عربة التسوق + عربة التسوق 3

    • اسم المنتج
      - وصف مختصر + وصف مختصر
      - $12 + $12
    • المنتج الثاني
      - وصف مختصر + وصف مختصر
      - $8 + $8
    • البند الثالث
      - وصف مختصر + وصف مختصر
      - $5 + $5
    • -
    • +
    • رمز ترويجي
      EXAMPLECODE @@ -97,7 +96,7 @@ body_class: "bg-light"
      - +
      يرجى إدخال عنوان بريد إلكتروني صحيح لتصلكم تحديثات الشحن. @@ -113,7 +112,7 @@ body_class: "bg-light"
      - +
      @@ -183,7 +182,7 @@ body_class: "bg-light"
      - الاسم الكامل كما هو معروض على البطاقة + الاسم الكامل كما هو معروض على البطاقة
      الاسم على البطاقة مطلوب
      @@ -221,8 +220,8 @@ body_class: "bg-light"
    -
    -

    © {{< year >}}-2017 اسم الشركة

    +
    +

    © {new Date().getFullYear()}-2017 اسم الشركة

    • سياسة الخصوصية
    • اتفاقية الاستخدام
    • diff --git a/site/content/docs/5.2/examples/checkout/form-validation.css b/site/src/assets/examples/checkout/checkout.css similarity index 100% rename from site/content/docs/5.2/examples/checkout/form-validation.css rename to site/src/assets/examples/checkout/checkout.css diff --git a/site/content/docs/5.2/examples/checkout/form-validation.js b/site/src/assets/examples/checkout/checkout.js similarity index 100% rename from site/content/docs/5.2/examples/checkout/form-validation.js rename to site/src/assets/examples/checkout/checkout.js diff --git a/site/content/docs/5.2/examples/checkout/index.html b/site/src/assets/examples/checkout/index.astro similarity index 87% rename from site/content/docs/5.2/examples/checkout/index.html rename to site/src/assets/examples/checkout/index.astro index 4809dc4..029bc79 100644 --- a/site/content/docs/5.2/examples/checkout/index.html +++ b/site/src/assets/examples/checkout/index.astro @@ -1,18 +1,17 @@ --- -layout: examples -title: Checkout example -extra_css: - - "form-validation.css" -extra_js: - - src: "form-validation.js" -body_class: "bg-light" +import { getVersionedDocsPath } from '@libs/path' + +export const title = 'Checkout example' +export const extra_css = ['checkout.css'] +export const extra_js = [{ src: 'checkout.js' }] +export const body_class = 'bg-body-tertiary' ---
      - -

      Checkout form

      + +

      Checkout form

      Below is an example form built entirely with Bootstrap’s form controls. Each required form group has a validation state that can be triggered by attempting to submit the form without completing it.

      @@ -26,25 +25,25 @@ body_class: "bg-light"
    • Product name
      - Brief description + Brief description
      - $12 + $12
    • Second product
      - Brief description + Brief description
      - $8 + $8
    • Third item
      - Brief description + Brief description
      - $5 + $5
    • -
    • +
    • Promo code
      EXAMPLECODE @@ -96,7 +95,7 @@ body_class: "bg-light"
      - +
      Please enter a valid email address for shipping updates. @@ -112,7 +111,7 @@ body_class: "bg-light"
      - +
      @@ -182,7 +181,7 @@ body_class: "bg-light"
      - Full name as displayed on card + Full name as displayed on card
      Name on card is required
      @@ -221,8 +220,8 @@ body_class: "bg-light"
    • -
      -

      © 2017–{{< year >}} Company Name

      +
      +

      © 2017–{new Date().getFullYear()} Company Name

      • Privacy
      • Terms
      • diff --git a/site/content/docs/5.2/examples/cover/cover.css b/site/src/assets/examples/cover/cover.css similarity index 91% rename from site/content/docs/5.2/examples/cover/cover.css rename to site/src/assets/examples/cover/cover.css index 1b1c540..2e7aef8 100644 --- a/site/content/docs/5.2/examples/cover/cover.css +++ b/site/src/assets/examples/cover/cover.css @@ -4,9 +4,9 @@ /* Custom default button */ -.btn-secondary, -.btn-secondary:hover, -.btn-secondary:focus { +.btn-light, +.btn-light:hover, +.btn-light:focus { color: #333; text-shadow: none; /* Prevent inheritance from `body` */ } diff --git a/site/content/docs/5.2/examples/cover/index.html b/site/src/assets/examples/cover/index.astro similarity index 71% rename from site/content/docs/5.2/examples/cover/index.html rename to site/src/assets/examples/cover/index.astro index 34dc1c2..3af7315 100644 --- a/site/content/docs/5.2/examples/cover/index.html +++ b/site/src/assets/examples/cover/index.astro @@ -1,11 +1,8 @@ --- -layout: examples -title: Cover Template -extra_css: - - "cover.css" -html_class: "h-100" -body_class: "d-flex h-100 text-center text-bg-dark" -include_js: false +export const title = 'Cover Template' +export const extra_css = ['cover.css'] +export const html_class = 'h-100' +export const body_class = 'd-flex h-100 text-center text-bg-dark' ---
        @@ -24,11 +21,11 @@ include_js: false

        Cover your page.

        Cover is a one-page template for building simple and beautiful home pages. Download, edit the text, and add your own fullscreen background photo to make it your own.

        - Learn more + Learn more

        diff --git a/site/content/docs/5.2/examples/dashboard-rtl/dashboard.js b/site/src/assets/examples/dashboard-rtl/dashboard.js similarity index 68% rename from site/content/docs/5.2/examples/dashboard-rtl/dashboard.js rename to site/src/assets/examples/dashboard-rtl/dashboard.js index 96e0895..590eb3e 100644 --- a/site/content/docs/5.2/examples/dashboard-rtl/dashboard.js +++ b/site/src/assets/examples/dashboard-rtl/dashboard.js @@ -1,14 +1,11 @@ -/* globals Chart:false, feather:false */ +/* globals Chart:false */ (() => { 'use strict' - feather.replace({ 'aria-hidden': 'true' }) - // Graphs const ctx = document.getElementById('myChart') - // eslint-disable-next-line no-unused-vars - const myChart = new Chart(ctx, { + new Chart(ctx, { type: 'line', data: { labels: [ @@ -38,15 +35,13 @@ }] }, options: { - scales: { - yAxes: [{ - ticks: { - beginAtZero: false - } - }] - }, - legend: { - display: false + plugins: { + legend: { + display: false + }, + tooltip: { + boxPadding: 3 + } } } }) diff --git a/site/src/assets/examples/dashboard-rtl/index.astro b/site/src/assets/examples/dashboard-rtl/index.astro new file mode 100644 index 0000000..c5758e9 --- /dev/null +++ b/site/src/assets/examples/dashboard-rtl/index.astro @@ -0,0 +1,330 @@ +--- +export const title = 'قالب لوحة القيادة' +export const direction = 'rtl' +export const extra_css = ['../dashboard/dashboard.rtl.css'] +export const extra_js = [ + { src: 'https://cdn.jsdelivr.net/npm/chart.js@4.3.2/dist/chart.umd.js', integrity: 'sha384-eI7PSr3L1XLISH8JdDII5YN/njoSsxfbrkCTnJrzXt+ENP5MOVBxD+l6sEG4zoLp'}, + { src: 'dashboard.js'} +] +--- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +
        + + +
        +
        +

        لوحة القيادة

        +
        +
        + + +
        + +
        +
        + + + +

        عنوان القسم

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        #عنوانعنوانعنوانعنوان
        1,001بياناتعشوائيةتثريالجدول
        1,002تثريمبهةتصميمتنسيق
        1,003عشوائيةغنيةقيمةمفيدة
        1,003معلوماتتثريتوضيحيةعشوائية
        1,004الجدولبياناتتنسيققيمة
        1,005قيمةمبهةالجدولتثري
        1,006قيمةتوضيحيةغنيةعشوائية
        1,007تثريمفيدةمعلوماتمبهة
        1,008بياناتعشوائيةتثريالجدول
        1,009تثريمبهةتصميمتنسيق
        1,010عشوائيةغنيةقيمةمفيدة
        1,011معلوماتتثريتوضيحيةعشوائية
        1,012الجدولتثريتنسيققيمة
        1,013قيمةمبهةالجدولتصميم
        1,014قيمةتوضيحيةغنيةعشوائية
        1,015بياناتمفيدةمعلوماتالجدول
        +
        +
        +
        +
        diff --git a/site/src/assets/examples/dashboard/dashboard.css b/site/src/assets/examples/dashboard/dashboard.css new file mode 100644 index 0000000..154940c --- /dev/null +++ b/site/src/assets/examples/dashboard/dashboard.css @@ -0,0 +1,48 @@ +.bi { + display: inline-block; + width: 1rem; + height: 1rem; +} + +/* + * Sidebar + */ + +@media (min-width: 768px) { + .sidebar .offcanvas-lg { + position: -webkit-sticky; + position: sticky; + top: 48px; + } + .navbar-search { + display: block; + } +} + +.sidebar .nav-link { + font-size: .875rem; + font-weight: 500; +} + +.sidebar .nav-link.active { + color: #2470dc; +} + +.sidebar-heading { + font-size: .75rem; +} + +/* + * Navbar + */ + +.navbar-brand { + padding-top: .75rem; + padding-bottom: .75rem; + background-color: rgba(0, 0, 0, .25); + box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25); +} + +.navbar .form-control { + padding: .75rem 1rem; +} diff --git a/site/content/docs/5.2/examples/dashboard/dashboard.js b/site/src/assets/examples/dashboard/dashboard.js similarity index 67% rename from site/content/docs/5.2/examples/dashboard/dashboard.js rename to site/src/assets/examples/dashboard/dashboard.js index e137975..0bae987 100644 --- a/site/content/docs/5.2/examples/dashboard/dashboard.js +++ b/site/src/assets/examples/dashboard/dashboard.js @@ -1,14 +1,11 @@ -/* globals Chart:false, feather:false */ +/* globals Chart:false */ (() => { 'use strict' - feather.replace({ 'aria-hidden': 'true' }) - // Graphs const ctx = document.getElementById('myChart') - // eslint-disable-next-line no-unused-vars - const myChart = new Chart(ctx, { + new Chart(ctx, { type: 'line', data: { labels: [ @@ -38,15 +35,13 @@ }] }, options: { - scales: { - yAxes: [{ - ticks: { - beginAtZero: false - } - }] - }, - legend: { - display: false + plugins: { + legend: { + display: false + }, + tooltip: { + boxPadding: 3 + } } } }) diff --git a/site/src/assets/examples/dashboard/dashboard.rtl.css b/site/src/assets/examples/dashboard/dashboard.rtl.css new file mode 100644 index 0000000..5c8a7e2 --- /dev/null +++ b/site/src/assets/examples/dashboard/dashboard.rtl.css @@ -0,0 +1,48 @@ +.bi { + display: inline-block; + width: 1rem; + height: 1rem; +} + +/* + * Sidebar + */ + +@media (min-width: 768px) { + .sidebar .offcanvas-lg { + position: -webkit-sticky; + position: sticky; + top: 48px; + } + .navbar-search { + display: block; + } +} + +.sidebar .nav-link { + font-size: .875rem; + font-weight: 500; +} + +.sidebar .nav-link.active { + color: #2470dc; +} + +.sidebar-heading { + font-size: .75rem; +} + +/* + * Navbar + */ + +.navbar-brand { + padding-top: .75rem; + padding-bottom: .75rem; + background-color: rgba(0, 0, 0, .25); + box-shadow: inset 1px 0 0 rgba(0, 0, 0, .25); +} + +.navbar .form-control { + padding: .75rem 1rem; +} diff --git a/site/src/assets/examples/dashboard/index.astro b/site/src/assets/examples/dashboard/index.astro new file mode 100644 index 0000000..4d33c7f --- /dev/null +++ b/site/src/assets/examples/dashboard/index.astro @@ -0,0 +1,329 @@ +--- +export const title = 'Dashboard Template' +export const extra_css = ['dashboard.css'] +export const extra_js = [ + { src: 'https://cdn.jsdelivr.net/npm/chart.js@4.3.2/dist/chart.umd.js', integrity: 'sha384-eI7PSr3L1XLISH8JdDII5YN/njoSsxfbrkCTnJrzXt+ENP5MOVBxD+l6sEG4zoLp'}, + { src: 'dashboard.js'} +] +--- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +
        + + +
        +
        +

        Dashboard

        +
        +
        + + +
        + +
        +
        + + + +

        Section title

        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        #HeaderHeaderHeaderHeader
        1,001randomdataplaceholdertext
        1,002placeholderirrelevantvisuallayout
        1,003datarichdashboardtabular
        1,003informationplaceholderillustrativedata
        1,004textrandomlayoutdashboard
        1,005dashboardirrelevanttextplaceholder
        1,006dashboardillustrativerichdata
        1,007placeholdertabularinformationirrelevant
        1,008randomdataplaceholdertext
        1,009placeholderirrelevantvisuallayout
        1,010datarichdashboardtabular
        1,011informationplaceholderillustrativedata
        1,012textplaceholderlayoutdashboard
        1,013dashboardirrelevanttextvisual
        1,014dashboardillustrativerichdata
        1,015randomtabularinformationtext
        +
        +
        +
        +
        diff --git a/site/content/docs/5.2/examples/dropdowns/dropdowns.css b/site/src/assets/examples/dropdowns/dropdowns.css similarity index 73% rename from site/content/docs/5.2/examples/dropdowns/dropdowns.css rename to site/src/assets/examples/dropdowns/dropdowns.css index 556f310..f633e2c 100644 --- a/site/content/docs/5.2/examples/dropdowns/dropdowns.css +++ b/site/src/assets/examples/dropdowns/dropdowns.css @@ -1,7 +1,3 @@ -.dropdown-menu { - margin: 4rem auto; -} - .dropdown-item-danger { color: var(--bs-red); } @@ -15,12 +11,13 @@ } .btn-hover-light { - background-color: var(--bs-white); + color: var(--bs-body-color); + background-color: var(--bs-body-bg); } .btn-hover-light:hover, .btn-hover-light:focus { - color: var(--bs-blue); - background-color: var(--bs-light); + color: var(--bs-link-hover-color); + background-color: var(--bs-tertiary-bg); } .cal-month, @@ -46,22 +43,17 @@ } .cal-btn:not([disabled]) { font-weight: 500; + color: var(--bs-emphasis-color); } .cal-btn:hover, .cal-btn:focus { - background-color: rgba(0, 0, 0, .05); + background-color: var(--bs-secondary-bg); } .cal-btn[disabled] { border: 0; opacity: .5; } -.form-control-dark { - background-color: rgba(255, 255, 255, .05); - border-color: rgba(255, 255, 255, .15); -} - - .w-220px { width: 220px; } @@ -74,6 +66,6 @@ width: 340px; } -.w-600px { - width: 600px; +.opacity-10 { + opacity: .1; } diff --git a/site/src/assets/examples/dropdowns/index.astro b/site/src/assets/examples/dropdowns/index.astro new file mode 100644 index 0000000..812109e --- /dev/null +++ b/site/src/assets/examples/dropdowns/index.astro @@ -0,0 +1,459 @@ +--- +export const title = 'Dropdowns' +export const extra_css = ['dropdowns.css'] +--- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + + + +
        + + + +
        + +
        + + + +
        + +
        + + diff --git a/site/content/docs/5.2/examples/features/features.css b/site/src/assets/examples/features/features.css similarity index 73% rename from site/content/docs/5.2/examples/features/features.css rename to site/src/assets/examples/features/features.css index b5d658f..debc263 100644 --- a/site/content/docs/5.2/examples/features/features.css +++ b/site/src/assets/examples/features/features.css @@ -4,16 +4,6 @@ border-radius: .75rem; } -.icon-link > .bi { - margin-top: .125rem; - margin-left: .125rem; - fill: currentcolor; - transition: transform .25s ease-in-out; -} -.icon-link:hover > .bi { - transform: translate(.25rem); -} - .icon-square { width: 3rem; height: 3rem; diff --git a/site/content/docs/5.2/examples/features/index.html b/site/src/assets/examples/features/index.astro similarity index 66% rename from site/content/docs/5.2/examples/features/index.html rename to site/src/assets/examples/features/index.astro index c134a0a..7a3a764 100644 --- a/site/content/docs/5.2/examples/features/index.html +++ b/site/src/assets/examples/features/index.astro @@ -1,12 +1,9 @@ --- -layout: examples -title: Features -extra_css: - - "features.css" -body_class: "" +export const title = 'Features' +export const extra_css = ['features.css'] --- - + Bootstrap @@ -25,9 +22,6 @@ body_class: "" - - - @@ -35,9 +29,6 @@ body_class: "" - - - @@ -72,35 +63,35 @@ body_class: ""
        - +
        -

        Featured title

        +

        Featured title

        Paragraph of text beneath the heading to explain the heading. We'll add onto it with another sentence and probably just keep going until we run out of words.

        - + Call to action - +
        - +
        -

        Featured title

        +

        Featured title

        Paragraph of text beneath the heading to explain the heading. We'll add onto it with another sentence and probably just keep going until we run out of words.

        - + Call to action - +
        - +
        -

        Featured title

        +

        Featured title

        Paragraph of text beneath the heading to explain the heading. We'll add onto it with another sentence and probably just keep going until we run out of words.

        - + Call to action - +
        @@ -112,11 +103,11 @@ body_class: ""

        Hanging icons

      @@ -186,11 +177,11 @@ body_class: "" Bootstrap
    • - + Pakistan
    • - + 4d
    @@ -207,11 +198,11 @@ body_class: "" Bootstrap
  • - + California
  • - + 5d
  • @@ -228,58 +219,58 @@ body_class: ""
    - +
    -

    Featured title

    +

    Featured title

    Paragraph of text beneath the heading to explain the heading.

    - +
    -

    Featured title

    +

    Featured title

    Paragraph of text beneath the heading to explain the heading.

    - +
    -

    Featured title

    +

    Featured title

    Paragraph of text beneath the heading to explain the heading.

    - +
    -

    Featured title

    +

    Featured title

    Paragraph of text beneath the heading to explain the heading.

    - +
    -

    Featured title

    +

    Featured title

    Paragraph of text beneath the heading to explain the heading.

    - +
    -

    Featured title

    +

    Featured title

    Paragraph of text beneath the heading to explain the heading.

    - +
    -

    Featured title

    +

    Featured title

    Paragraph of text beneath the heading to explain the heading.

    - +
    -

    Featured title

    +

    Featured title

    Paragraph of text beneath the heading to explain the heading.

    @@ -292,54 +283,53 @@ body_class: ""

    Features with title

    -
    -

    Left-aligned title explaining these awesome features

    -

    Paragraph of text beneath the heading to explain the heading. We'll add onto it with another sentence and probably just keep going until we run out of words.

    +
    -
    -
    -
    - - - -
    -

    Featured title

    -

    Paragraph of text beneath the heading to explain the heading.

    -
    -
    -
    - - - +
    +
    +
    +
    + +
    +

    Featured title

    +

    Paragraph of text beneath the heading to explain the heading.

    -

    Featured title

    -

    Paragraph of text beneath the heading to explain the heading.

    -
    -
    -
    - - - +
    +
    + +
    +

    Featured title

    +

    Paragraph of text beneath the heading to explain the heading.

    -

    Featured title

    -

    Paragraph of text beneath the heading to explain the heading.

    -
    -
    -
    - - - +
    +
    + +
    +

    Featured title

    +

    Paragraph of text beneath the heading to explain the heading.

    +
    + +
    +
    + +
    +

    Featured title

    +

    Paragraph of text beneath the heading to explain the heading.

    -

    Featured title

    -

    Paragraph of text beneath the heading to explain the heading.

    diff --git a/site/content/docs/5.2/examples/features/unsplash-photo-1.jpg b/site/src/assets/examples/features/unsplash-photo-1.jpg similarity index 99% rename from site/content/docs/5.2/examples/features/unsplash-photo-1.jpg rename to site/src/assets/examples/features/unsplash-photo-1.jpg index ed2e36a..283acd0 100644 Binary files a/site/content/docs/5.2/examples/features/unsplash-photo-1.jpg and b/site/src/assets/examples/features/unsplash-photo-1.jpg differ diff --git a/site/content/docs/5.2/examples/features/unsplash-photo-2.jpg b/site/src/assets/examples/features/unsplash-photo-2.jpg similarity index 99% rename from site/content/docs/5.2/examples/features/unsplash-photo-2.jpg rename to site/src/assets/examples/features/unsplash-photo-2.jpg index b66864a..81eae64 100644 Binary files a/site/content/docs/5.2/examples/features/unsplash-photo-2.jpg and b/site/src/assets/examples/features/unsplash-photo-2.jpg differ diff --git a/site/content/docs/5.2/examples/features/unsplash-photo-3.jpg b/site/src/assets/examples/features/unsplash-photo-3.jpg similarity index 99% rename from site/content/docs/5.2/examples/features/unsplash-photo-3.jpg rename to site/src/assets/examples/features/unsplash-photo-3.jpg index c411b17..0f401d1 100644 Binary files a/site/content/docs/5.2/examples/features/unsplash-photo-3.jpg and b/site/src/assets/examples/features/unsplash-photo-3.jpg differ diff --git a/site/content/docs/5.2/examples/footers/index.html b/site/src/assets/examples/footers/index.astro similarity index 68% rename from site/content/docs/5.2/examples/footers/index.html rename to site/src/assets/examples/footers/index.astro index 827c242..9aaaa9f 100644 --- a/site/content/docs/5.2/examples/footers/index.html +++ b/site/src/assets/examples/footers/index.astro @@ -1,10 +1,8 @@ --- -layout: examples -title: Footers -body_class: "" +export const title = 'Footers' --- - + Bootstrap @@ -15,25 +13,22 @@ body_class: "" - - -
    @@ -43,16 +38,15 @@ body_class: ""
    - - + + - © {{< year >}} Company, Inc + © {new Date().getFullYear()} Company, Inc
    @@ -62,13 +56,13 @@ body_class: ""
    @@ -77,10 +71,10 @@ body_class: ""
    @@ -131,33 +125,33 @@ body_class: ""
    Section
    Section
    Section
    @@ -167,7 +161,7 @@ body_class: ""

    Monthly digest of what's new and exciting from us.

    - +
    @@ -175,11 +169,10 @@ body_class: ""
    -

    © {{< year >}} Company, Inc. All rights reserved.

    +

    © {new Date().getFullYear()} Company, Inc. All rights reserved.

      -
    • -
    • -
    • +
    • +
    diff --git a/site/src/assets/examples/grid/grid.css b/site/src/assets/examples/grid/grid.css new file mode 100644 index 0000000..cbc7c31 --- /dev/null +++ b/site/src/assets/examples/grid/grid.css @@ -0,0 +1,13 @@ +.themed-grid-col { + padding-top: .75rem; + padding-bottom: .75rem; + background-color: rgba(112.520718, 44.062154, 249.437846, .15); + border: 1px solid rgba(112.520718, 44.062154, 249.437846, .3); +} + +.themed-container { + padding: .75rem; + margin-bottom: 1.5rem; + background-color: rgba(112.520718, 44.062154, 249.437846, .15); + border: 1px solid rgba(112.520718, 44.062154, 249.437846, .3); +} diff --git a/site/content/docs/5.2/examples/grid/index.html b/site/src/assets/examples/grid/index.astro similarity index 98% rename from site/content/docs/5.2/examples/grid/index.html rename to site/src/assets/examples/grid/index.astro index 7c4f1d3..2c01d8d 100644 --- a/site/content/docs/5.2/examples/grid/index.html +++ b/site/src/assets/examples/grid/index.astro @@ -1,10 +1,7 @@ --- -layout: examples -title: Grid Template -extra_css: - - "grid.css" -body_class: "py-4" -include_js: false +export const title = 'Grid Template' +export const extra_css = ['grid.css'] +export const body_class = 'py-4' ---
    diff --git a/site/content/docs/5.2/examples/headers/headers.css b/site/src/assets/examples/headers/headers.css similarity index 87% rename from site/content/docs/5.2/examples/headers/headers.css rename to site/src/assets/examples/headers/headers.css index 8230c9a..f887573 100644 --- a/site/content/docs/5.2/examples/headers/headers.css +++ b/site/src/assets/examples/headers/headers.css @@ -10,6 +10,6 @@ font-size: 85%; } -.dropdown-toggle { +.dropdown-toggle:not(:focus) { outline: 0; } diff --git a/site/content/docs/5.2/examples/headers/index.html b/site/src/assets/examples/headers/index.astro similarity index 80% rename from site/content/docs/5.2/examples/headers/index.html rename to site/src/assets/examples/headers/index.astro index b8fc8b2..a233ae9 100644 --- a/site/content/docs/5.2/examples/headers/index.html +++ b/site/src/assets/examples/headers/index.astro @@ -1,12 +1,9 @@ --- -layout: examples -title: Headers -extra_css: - - "headers.css" -body_class: "" +export const title = 'Headers' +export const extra_css = ['headers.css'] --- - + Bootstrap @@ -35,8 +32,8 @@ body_class: ""
    - - + + Simple header @@ -68,16 +65,18 @@ body_class: ""
    - - - +
    + + + +
    @@ -121,15 +120,15 @@ body_class: ""
    - + + diff --git a/site/src/components/icons/BootstrapWhiteFillIcon.astro b/site/src/components/icons/BootstrapWhiteFillIcon.astro new file mode 100644 index 0000000..ef40e6a --- /dev/null +++ b/site/src/components/icons/BootstrapWhiteFillIcon.astro @@ -0,0 +1,18 @@ +--- +import type { SvgIconProps } from '@libs/icon' + +type Props = SvgIconProps + +const { class: className, height, width } = Astro.props +--- + + + Bootstrap + + + diff --git a/site/src/components/icons/CircleSquareIcon.astro b/site/src/components/icons/CircleSquareIcon.astro new file mode 100644 index 0000000..d718950 --- /dev/null +++ b/site/src/components/icons/CircleSquareIcon.astro @@ -0,0 +1,23 @@ +--- +import type { SvgIconProps } from '@libs/icon' + +type Props = SvgIconProps + +const { class: className, height, width } = Astro.props +--- + + diff --git a/site/src/components/icons/DropletFillIcon.astro b/site/src/components/icons/DropletFillIcon.astro new file mode 100644 index 0000000..d1fe5b5 --- /dev/null +++ b/site/src/components/icons/DropletFillIcon.astro @@ -0,0 +1,24 @@ +--- +import type { SvgIconProps } from '@libs/icon' + +type Props = SvgIconProps + +const { class: className, height, width } = Astro.props +--- + + diff --git a/site/src/components/icons/GitHubIcon.astro b/site/src/components/icons/GitHubIcon.astro new file mode 100644 index 0000000..faa0143 --- /dev/null +++ b/site/src/components/icons/GitHubIcon.astro @@ -0,0 +1,24 @@ +--- +import type { SvgIconProps } from '@libs/icon' + +type Props = SvgIconProps + +const { class: className, height, width } = Astro.props +--- + + + GitHub + + + diff --git a/site/src/components/icons/HamburgerIcon.astro b/site/src/components/icons/HamburgerIcon.astro new file mode 100644 index 0000000..8ff4730 --- /dev/null +++ b/site/src/components/icons/HamburgerIcon.astro @@ -0,0 +1,23 @@ +--- +import type { SvgIconProps } from '@libs/icon' + +type Props = SvgIconProps + +const { class: className, height, width } = Astro.props +--- + + diff --git a/site/src/components/icons/OpenCollectiveIcon.astro b/site/src/components/icons/OpenCollectiveIcon.astro new file mode 100644 index 0000000..fc50164 --- /dev/null +++ b/site/src/components/icons/OpenCollectiveIcon.astro @@ -0,0 +1,26 @@ +--- +import type { SvgIconProps } from '@libs/icon' + +type Props = SvgIconProps + +const { class: className, height, width } = Astro.props +--- + + + Open Collective + + + + diff --git a/site/src/components/icons/Symbols.astro b/site/src/components/icons/Symbols.astro new file mode 100644 index 0000000..44d3e73 --- /dev/null +++ b/site/src/components/icons/Symbols.astro @@ -0,0 +1,148 @@ +--- +--- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/site/src/components/icons/XIcon.astro b/site/src/components/icons/XIcon.astro new file mode 100644 index 0000000..ea0f4bd --- /dev/null +++ b/site/src/components/icons/XIcon.astro @@ -0,0 +1,23 @@ +--- +import type { SvgIconProps } from '@libs/icon' + +type Props = SvgIconProps + +const { class: className, height, width } = Astro.props +--- + + + X + + + diff --git a/site/src/components/shortcodes/AddedIn.astro b/site/src/components/shortcodes/AddedIn.astro new file mode 100644 index 0000000..d9a26ce --- /dev/null +++ b/site/src/components/shortcodes/AddedIn.astro @@ -0,0 +1,16 @@ +--- +/* + * Outputs badge to identify the first version something was added + */ + +interface Props { + version: string +} + +const { version } = Astro.props +--- + +Added in v{version} diff --git a/site/src/components/shortcodes/BsTable.astro b/site/src/components/shortcodes/BsTable.astro new file mode 100644 index 0000000..df80455 --- /dev/null +++ b/site/src/components/shortcodes/BsTable.astro @@ -0,0 +1,16 @@ +--- +interface Props { + /** + * The CSS class to apply to the table. + * Note that the prop is not used in this component, but in a rehype plugin applying the classes to the table element + * directly on the HTML AST (HAST) generated by Astro. + * @default "table" + * @see src/libs/rehype.ts + */ + class?: string +} +--- + +
    + +
    diff --git a/site/src/components/shortcodes/Callout.astro b/site/src/components/shortcodes/Callout.astro new file mode 100644 index 0000000..11243c8 --- /dev/null +++ b/site/src/components/shortcodes/Callout.astro @@ -0,0 +1,44 @@ +--- +import { getCalloutByName } from '@libs/content' +import type { MarkdownInstance } from 'astro' + +interface Props { + /** + * The name of an existing callout to display located in `src/content/callouts`. + * This will override any content passed in via the default slot. + */ + name?: + | 'danger-async-methods' + | 'info-mediaqueries-breakpoints' + | 'info-npm-starter' + | 'info-prefersreducedmotion' + | 'info-sanitizer' + | 'warning-color-assistive-technologies' + | 'warning-data-bs-title-vs-title' + | 'warning-input-support' + /** + * The type of callout to display. One of `info`, `danger`, or `warning`. + * @default 'info' + */ + type?: 'danger' | 'info' | 'warning' +} + +const { name, type = 'info' } = Astro.props + +let Content: MarkdownInstance<{}>['Content'] | undefined + +if (name) { + const callout = await getCalloutByName(name) + + if (!callout) { + throw new Error(`Could not find callout with name '${name}'.`) + } + + const namedCallout = await callout.render() + Content = namedCallout.Content +} +--- + +
    + {Content ? : } +
    diff --git a/site/src/components/shortcodes/CalloutDeprecatedDarkVariants.astro b/site/src/components/shortcodes/CalloutDeprecatedDarkVariants.astro new file mode 100644 index 0000000..4033900 --- /dev/null +++ b/site/src/components/shortcodes/CalloutDeprecatedDarkVariants.astro @@ -0,0 +1,19 @@ +--- +/* + * Outputs message about dark mode component variants being deprecated in v5.3. + */ + +interface Props { + component: string +} + +const { component } = Astro.props +--- + +
    +

    + Heads up! Dark variants for components were deprecated in v5.3.0 with the introduction of color modes. + Instead of adding .{component}-dark, set data-bs-theme="dark" on the root element, a parent + wrapper, or the component itself. +

    +
    diff --git a/site/src/components/shortcodes/Code.astro b/site/src/components/shortcodes/Code.astro new file mode 100644 index 0000000..ba2c611 --- /dev/null +++ b/site/src/components/shortcodes/Code.astro @@ -0,0 +1,165 @@ +--- +import fs from 'node:fs' +import path from 'node:path' +import { Prism } from '@astrojs/prism' + +interface Props { + /** + * The CSS class(es) to be added to the `pre` HTML element when rendering code blocks in Markdown. + * Note that this prop is not used when the component is invoked directly. + */ + class?: string + /** + * The code to highlight. + * If an array is passed, elements will be joined with a new line. + */ + code?: string | string[] + /** + * The CSS class(es) to be added to the `div` wrapper HTML element. + */ + containerClass?: string + /** + * The language to use for highlighting. + * @see https://prismjs.com/#supported-languages + */ + lang?: string + /** + * If the `filePath` prop is defined, this prop can be used to specify a regex containing a match group to extract + * only a part of the file. + */ + fileMatch?: string + /** + * A path to the file containing the code to highlight relative to the root of the repository. + * This takes precedence over the `code` prop. + */ + filePath?: string + /** + * Defines if the `` component is nested inside an `` component or not. + * @default false + */ + nestedInExample?: boolean +} + +const { class: className, code, containerClass, fileMatch, filePath, lang, nestedInExample = false } = Astro.props + +let codeToDisplay = filePath + ? fs.readFileSync(path.join(process.cwd(), filePath), 'utf8') + : Array.isArray(code) + ? code.join('\n') + : code + +if (filePath && fileMatch && codeToDisplay) { + const match = codeToDisplay.match(new RegExp(fileMatch)) + + if (!match || !match[0]) { + throw new Error(`The file at ${filePath} does not contains a match for the regex '${fileMatch}'.`) + } + + codeToDisplay = match[0] +} +--- + + + +
    + { + nestedInExample + ? (<>) + : Astro.slots.has('pre') + ? ( + + ) + : ( +
    + +
    + ) + } +
    + { + codeToDisplay && lang ? ( + + ) : ( + /* prettier-ignore */
    + ) + } +
    +
    diff --git a/site/src/components/shortcodes/DeprecatedIn.astro b/site/src/components/shortcodes/DeprecatedIn.astro new file mode 100644 index 0000000..50ba42b --- /dev/null +++ b/site/src/components/shortcodes/DeprecatedIn.astro @@ -0,0 +1,17 @@ +--- +/* + * Outputs badge to identify the version something was deprecated + */ + +interface Props { + version: string +} + +const { version } = Astro.props +--- + + + Deprecated in v{version} + diff --git a/site/src/components/shortcodes/Example.astro b/site/src/components/shortcodes/Example.astro new file mode 100644 index 0000000..044e1af --- /dev/null +++ b/site/src/components/shortcodes/Example.astro @@ -0,0 +1,103 @@ +--- +import { replacePlaceholdersInHtml } from '@libs/placeholder' +import Code from '@components/shortcodes/Code.astro' + +interface Props { + /** + * Defines if extra JS snippet should be added to StackBlitz or not. + * @default false + */ + addStackblitzJs?: boolean + /** + * The example code. + * If an array is passed, elements will be joined with a new line. + */ + code: string | string[] + /** + * The CSS class(es) to be added to the preview wrapping `div` element. + */ + class?: string + /** + * The preview wrapping `div` element ID. + */ + id?: string + /** + * Language used to display the code. + * @default 'html' + */ + lang?: string + /** + * Defines if the markup should be visible or not. + * @default true + */ + showMarkup?: boolean + /** + * Defines if the preview should be visible or not. + * @default true + */ + showPreview?: boolean +} + +const { + addStackblitzJs = false, + code, + class: className, + id, + lang = 'html', + showMarkup = true, + showPreview = true +} = Astro.props + +let markup = Array.isArray(code) ? code.join('\n') : code +markup = replacePlaceholdersInHtml(markup) + +const simplifiedMarkup = markup + .replace( + //g, + (match, classes) => `...` + ) + .replace( + //g, + (match, classes) => `...` + ) +--- + +
    + { + showPreview && ( +
    + +
    + ) + } + + { + showMarkup && ( + <> + {showPreview && ( +
    + {lang} +
    + + +
    +
    + )} + + + ) + } +
    diff --git a/site/src/components/shortcodes/GuideFooter.mdx b/site/src/components/shortcodes/GuideFooter.mdx new file mode 100644 index 0000000..426a71f --- /dev/null +++ b/site/src/components/shortcodes/GuideFooter.mdx @@ -0,0 +1,3 @@ +
    + +_See something wrong or out of date here? Please [open an issue on GitHub]([[config:repo]]/issues/new/choose). Need help troubleshooting? [Search or start a discussion]([[config:repo]]/discussions) on GitHub._ diff --git a/site/layouts/partials/js-data-attributes.md b/site/src/components/shortcodes/JsDataAttributes.mdx similarity index 66% rename from site/layouts/partials/js-data-attributes.md rename to site/src/components/shortcodes/JsDataAttributes.mdx index e99ff71..b7c6c0a 100644 --- a/site/layouts/partials/js-data-attributes.md +++ b/site/src/components/shortcodes/JsDataAttributes.mdx @@ -1,3 +1,5 @@ -As options can be passed via data attributes or JavaScript, you can append an option name to `data-bs-`, as in `data-bs-animation="{value}"`. Make sure to change the case type of the option name from "_camelCase_" to "_kebab-case_" when passing the options via data attributes. For example, use `data-bs-custom-class="beautifier"` instead of `data-bs-customClass="beautifier"`. +As options can be passed via data attributes or JavaScript, you can append an option name to `data-bs-`, as in `data-bs-animation="{value}"`. Make sure to change the case type of the option name from “_camelCase_” to “_kebab-case_” when passing the options via data attributes. For example, use `data-bs-custom-class="beautifier"` instead of `data-bs-customClass="beautifier"`. As of Bootstrap 5.2.0, all components support an **experimental** reserved data attribute `data-bs-config` that can house simple component configuration as a JSON string. When an element has `data-bs-config='{"delay":0, "title":123}'` and `data-bs-title="456"` attributes, the final `title` value will be `456` and the separate data attributes will override values given on `data-bs-config`. In addition, existing data attributes are able to house JSON values like `data-bs-delay='{"show":0,"hide":150}'`. + +The final configuration object is the merged result of `data-bs-config`, `data-bs-`, and `js object` where the latest given key-value overrides the others. diff --git a/site/src/components/shortcodes/JsDismiss.astro b/site/src/components/shortcodes/JsDismiss.astro new file mode 100644 index 0000000..d1da8fc --- /dev/null +++ b/site/src/components/shortcodes/JsDismiss.astro @@ -0,0 +1,29 @@ +--- +import Code from '@shortcodes/Code.astro' + +interface Props { + name: string +} + +const { name } = Astro.props +--- + +

    + Dismissal can be achieved with the data-bs-dismiss attribute on a button within the {name} as demonstrated below: +

    + +`} + lang="html" +/> + +

    + or on a button outside the {name} using the additional data-bs-target as demonstrated below: +

    + +`} + lang="html" +/> diff --git a/site/src/components/shortcodes/JsDocs.astro b/site/src/components/shortcodes/JsDocs.astro new file mode 100644 index 0000000..4430f21 --- /dev/null +++ b/site/src/components/shortcodes/JsDocs.astro @@ -0,0 +1,69 @@ +--- +import fs from 'node:fs' +import { getConfig } from '@libs/config' +import Code from '@shortcodes/Code.astro' + +// Prints everything between `// js-docs-start "name"` and `// js-docs-end "name"` +// comments in the docs. + +interface Props { + /** + * Reference name used to find the content to display within the content of the `file` prop. + */ + name: string + /** + * File path that contains the content to display relative to the root of the repository. + */ + file: string +} + +const { name, file } = Astro.props + +if (!name || !file) { + throw new Error( + `Missing required parameter(s) for the '' component, expected both 'name' and 'file' but got 'name: ${name}' and 'file: ${file}'.` + ) +} + +let content: string + +try { + const fileContent = fs.readFileSync(file, 'utf8') + + const matches = fileContent.match(new RegExp(`\/\/ js-docs-start ${name}\n((?:.|\n)*)\/\/ js-docs-end ${name}`, 'm')) + + if (!matches || !matches[1]) { + throw new Error( + `Failed to find the content named '${name}', make sure that '// js-docs-start ${name}' and '// js-docs-end ${name}' are defined.` + ) + } + + content = matches[1] + + // Fix the indentation by removing extra spaces at the beginning of each line + const lines = content.split('\n') + const spaceCounts = lines.filter((line) => line.trim().length > 0).map((line) => line.match(/^ */)[0].length) + const minSpaces = spaceCounts.length ? Math.min(...spaceCounts) : 0 + content = lines.map((line) => line.slice(minSpaces)).join('\n') +} catch (error) { + throw new Error(`Failed to find the content to render in the '' component at '${file}'.`, { + cause: error + }) +} +--- + + +
    + + {file} + +
    + +
    +
    +
    diff --git a/site/src/components/shortcodes/Placeholder.astro b/site/src/components/shortcodes/Placeholder.astro new file mode 100644 index 0000000..3ebde32 --- /dev/null +++ b/site/src/components/shortcodes/Placeholder.astro @@ -0,0 +1,27 @@ +--- +import { getPlaceholder, type PlaceholderOptions } from '@libs/placeholder' + +type Props = Partial + +const { + options: { background, color, showText, showTitle, text, title }, + props, + type +} = getPlaceholder(Astro.props) +--- + +{ + type === 'img' ? ( + + ) : ( + + {showTitle && {title}} + + {showText && ( + + {text} + + )} + + ) +} diff --git a/site/src/components/shortcodes/ScssDocs.astro b/site/src/components/shortcodes/ScssDocs.astro new file mode 100644 index 0000000..4eaa8ca --- /dev/null +++ b/site/src/components/shortcodes/ScssDocs.astro @@ -0,0 +1,71 @@ +--- +import fs from 'node:fs' +import { getConfig } from '@libs/config' +import Code from '@shortcodes/Code.astro' + +// Prints everything between `// scss-docs-start "name"` and `// scss-docs-end "name"` +// comments in the docs. + +interface Props { + /** + * Reference name used to find the content to display within the content of the `file` prop. + */ + name: string + /** + * File path that contains the content to display relative to the root of the repository. + */ + file: string +} + +const { name, file } = Astro.props + +if (!name || !file) { + throw new Error( + `Missing required parameter(s) for the '' component, expected both 'name' and 'file' but got 'name: ${name}' and 'file: ${file}'.` + ) +} + +let content: string + +try { + const fileContent = fs.readFileSync(file, 'utf8') + + const matches = fileContent.match( + new RegExp(`\/\/ scss-docs-start ${name}\n((?:.|\n)*)\/\/ scss-docs-end ${name}`, 'm') + ) + + if (!matches || !matches[1]) { + throw new Error( + `Failed to find the content named '${name}', make sure that '// scss-docs-start ${name}' and '// scss-docs-end ${name}' are defined.` + ) + } + + content = matches[1].replaceAll(' !default', '') + + // Fix the indentation by removing extra spaces at the beginning of each line + const lines = content.split('\n') + const spaceCounts = lines.filter((line) => line.trim().length > 0).map((line) => line.match(/^ */)[0].length) + const minSpaces = spaceCounts.length ? Math.min(...spaceCounts) : 0 + content = lines.map((line) => line.slice(minSpaces)).join('\n') +} catch (error) { + throw new Error(`Failed to find the content to render in the '' component at '${file}'.`, { + cause: error + }) +} +--- + + +
    + + {file} + +
    + +
    +
    +
    diff --git a/site/src/components/shortcodes/Table.astro b/site/src/components/shortcodes/Table.astro new file mode 100644 index 0000000..622aff9 --- /dev/null +++ b/site/src/components/shortcodes/Table.astro @@ -0,0 +1,32 @@ +--- +import Code from '@shortcodes/Code.astro' +import Example from '@shortcodes/Example.astro' +import * as tableContent from '@shortcodes/TableContent.md' + +interface Props { + /** + * Any class(es) to be added to the `` element (both in the example and code snippet). + */ + class?: string + /** + * Show a simplified version in the example code snippet by replacing the table content inside `
    ` & `
    ` + * with `...`. + * @default true + */ + simplified?: boolean +} + +const { class: className, simplified = true } = Astro.props + +const tableCode = ` +${simplified ? ' ...' : await tableContent.compiledContent()} +` + +const exampleCode = ` +${await tableContent.compiledContent()} +` +--- + + + + diff --git a/site/layouts/partials/table-content.html b/site/src/components/shortcodes/TableContent.md similarity index 87% rename from site/layouts/partials/table-content.html rename to site/src/components/shortcodes/TableContent.md index 71fca1d..cee54c6 100644 --- a/site/layouts/partials/table-content.html +++ b/site/src/components/shortcodes/TableContent.md @@ -21,7 +21,8 @@ 3 - Larry the Bird - @twitter + John + Doe + @social diff --git a/site/src/content/callouts/danger-async-methods.md b/site/src/content/callouts/danger-async-methods.md new file mode 100644 index 0000000..7b7a654 --- /dev/null +++ b/site/src/content/callouts/danger-async-methods.md @@ -0,0 +1 @@ +**All API methods are asynchronous and start a transition.** They return to the caller as soon as the transition is started, but before it ends. In addition, a method call on a transitioning component will be ignored. [Learn more in our JavaScript docs.](/docs/[[config:docs_version]]/getting-started/javascript/#asynchronous-functions-and-transitions) diff --git a/site/src/content/callouts/info-mediaqueries-breakpoints.md b/site/src/content/callouts/info-mediaqueries-breakpoints.md new file mode 100644 index 0000000..52be673 --- /dev/null +++ b/site/src/content/callouts/info-mediaqueries-breakpoints.md @@ -0,0 +1 @@ +**Why subtract .02px?** Browsers don’t currently support [range context queries](https://www.w3.org/TR/mediaqueries-4/#range-context), so we work around the limitations of [`min-` and `max-` prefixes](https://www.w3.org/TR/mediaqueries-4/#mq-min-max) and viewports with fractional widths (which can occur under certain conditions on high-dpi devices, for instance) by using values with higher precision. diff --git a/site/src/content/callouts/info-npm-starter.md b/site/src/content/callouts/info-npm-starter.md new file mode 100644 index 0000000..cc4a50e --- /dev/null +++ b/site/src/content/callouts/info-npm-starter.md @@ -0,0 +1 @@ +**Get started with Bootstrap via npm with our starter project!** Head to the [Sass & JS example](https://github.com/twbs/examples/tree/main/sass-js) template repository to see how to build and customize Bootstrap in your own npm project. Includes Sass compiler, Autoprefixer, Stylelint, PurgeCSS, and Bootstrap Icons. diff --git a/site/layouts/partials/callout-info-prefersreducedmotion.md b/site/src/content/callouts/info-prefersreducedmotion.md similarity index 50% rename from site/layouts/partials/callout-info-prefersreducedmotion.md rename to site/src/content/callouts/info-prefersreducedmotion.md index d258fbe..49d81ef 100644 --- a/site/layouts/partials/callout-info-prefersreducedmotion.md +++ b/site/src/content/callouts/info-prefersreducedmotion.md @@ -1 +1 @@ -The animation effect of this component is dependent on the `prefers-reduced-motion` media query. See the [reduced motion section of our accessibility documentation](/docs/{{ .Site.Params.docs_version }}/getting-started/accessibility/#reduced-motion). +The animation effect of this component is dependent on the `prefers-reduced-motion` media query. See the [reduced motion section of our accessibility documentation](/docs/[[config:docs_version]]/getting-started/accessibility/#reduced-motion). diff --git a/site/layouts/partials/callout-info-sanitizer.md b/site/src/content/callouts/info-sanitizer.md similarity index 66% rename from site/layouts/partials/callout-info-sanitizer.md rename to site/src/content/callouts/info-sanitizer.md index ee0eda4..516975b 100644 --- a/site/layouts/partials/callout-info-sanitizer.md +++ b/site/src/content/callouts/info-sanitizer.md @@ -1 +1 @@ -By default, this component uses the built-in content sanitizer, which strips out any HTML elements that are not explicitly allowed. See the [sanitizer section in our JavaScript documentation](/docs/{{ .Site.Params.docs_version }}/getting-started/javascript/#sanitizer) for more details. +By default, this component uses the built-in content sanitizer, which strips out any HTML elements that are not explicitly allowed. See the [sanitizer section in our JavaScript documentation](/docs/[[config:docs_version]]/getting-started/javascript/#sanitizer) for more details. diff --git a/site/src/content/callouts/warning-color-assistive-technologies.md b/site/src/content/callouts/warning-color-assistive-technologies.md new file mode 100644 index 0000000..8afa62e --- /dev/null +++ b/site/src/content/callouts/warning-color-assistive-technologies.md @@ -0,0 +1 @@ +**Accessibility tip:** Using color to add meaning only provides a visual indication, which will not be conveyed to users of assistive technologies like screen readers. Please ensure the meaning is obvious from the content itself (e.g., the visible text with a [_sufficient_ color contrast](/docs/[[config:docs_version]]/getting-started/accessibility/#color-contrast)) or is included through alternative means, such as additional text hidden with the `.visually-hidden` class. diff --git a/site/layouts/partials/callout-warning-data-bs-title-vs-title.md b/site/src/content/callouts/warning-data-bs-title-vs-title.md similarity index 100% rename from site/layouts/partials/callout-warning-data-bs-title-vs-title.md rename to site/src/content/callouts/warning-data-bs-title-vs-title.md diff --git a/site/src/content/callouts/warning-input-support.md b/site/src/content/callouts/warning-input-support.md new file mode 100644 index 0000000..f9d9c0a --- /dev/null +++ b/site/src/content/callouts/warning-input-support.md @@ -0,0 +1 @@ +Some date inputs types are [not fully supported](https://caniuse.com/input-datetime) by the latest versions of Safari and Firefox. diff --git a/site/src/content/config.ts b/site/src/content/config.ts new file mode 100644 index 0000000..387a005 --- /dev/null +++ b/site/src/content/config.ts @@ -0,0 +1,45 @@ +import { z, defineCollection } from 'astro:content' + +const docsSchema = z.object({ + added: z + .object({ + show_badge: z.boolean().optional(), + version: z.string() + }) + .optional(), + aliases: z.string().or(z.string().array()).optional(), + description: z.string(), + direction: z.literal('rtl').optional(), + extra_js: z + .object({ + async: z.boolean().optional(), + src: z.string() + }) + .array() + .optional(), + sections: z + .object({ + description: z.string(), + title: z.string() + }) + .array() + .optional(), + thumbnail: z.string().optional(), + title: z.string(), + toc: z.boolean().optional() +}) + +const docsCollection = defineCollection({ + schema: docsSchema +}) + +const calloutsSchema = z.object({}) + +const calloutsCollection = defineCollection({ + schema: calloutsSchema +}) + +export const collections = { + docs: docsCollection, + callouts: calloutsCollection +} diff --git a/site/src/content/docs/about/brand.mdx b/site/src/content/docs/about/brand.mdx new file mode 100644 index 0000000..0b17162 --- /dev/null +++ b/site/src/content/docs/about/brand.mdx @@ -0,0 +1,41 @@ +--- +title: Brand guidelines +description: Documentation and examples for Bootstrap’s logo and brand usage guidelines. +toc: true +--- + +Have a need for Bootstrap’s brand resources? Great! We have only a few guidelines we follow, and in turn ask you to follow as well. + +## Logo + +When referencing Bootstrap, use our logo mark. Do not modify our logos in any way. Do not use Bootstrap’s branding for your own open or closed source projects. + +
    + Bootstrap +
    + +Our logo mark is also available in black and white. All rules for our primary logo apply to these as well. + +
    +
    + Bootstrap +
    +
    + Bootstrap +
    +
    + +## Name + +Bootstrap should always be referred to as just **Bootstrap**. No capital _s_. + +
    +
    +
    Bootstrap
    + Correct +
    +
    +
    BootStrap
    + Incorrect +
    +
    diff --git a/site/content/docs/5.2/about/license.md b/site/src/content/docs/about/license.mdx similarity index 58% rename from site/content/docs/5.2/about/license.md rename to site/src/content/docs/about/license.mdx index 07e60e0..6479df6 100644 --- a/site/content/docs/5.2/about/license.md +++ b/site/src/content/docs/about/license.mdx @@ -1,15 +1,13 @@ --- -layout: docs title: License FAQs -description: Commonly asked questions about Bootstrap's open source license. -group: about +description: Commonly asked questions about Bootstrap’s open source license. --- -Bootstrap is released under the MIT license and is copyright {{< year >}} Twitter. Boiled down to smaller chunks, it can be described with the following conditions. +Bootstrap is released under the MIT license and is copyright {new Date().getFullYear()}. Boiled down to smaller chunks, it can be described with the following conditions. ## It requires you to: -- Keep the license and copyright notice included in Bootstrap's CSS and JavaScript files when you use them in your works +- Keep the license and copyright notice included in Bootstrap’s CSS and JavaScript files when you use them in your works ## It permits you to: @@ -23,12 +21,12 @@ Bootstrap is released under the MIT license and is copyright {{< year >}} Twitte - Hold the authors and license owners liable for damages as Bootstrap is provided without warranty - Hold the creators or copyright holders of Bootstrap liable - Redistribute any piece of Bootstrap without proper attribution -- Use any marks owned by Twitter in any way that might state or imply that Twitter endorses your distribution -- Use any marks owned by Twitter in any way that might state or imply that you created the Twitter software in question +- Use any marks owned by Bootstrap in any way that might state or imply that Bootstrap endorses your distribution +- Use any marks owned by Bootstrap in any way that might state or imply that you created the Bootstrap software in question ## It does not require you to: - Include the source of Bootstrap itself, or of any modifications you may have made to it, in any redistribution you may assemble that includes it - Submit changes that you make to Bootstrap back to the Bootstrap project (though such feedback is encouraged) -The full Bootstrap license is located [in the project repository]({{< param repo >}}/blob/v{{< param current_version >}}/LICENSE) for more information. +The full Bootstrap license is located [in the project repository]([[config:repo]]/blob/v[[config:current_version]]/LICENSE) for more information. diff --git a/site/src/content/docs/about/overview.mdx b/site/src/content/docs/about/overview.mdx new file mode 100644 index 0000000..efd16c5 --- /dev/null +++ b/site/src/content/docs/about/overview.mdx @@ -0,0 +1,27 @@ +--- +title: About Bootstrap +description: Learn more about the team maintaining Bootstrap, how and why the project started, and how to get involved. +aliases: + - "/about/" + - "/docs/[[config:docs_version]]/about/" +--- + +## Team + +Bootstrap is maintained by a [small team of developers](https://github.com/orgs/twbs/people) on GitHub. We’re actively looking to grow this team and would love to hear from you if you’re excited about CSS at scale, writing and maintaining vanilla JavaScript plugins, and improving build tooling processes for frontend code. + +## History + +Originally created by a designer and a developer at Twitter, Bootstrap has become one of the most popular front-end frameworks and open source projects in the world. + +Bootstrap was created at Twitter in mid-2010 by [@mdo](https://x.com/mdo) and [@fat](https://x.com/fat). Prior to being an open-sourced framework, Bootstrap was known as _Twitter Blueprint_. A few months into development, Twitter held its [first Hack Week](https://blog.x.com/engineering/en_us/a/2010/hack-week) and the project exploded as developers of all skill levels jumped in without any external guidance. It served as the style guide for internal tools development at the company for over a year before its public release, and continues to do so today. + +Originally [released](https://blog.x.com/developer/en_us/a/2011/bootstrap-twitter) on , we’ve since had over [twenty releases]([[config:repo]]/releases), including two major rewrites with v2 and v3. With Bootstrap 2, we added responsive functionality to the entire framework as an optional stylesheet. Building on that with Bootstrap 3, we rewrote the library once more to make it responsive by default with a mobile first approach. + +With Bootstrap 4, we once again rewrote the project to account for two key architectural changes: a migration to Sass and the move to CSS’s flexbox. Our intention is to help in a small way to move the web development community forward by pushing for newer CSS properties, fewer dependencies, and new technologies across more modern browsers. + +Our latest release, Bootstrap 5, focuses on improving v4’s codebase with as few major breaking changes as possible. We improved existing features and components, removed support for older browsers, dropped jQuery for regular JavaScript, and embraced more future-friendly technologies like CSS custom properties as part of our tooling. + +## Get involved + +Get involved with Bootstrap development by [opening an issue]([[config:repo]]/issues/new/choose) or submitting a pull request. Read our [contributing guidelines]([[config:repo]]/blob/v[[config:current_version]]/.github/CONTRIBUTING.md) for information on how we develop. diff --git a/site/src/content/docs/about/team.mdx b/site/src/content/docs/about/team.mdx new file mode 100644 index 0000000..46b03d8 --- /dev/null +++ b/site/src/content/docs/about/team.mdx @@ -0,0 +1,23 @@ +--- +title: Team +description: An overview of the founding team and core contributors to Bootstrap. +--- + +import { getData } from '@libs/data' + +Bootstrap is maintained by the founding team and a small group of invaluable core contributors, with the massive support and involvement of our community. + +
    + {getData('core-team').map((member) => { + return ( + + {`@${member.user}`} + + {member.name} @{member.user} + + + ) + })} +
    + +Get involved with Bootstrap development by [opening an issue]([[config:repo]]/issues/new/choose) or submitting a pull request. Read our [contributing guidelines]([[config:repo]]/blob/v[[config:current_version]]/.github/CONTRIBUTING.md) for information on how we develop. diff --git a/site/src/content/docs/about/translations.mdx b/site/src/content/docs/about/translations.mdx new file mode 100644 index 0000000..7db4ab8 --- /dev/null +++ b/site/src/content/docs/about/translations.mdx @@ -0,0 +1,20 @@ +--- +title: Translations +description: Links to community-translated Bootstrap documentation sites. +--- + +import { getData } from '@libs/data' + +Community members have translated Bootstrap’s documentation into various languages. None are officially supported and they may not always be up-to-date. + + + +**We don’t help organize or host translations, we just link to them.** + +Finished a new or better translation? Open a pull request to add it to our list. diff --git a/site/src/content/docs/components/accordion.mdx b/site/src/content/docs/components/accordion.mdx new file mode 100644 index 0000000..e8df6d8 --- /dev/null +++ b/site/src/content/docs/components/accordion.mdx @@ -0,0 +1,240 @@ +--- +title: Accordion +description: Build vertically collapsing accordions in combination with our Collapse JavaScript plugin. +aliases: + - "/components/" + - "/docs/[[config:docs_version]]/components/" +toc: true +--- + +## How it works + +The accordion uses [collapse]([[docsref:/components/collapse]]) internally to make it collapsible. + + + +## Example + +Click the accordions below to expand/collapse the accordion content. + +To render an accordion that’s expanded by default: +- add the `.show` class on the `.accordion-collapse` element. +- drop the `.collapsed` class from the `.accordion-button` element and set its `aria-expanded` attribute to `true`. + + +
    +

    + +

    +
    +
    + This is the first item’s accordion body. It is shown by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It’s also worth noting that just about any HTML can go within the .accordion-body, though the transition does limit overflow. +
    +
    +
    +
    +

    + +

    +
    +
    + This is the second item’s accordion body. It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It’s also worth noting that just about any HTML can go within the .accordion-body, though the transition does limit overflow. +
    +
    +
    +
    +

    + +

    +
    +
    + This is the third item’s accordion body. It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It’s also worth noting that just about any HTML can go within the .accordion-body, though the transition does limit overflow. +
    +
    +
    +
    `} /> + +### Flush + +Add `.accordion-flush` to remove some borders and rounded corners to render accordions edge-to-edge with their parent container. + + +
    +

    + +

    +
    +
    Placeholder content for this accordion, which is intended to demonstrate the .accordion-flush class. This is the first item’s accordion body.
    +
    +
    +
    +

    + +

    +
    +
    Placeholder content for this accordion, which is intended to demonstrate the .accordion-flush class. This is the second item’s accordion body. Let’s imagine this being filled with some actual content.
    +
    +
    +
    +

    + +

    +
    +
    Placeholder content for this accordion, which is intended to demonstrate the .accordion-flush class. This is the third item’s accordion body. Nothing more exciting happening here in terms of content, but just filling up the space to make it look, at least at first glance, a bit more representative of how this would look in a real-world application.
    +
    +
    +
    `} /> + +### Always open + +Omit the `data-bs-parent` attribute on each `.accordion-collapse` to make accordion items stay open when another item is opened. + + +
    +

    + +

    +
    +
    + This is the first item’s accordion body. It is shown by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It’s also worth noting that just about any HTML can go within the .accordion-body, though the transition does limit overflow. +
    +
    +
    +
    +

    + +

    +
    +
    + This is the second item’s accordion body. It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It’s also worth noting that just about any HTML can go within the .accordion-body, though the transition does limit overflow. +
    +
    +
    +
    +

    + +

    +
    +
    + This is the third item’s accordion body. It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It’s also worth noting that just about any HTML can go within the .accordion-body, though the transition does limit overflow. +
    +
    +
    +
    `} /> + +## Accessibility + +Please read the [collapse accessibility section]([[docsref:/components/collapse#accessibility]]) for more information. + +## CSS + +### Variables + + + +As part of Bootstrap’s evolving CSS variables approach, accordions now use local CSS variables on `.accordion` 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 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 group management to a collapsible area, add the data attribute `data-bs-parent="#selector"`. + +### Via JavaScript + +Enable manually with: + +```js +const accordionCollapseElementList = document.querySelectorAll('#myAccordion .collapse') +const accordionCollapseList = [...accordionCollapseElementList].map(accordionCollapseEl => new bootstrap.Collapse(accordionCollapseEl)) +``` + +### 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/alerts.mdx b/site/src/content/docs/components/alerts.mdx new file mode 100644 index 0000000..e256044 --- /dev/null +++ b/site/src/content/docs/components/alerts.mdx @@ -0,0 +1,218 @@ +--- +title: Alerts +description: Provide contextual feedback messages for typical user actions with the handful of available and flexible alert messages. +toc: true +--- + +import { getData } from '@libs/data' + +## Examples + +Alerts are available for any length of text, as well as an optional close button. For proper styling, use one of the eight **required** contextual classes (e.g., `.alert-success`). For inline dismissal, use the [alerts JavaScript plugin](#dismissing). + + +**Heads up!** As of v5.3.0, the `alert-variant()` Sass mixin is deprecated. Alert variants now have their CSS variables overridden in [a Sass loop](#sass-loops). + + + ``)} /> + + + +### Live example + +Click the button below to show an alert (hidden with inline styles to start), then dismiss (and destroy) it with the built-in close button. + +
    +`} /> + +We use the following JavaScript to trigger our live alert demo: + + + +### Link color + +Use the `.alert-link` utility class to quickly provide matching colored links within any alert. + + ``)} /> + +### Additional content + +Alerts can also contain additional HTML elements like headings, paragraphs and dividers. + + +

    Well done!

    +

    Aww yeah, you successfully read this important alert message. This example text is going to run a bit longer so that you can see how spacing within an alert works with this kind of content.

    +
    +

    Whenever you need to, be sure to use margin utilities to keep things nice and tidy.

    +
    `} /> + +### Icons + +Similarly, you can use [flexbox utilities]([[docsref:/utilities/flex]]) and [Bootstrap Icons]([[config:icons]]) to create alerts with icons. Depending on your icons and content, you may want to add more utilities or custom styles. + + + + + +
    + An example alert with an icon +
    +
    `} /> + +Need more than one icon for your alerts? Consider using more Bootstrap Icons and making a local SVG sprite like so to easily reference the same icons repeatedly. + + + + + + + + + + + + + + + + + `} /> + +### Dismissing + +Using the alert JavaScript plugin, it’s possible to dismiss any alert inline. Here’s how: + +- Be sure you’ve loaded the alert plugin, or the compiled Bootstrap JavaScript. +- Add a [close button]([[docsref:/components/close-button]]) and the `.alert-dismissible` class, which adds extra padding to the right of the alert and positions the close button. +- On the close button, add the `data-bs-dismiss="alert"` attribute, which triggers the JavaScript functionality. Be sure to use the ` + `} /> + + +When an alert is dismissed, the element is completely removed from the page structure. If a keyboard user dismisses the alert using the close button, their focus will suddenly be lost and, depending on the browser, reset to the start of the page/document. For this reason, we recommend including additional JavaScript that listens for the `closed.bs.alert` event and programmatically sets `focus()` to the most appropriate location in the page. If you’re planning to move focus to a non-interactive element that normally does not receive focus, make sure to add `tabindex="-1"` to the element. + + +## CSS + +### Variables + + + +As part of Bootstrap’s evolving CSS variables approach, alerts now use local CSS variables on `.alert` for enhanced real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too. + + + +### Sass variables + + + +### Sass mixins + + + + + +### Sass loops + +Loop that generates the modifier classes with an overriding of CSS variables. + + + +## JavaScript behavior + +### Initialize + +Initialize elements as alerts + +```js +const alertList = document.querySelectorAll('.alert') +const alerts = [...alertList].map(element => new bootstrap.Alert(element)) +``` + + +For the sole purpose of dismissing an alert, it isn’t necessary to initialize the component manually via the JS API. By making use of `data-bs-dismiss="alert"`, the component will be initialized automatically and properly dismissed. + +See the [triggers](#triggers) section for more details. + + +### Triggers + + + +**Note that closing an alert will remove it from the DOM.** + +### Methods + +You can create an alert instance with the alert constructor, for example: + +```js +const bsAlert = new bootstrap.Alert('#myAlert') +``` + +This makes an alert listen for click events on descendant elements which have the `data-bs-dismiss="alert"` attribute. (Not necessary when using the data-api’s auto-initialization.) + + +| Method | Description | +| --- | --- | +| `close` | Closes an alert by removing it from the DOM. If the `.fade` and `.show` classes are present on the element, the alert will fade out before it is removed. | +| `dispose` | Destroys an element’s alert. (Removes stored data on the DOM element) | +| `getInstance` | Static method which allows you to get the alert instance associated to a DOM element. For example: `bootstrap.Alert.getInstance(alert)`. | +| `getOrCreateInstance` | Static method which returns an alert instance associated to a DOM element or create a new one in case it wasn’t initialized. You can use it like this: `bootstrap.Alert.getOrCreateInstance(element)`. | + + +Basic usage: + +```js +const alert = bootstrap.Alert.getOrCreateInstance('#myAlert') +alert.close() +``` + +### Events + +Bootstrap’s alert plugin exposes a few events for hooking into alert functionality. + + +| Event | Description | +| --- | --- | +| `close.bs.alert` | Fires immediately when the `close` instance method is called. | +| `closed.bs.alert` | Fired when the alert has been closed and CSS transitions have completed. | + + +```js +const myAlert = document.getElementById('myAlert') +myAlert.addEventListener('closed.bs.alert', event => { + // do something, for instance, explicitly move focus to the most appropriate element, + // so it doesn’t get lost/reset to the start of the page + // document.getElementById('...').focus() +}) +``` diff --git a/site/src/content/docs/components/badge.mdx b/site/src/content/docs/components/badge.mdx new file mode 100644 index 0000000..b3e574b --- /dev/null +++ b/site/src/content/docs/components/badge.mdx @@ -0,0 +1,83 @@ +--- +title: Badges +description: Documentation and examples for badges, our small count and labeling component. +toc: true +--- + +import { getData } from '@libs/data' + +## Examples + +Badges scale to match the size of the immediate parent element by using relative font sizing and `em` units. As of v5, badges no longer have focus or hover styles for links. + +### Headings + +Example heading New +

    Example heading New

    +

    Example heading New

    +

    Example heading New

    +
    Example heading New
    +
    Example heading New
    `} /> + +### Buttons + +Badges can be used as part of links or buttons to provide a counter. + + + Notifications 4 + `} /> + +Note that depending on how they are used, badges may be confusing for users of screen readers and similar assistive technologies. While the styling of badges provides a visual cue as to their purpose, these users will simply be presented with the content of the badge. Depending on the specific situation, these badges may seem like random additional words or numbers at the end of a sentence, link, or button. + +Unless the context is clear (as with the “Notifications” example, where it is understood that the “4” is the number of notifications), consider including additional context with a visually hidden piece of additional text. + +### Positioned + +Use utilities to modify a `.badge` and position it in the corner of a link or button. + + + Inbox + + 99+ + unread messages + + `} /> + +You can also replace the `.badge` class with a few more utilities without a count for a more generic indicator. + + + Profile + + New alerts + + `} /> + +## Background colors + + + +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. + + `${themeColor.title}`)} /> + + + +## Pill badges + +Use the `.rounded-pill` utility class to make badges more rounded with a larger `border-radius`. + + `${themeColor.title}`)} /> + +## CSS + +### Variables + + + +As part of Bootstrap’s evolving CSS variables approach, badges now use local CSS variables on `.badge` 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/breadcrumb.mdx b/site/src/content/docs/components/breadcrumb.mdx new file mode 100644 index 0000000..50cceb1 --- /dev/null +++ b/site/src/content/docs/components/breadcrumb.mdx @@ -0,0 +1,98 @@ +--- +title: Breadcrumb +description: Indicate the current page’s location within a navigational hierarchy that automatically adds separators via CSS. +toc: true +--- + +## Example + +Use an ordered or unordered list with linked list items to create a minimally styled breadcrumb. Use our utilities to add additional styles as desired. + + + + + + + + `} /> + +## Dividers + +Dividers are automatically added in CSS through [`::before`](https://developer.mozilla.org/en-US/docs/Web/CSS/::before) and [`content`](https://developer.mozilla.org/en-US/docs/Web/CSS/content). They can be changed by modifying a local CSS custom property `--bs-breadcrumb-divider`, or through the `$breadcrumb-divider` Sass variable — and `$breadcrumb-divider-flipped` for its RTL counterpart, if needed. We default to our Sass variable, which is set as a fallback to the custom property. This way, you get a global divider that you can override without recompiling CSS at any time. + + + + `} /> + +When modifying via Sass, the [quote](https://sass-lang.com/documentation/modules/string/#quote) function is required to generate the quotes around a string. For example, using `>` as the divider, you can use this: + +```scss +$breadcrumb-divider: quote(">"); +``` + +It’s also possible to use an **embedded SVG icon**. Apply it via our CSS custom property, or use the Sass variable. + + +**Inlined SVG requires properly escaped characters.** Some reserved characters, such as `<`, `>` and `#`, must be URL-encoded or escaped. We do this with the `$breadcrumb-divider` variable using our [`escape-svg()` Sass function]([[docsref:/customize/sass#escape-svg]]). When customizing the CSS variable, you must handle this yourself. Read [Kevin Weber’s explanations on CodePen](https://codepen.io/kevinweber/pen/dXWoRw ) for more info. + + + + + `} /> + +```scss +$breadcrumb-divider: url("data:image/svg+xml,"); +``` + +You can also remove the divider setting `--bs-breadcrumb-divider: '';` (empty strings in CSS custom properties counts as a value), or setting the Sass variable to `$breadcrumb-divider: none;`. + + + + `} /> + + +```scss +$breadcrumb-divider: none; +``` + +## Accessibility + +Since breadcrumbs provide a navigation, it’s a good idea to add a meaningful label such as `aria-label="breadcrumb"` to describe the type of navigation provided in the ``} /> ## Directions -{{< callout info >}} -#### RTL -Directions are mirrored when using Bootstrap in RTL, meaning `.dropstart` will appear on the right side. -{{< /callout >}} + +**Directions are flipped in RTL mode.** As such, `.dropstart` will appear on the right side. + ### Centered Make the dropdown menu centered below the toggle with `.dropdown-center` on the parent element. -{{< example >}} - -{{< /example >}} + + + + `} /> ### Dropup Trigger dropdown menus above elements by adding `.dropup` to the parent element. -
    -
    + @@ -435,9 +417,7 @@ Trigger dropdown menus above elements by adding `.dropup` to the parent element.
    - + @@ -448,8 +428,7 @@ Trigger dropdown menus above elements by adding `.dropup` to the parent element.
  • Separated link
  • -
    -
    + `} /> ```html @@ -480,25 +459,22 @@ Trigger dropdown menus above elements by adding `.dropup` to the parent element. Make the dropup menu centered above the toggle with `.dropup-center` on the parent element. -{{< example >}} -
    - - -
    -{{< /example >}} + + + + `} /> ### Dropend Trigger dropdown menus at the right of the elements by adding `.dropend` to the parent element. -
    -
    + @@ -511,9 +487,7 @@ Trigger dropdown menus at the right of the elements by adding `.dropend` to the
    - + @@ -524,8 +498,7 @@ Trigger dropdown menus at the right of the elements by adding `.dropend` to the
  • Separated link
  • -
    -
    + `} /> ```html @@ -556,8 +529,7 @@ Trigger dropdown menus at the right of the elements by adding `.dropend` to the Trigger dropdown menus at the left of the elements by adding `.dropstart` to the parent element. -
    -
    + @@ -580,11 +552,8 @@ Trigger dropdown menus at the left of the elements by adding `.dropstart` to the
  • Separated link
  • - -
    -
    + + `} /> ```html @@ -615,53 +584,45 @@ Trigger dropdown menus at the left of the elements by adding `.dropstart` to the You can use `` or ` - - -{{< /example >}} + + + + `} /> You can also create non-interactive dropdown items with `.dropdown-item-text`. Feel free to style further with custom CSS or text utilities. -{{< example >}} - -{{< /example >}} + +
  • Dropdown item text
  • +
  • Action
  • +
  • Another action
  • +
  • Something else here
  • + `} /> ### Active Add `.active` to items in the dropdown to **style them as active**. To convey the active state to assistive technologies, use the `aria-current` attribute — using the `page` value for the current page, or `true` for the current item in a set. -{{< example >}} - -{{< /example >}} + +
  • Regular link
  • +
  • Active link
  • +
  • Another link
  • + `} /> ### Disabled Add `.disabled` to items in the dropdown to **style them as disabled**. -{{< example >}} - -{{< /example >}} + +
  • Regular link
  • +
  • Disabled link
  • +
  • Another link
  • + `} /> ## Menu alignment @@ -669,22 +630,20 @@ By default, a dropdown menu is automatically positioned 100% from the top and al Add `.dropdown-menu-end` to a `.dropdown-menu` to right align the dropdown menu. Directions are mirrored when using Bootstrap in RTL, meaning `.dropdown-menu-end` will appear on the left side. -{{< callout info >}} + **Heads up!** Dropdowns are positioned thanks to Popper except when they are contained in a navbar. -{{< /callout >}} + -{{< example >}} -
    - - -
    -{{< /example >}} + + + + `} /> ### Responsive alignment @@ -692,118 +651,112 @@ If you want to use responsive alignment, disable dynamic positioning by adding t To align **right** the dropdown menu with the given breakpoint or larger, add `.dropdown-menu{-sm|-md|-lg|-xl|-xxl}-end`. -{{< example >}} -
    - - -
    -{{< /example >}} + + + + `} /> To align **left** the dropdown menu with the given breakpoint or larger, add `.dropdown-menu-end` and `.dropdown-menu{-sm|-md|-lg|-xl|-xxl}-start`. -{{< example >}} -
    - - -
    -{{< /example >}} + + + + `} /> -Note that you don't need to add a `data-bs-display="static"` attribute to dropdown buttons in navbars, since Popper isn't used in navbars. +Note that you don’t need to add a `data-bs-display="static"` attribute to dropdown buttons in navbars, since Popper isn’t used in navbars. ### Alignment options -Taking most of the options shown above, here's a small kitchen sink demo of various dropdown alignment options in one place. +Taking most of the options shown above, here’s a small kitchen sink demo of various dropdown alignment options in one place. -{{< example >}} -
    - - -
    + + + + -
    - - -
    +
    + + +
    -
    - - -
    +
    + + +
    -
    - - -
    +
    + + +
    -
    - - -
    +
    + + +
    -
    - - -
    +
    + + +
    -
    - - -
    -{{< /example >}} +
    + + +
    `} /> ## Menu content @@ -811,224 +764,216 @@ Taking most of the options shown above, here's a small kitchen sink demo of vari Add a header to label sections of actions in any dropdown menu. -{{< example >}} - -{{< /example >}} + +
  • +
  • Action
  • +
  • Another action
  • + `} /> ### Dividers Separate groups of related menu items with a divider. -{{< example >}} - -{{< /example >}} + +
  • Action
  • +
  • Another action
  • +
  • Something else here
  • +
  • +
  • Separated link
  • + `} /> ### 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. -{{< example >}} - -{{< /example >}} + +

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

    +

    + And this is more example text. +

    + `} /> ### 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 >}} -`} /> -{{< 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. -{{< scss-docs name="dropdown-css-vars" file="scss/_dropdown.scss" >}} + + + +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` + 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" >}} + -### Mixins +### Sass 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. -{{< 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 >}} + +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. + ### Via data attributes @@ -1047,6 +992,10 @@ 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 @@ -1054,28 +1003,20 @@ 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/). | -{{< /bs-table >}} +| `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/). | +
    #### Using function with `popperConfig` @@ -1091,30 +1032,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. | -{{< /bs-table >}} +| `update` | Updates the position of an element’s dropdown. | + ### 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/content/docs/5.2/components/list-group.md b/site/src/content/docs/components/list-group.mdx similarity index 51% rename from site/content/docs/5.2/components/list-group.md rename to site/src/content/docs/components/list-group.mdx index 7804c74..59827dd 100644 --- a/site/content/docs/5.2/components/list-group.md +++ b/site/src/content/docs/components/list-group.mdx @@ -1,98 +1,76 @@ --- -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. -{{< example >}} -
      -
    • 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
  • + `} /> ## Active items Add `.active` to a `.list-group-item` to indicate the current active selection. -{{< 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 >}} + +
  • An active item
  • +
  • A second item
  • +
  • A third item
  • +
  • A fourth item
  • +
  • And a fifth one
  • + `} /> ## Links and buttons -Use ``s or ` - - - - - -{{< /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). -{{< example >}} -
      -
    • 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
  • + `} /> ## Numbered @@ -100,228 +78,206 @@ 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`. -{{< example >}} -
          -
        1. A list item
        2. -
        3. A list item
        4. -
        5. A list item
        6. -
        -{{< /example >}} + +
      2. A list item
      3. +
      4. A list item
      5. +
      6. A list item
      7. +
      `} /> These work great with custom content as well. -{{< 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 >}} + +
    1. +
      +
      Subheading
      + Content for list item +
      + 14 +
    2. +
    3. +
      +
      Subheading
      + Content for list item +
      + 14 +
    4. +
    5. +
      +
      Subheading
      + Content for list item +
      + 14 +
    6. +
    `} /> ## 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. -{{< example >}} -{{< list-group.inline >}} -{{- range $.Site.Data.breakpoints }} -
      -
    • An item
    • -
    • A second item
    • -
    • A third item
    • -
    -{{- end -}} -{{< /list-group.inline >}} -{{< /example >}} + `
      +
    • An item
    • +
    • A second item
    • +
    • A third item
    • +
    `)} /> -## Contextual classes +## 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). + Use contextual classes to style list items with a stateful background and color. -{{< 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 >}} + +
  • A simple default list group item
  • + `, + ...getData('theme-colors').map((themeColor) => `
  • A simple ${themeColor.name} list group item
  • `), + `` + ]} /> -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. +### For links and buttons -{{< 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 >}} +Contextual classes also work with `.list-group-item-action` for `` and ` -
    - -
    +Launch demo modal +`} /> ```html @@ -148,7 +142,7 @@ When backdrop is set to static, the modal will not close when clicking outside o -
    - -
    +Launch static backdrop modal +`} /> ```html @@ -192,7 +184,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. `} /> + +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/content/docs/5.2/components/tooltips.md b/site/src/content/docs/components/tooltips.mdx similarity index 52% rename from site/content/docs/5.2/components/tooltips.md rename to site/src/content/docs/components/tooltips.mdx index a106d9d..32fa16d 100644 --- a/site/content/docs/5.2/components/tooltips.md +++ b/site/src/content/docs/components/tooltips.mdx @@ -1,8 +1,6 @@ --- -layout: docs title: Tooltips description: Documentation and examples for adding custom Bootstrap tooltips with CSS and JavaScript using CSS3 for animations and data-bs-attributes for local title storage. -group: components toc: true --- @@ -10,7 +8,7 @@ toc: true Things to know when using the tooltip plugin: -- Tooltips rely on the third party library [Popper](https://popper.js.org/) for positioning. You must include [popper.min.js]({{< param "cdn.popper" >}}) before `bootstrap.js`, or use one `bootstrap.bundle.min.js` which contains Popper. +- Tooltips rely on the third party library [Popper](https://popper.js.org/docs/v2/) for positioning. You must include [popper.min.js]([[config:cdn.popper]]) before `bootstrap.js`, or use one `bootstrap.bundle.min.js` which contains Popper. - Tooltips are opt-in for performance reasons, so **you must initialize them yourself**. - Tooltips with zero-length titles are never displayed. - Specify `container: 'body'` to avoid rendering problems in more complex components (like our input groups, button groups, etc). @@ -20,15 +18,11 @@ Things to know when using the tooltip plugin: - Tooltips must be hidden before their corresponding elements have been removed from the DOM. - Tooltips can be triggered thanks to an element inside a shadow DOM. -Got all that? Great, let's see how they work with some examples. +Got all that? Great, let’s see how they work with some examples. -{{< callout info >}} -{{< partial "callout-info-sanitizer.md" >}} -{{< /callout >}} + -{{< callout info >}} -{{< partial "callout-info-prefersreducedmotion.md" >}} -{{< /callout >}} + ## Examples @@ -45,46 +39,36 @@ const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstra Hover over the links below to see tooltips: -{{< example class="tooltip-demo" stackblitz_add_js="true" >}} -

    Placeholder text to demonstrate some inline links with tooltips. This is now just filler, no killer. Content placed here just to mimic the presence of real text. And all that just to give you an idea of how tooltips would look when used in real-world situations. So hopefully you've now seen how these tooltips on links can work in practice, once you use them on your own site or project. -

    -{{< /example >}} +Placeholder text to demonstrate some inline links with tooltips. This is now just filler, no killer. Content placed here just to mimic the presence of real text. And all that just to give you an idea of how tooltips would look when used in real-world situations. So hopefully you’ve now seen how these tooltips on links can work in practice, once you use them on your own site or project.

    `} /> -{{< callout warning >}} -{{< partial "callout-warning-data-bs-title-vs-title.md" >}} -{{< /callout >}} + ### Custom tooltips -{{< added-in "5.2.0" >}} + You can customize the appearance of tooltips using [CSS variables](#variables). We set a custom class with `data-bs-custom-class="custom-tooltip"` to scope our custom appearance and use it to override a local CSS variable. -{{< scss-docs name="custom-tooltip" file="site/assets/scss/_component-examples.scss" >}} + - -{{< example class="tooltip-demo" stackblitz_add_js="true" >}} - -{{< /example >}} + + Custom tooltip + `} /> ### Directions Hover over the buttons below to see the four tooltips directions: top, right, bottom, and left. Directions are mirrored when using Bootstrap in RTL. -
    -
    + -
    -
    + `} /> ```html - -{{< /example >}} - + + + `} /> ### Options -{{< markdown >}} -{{< partial "js-data-attributes.md" >}} -{{< /markdown >}} + -{{< callout warning >}} + Note that for security reasons the `sanitize`, `sanitizeFn`, and `allowList` options cannot be supplied using data attributes. -{{< /callout >}} + -{{< bs-table "table" >}} + | Name | Type | Default | Description | | --- | --- | --- | --- | -| `allowList` | object | [Default value]({{< docsref "/getting-started/javascript#sanitizer" >}}) | Object which contains allowed attributes and tags. | +| `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 tooltip. | -| `boundary` | string, element | `'clippingParents'` | Overflow constraint boundary of the tooltip (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 tooltip to a specific element. Example: `container: 'body'`. This option is particularly useful in that it allows you to position the tooltip in the flow of the document near the triggering element - which will prevent the tooltip from floating away from the triggering element during a window resize. | +| `boundary` | string, element | `'clippingParents'` | Overflow constraint boundary of the tooltip (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 tooltip to a specific element. Example: `container: 'body'`. This option is particularly useful in that it allows you to position the tooltip in the flow of the document near the triggering element - which will prevent the tooltip from floating away from the triggering element during a window resize. | | `customClass` | string, function | `''` | Add classes to the tooltip 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 tooltip (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` | 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 tooltip. If true, HTML tags in the tooltip's `title` will be rendered in the tooltip. If false, `innerText` property will be used to insert content into the DOM. Use text if you're worried about XSS attacks. | -| `offset` | array, string, function | `[0, 0]` | Offset of the tooltip 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). | +| `delay` | number, object | `0` | Delay showing and hiding the tooltip (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` | 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 tooltip. If true, HTML tags in the tooltip’s `title` will be rendered in the tooltip. 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` | array, string, function | `[0, 6]` | Offset of the tooltip 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 | `'top'` | How to position the tooltip: auto, top, bottom, left, right. When `auto` is specified, it will dynamically reorient the tooltip. When a function is used to determine the placement, it is called with the tooltip DOM node as its first argument and the triggering element DOM node as its second. The `this` context is set to the tooltip 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 or disable the sanitization. If activated `'template'`, `'content'` and `'title'` options will be sanitized. | -| `sanitizeFn` | null, function | `null` | Here you can supply your own sanitize 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, tooltip objects will be delegated to the specified targets. In practice, this is used to also apply tooltips to dynamically added DOM elements (`jQuery.on` support). See [this issue]({{< param 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 tooltip. The tooltip's `title` will be injected into the `.tooltip-inner`. `.tooltip-arrow` will become the tooltip's arrow. The outermost wrapper element should have the `.tooltip` class and `role="tooltip"`. | -| `title` | string, element, function | `''` | Default title value if `title` attribute isn't present. If a function is given, it will be called with its `this` reference set to the element that the popover is attached to. | +| `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, tooltip objects will be delegated to the specified targets. In practice, this is used to also apply tooltips 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 tooltip. The tooltip’s `title` will be injected into the `.tooltip-inner`. `.tooltip-arrow` will become the tooltip’s arrow. The outermost wrapper element should have the `.tooltip` class and `role="tooltip"`. | +| `title` | string, element, function | `''` | The tooltip 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 | `'hover focus'` | How tooltip is triggered: click, hover, focus, manual. You may pass multiple triggers; separate them with a space. `'manual'` indicates that the tooltip will be triggered programmatically via the `.tooltip('show')`, `.tooltip('hide')` and `.tooltip('toggle')` methods; this value cannot be combined with any other trigger. `'hover'` on its own will result in tooltips 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. | -{{< /bs-table >}} + -{{< callout info >}} + #### Data attributes for individual tooltips Options for individual tooltips can alternatively be specified through the use of data attributes, as explained above. -{{< /callout >}} + #### Using function with `popperConfig` @@ -245,49 +217,46 @@ const tooltip = new bootstrap.Tooltip(element, { ### Methods -{{< callout danger >}} -{{< partial "callout-danger-async-methods.md" >}} -{{< /callout >}} + -{{< bs-table "table" >}} + | Method | Description | | --- | --- | -| `disable` | Removes the ability for an element's tooltip to be shown. The tooltip will only be able to be shown if it is re-enabled. | -| `dispose` | Hides and destroys an element's tooltip (Removes stored data on the DOM element). Tooltips that use delegation (which are created using [the `selector` option](#options)) cannot be individually destroyed on descendant trigger elements. | -| `enable` | Gives an element's tooltip the ability to be shown. **Tooltips are enabled by default.** | -| `getInstance` | *Static* method which allows you to get the tooltip instance associated with a DOM element, or create a new one in case it wasn't initialized. | -| `getOrCreateInstance` | *Static* method which allows you to get the tooltip instance associated with a DOM element, or create a new one in case it wasn't initialized. | -| `hide` | Hides an element's tooltip. **Returns to the caller before the tooltip has actually been hidden** (i.e. before the `hidden.bs.tooltip` event occurs). This is considered a "manual" triggering of the tooltip. | -| `setContent` | Gives a way to change the tooltip's content after its initialization. | -| `show` | Reveals an element's tooltip. **Returns to the caller before the tooltip has actually been shown** (i.e. before the `shown.bs.tooltip` event occurs). This is considered a "manual" triggering of the tooltip. Tooltips with zero-length titles are never displayed. | -| `toggle` | Toggles an element's tooltip. **Returns to the caller before the tooltip has actually been shown or hidden** (i.e. before the `shown.bs.tooltip` or `hidden.bs.tooltip` event occurs). This is considered a "manual" triggering of the tooltip. | -| `toggleEnabled` | Toggles the ability for an element's tooltip to be shown or hidden. | -| `update` | Updates the position of an element's tooltip. | -{{< /bs-table >}} +| `disable` | Removes the ability for an element’s tooltip to be shown. The tooltip will only be able to be shown if it is re-enabled. | +| `dispose` | Hides and destroys an element’s tooltip (Removes stored data on the DOM element). Tooltips that use delegation (which are created using [the `selector` option](#options)) cannot be individually destroyed on descendant trigger elements. | +| `enable` | Gives an element’s tooltip the ability to be shown. **Tooltips are enabled by default.** | +| `getInstance` | *Static* method which allows you to get the tooltip instance associated with a DOM element. | +| `getOrCreateInstance` | *Static* method which allows you to get the tooltip instance associated with a DOM element, or create a new one in case it wasn’t initialized. | +| `hide` | Hides an element’s tooltip. **Returns to the caller before the tooltip has actually been hidden** (i.e. before the `hidden.bs.tooltip` event occurs). This is considered a “manual” triggering of the tooltip. | +| `setContent` | Gives a way to change the tooltip’s content after its initialization. | +| `show` | Reveals an element’s tooltip. **Returns to the caller before the tooltip has actually been shown** (i.e. before the `shown.bs.tooltip` event occurs). This is considered a “manual” triggering of the tooltip. Tooltips with zero-length titles are never displayed. | +| `toggle` | Toggles an element’s tooltip. **Returns to the caller before the tooltip has actually been shown or hidden** (i.e. before the `shown.bs.tooltip` or `hidden.bs.tooltip` event occurs). This is considered a “manual” triggering of the tooltip. | +| `toggleEnabled` | Toggles the ability for an element’s tooltip to be shown or hidden. | +| `update` | Updates the position of an element’s tooltip. | + ```js const tooltip = bootstrap.Tooltip.getInstance('#example') // Returns a Bootstrap tooltip instance // setContent example tooltip.setContent({ '.tooltip-inner': 'another title' }) - ``` -{{< callout info >}} -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` -{{< /callout >}} + +The `setContent` method accepts an `object` argument, where each property-key is a valid `string` selector within the tooltip template, and each related property-value can be `string` | `element` | `function` | `null` + ### Events -{{< bs-table >}} + | Event | Description | | --- | --- | | `hide.bs.tooltip` | This event is fired immediately when the `hide` instance method has been called. | -| `hidden.bs.tooltip` | This event is fired when the popover has finished being hidden from the user (will wait for CSS transitions to complete). | +| `hidden.bs.tooltip` | This event is fired when the tooltip has finished being hidden from the user (will wait for CSS transitions to complete). | | `inserted.bs.tooltip` | This event is fired after the `show.bs.tooltip` event when the tooltip template has been added to the DOM. | | `show.bs.tooltip` | This event fires immediately when the `show` instance method is called. | -| `shown.bs.tooltip` | This event is fired when the popover has been made visible to the user (will wait for CSS transitions to complete). | -{{< /bs-table >}} +| `shown.bs.tooltip` | This event is fired when the tooltip has been made visible to the user (will wait for CSS transitions to complete). | + ```js const myTooltipEl = document.getElementById('myTooltip') diff --git a/site/src/content/docs/content/figures.mdx b/site/src/content/docs/content/figures.mdx new file mode 100644 index 0000000..31cb30f --- /dev/null +++ b/site/src/content/docs/content/figures.mdx @@ -0,0 +1,27 @@ +--- +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 new file mode 100644 index 0000000..61cc42d --- /dev/null +++ b/site/src/content/docs/content/images.mdx @@ -0,0 +1,51 @@ +--- +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/content/docs/5.2/content/reboot.md b/site/src/content/docs/content/reboot.mdx similarity index 60% rename from site/content/docs/5.2/content/reboot.md rename to site/src/content/docs/content/reboot.mdx index ca667b2..642638f 100644 --- a/site/content/docs/5.2/content/reboot.md +++ b/site/src/content/docs/content/reboot.mdx @@ -1,9 +1,7 @@ --- -layout: docs title: Reboot description: Reboot, a collection of element-specific CSS changes in a single file, kickstart Bootstrap to provide an elegant, consistent, and simple baseline to build upon. -group: content -aliases: "/docs/5.2/content/" +aliases: "/docs/[[config:docs_version]]/content/" toc: true --- @@ -20,17 +18,17 @@ Here are our guidelines and reasons for choosing what to override in Reboot: ## CSS variables -{{< added-in "5.2.0" >}} + -With v5.1.1, we standardized our required `@import`s across all our CSS bundles (including `bootstrap.css`, `bootstrap-reboot.css`, and `bootstrap-grid.css`) to include `_root.scss`. This adds `:root` level CSS variables to all bundles, regardless of how many of them are used in that bundle. Ultimately Bootstrap 5 will continue to see more [CSS variables]({{< docsref "/customize/css-variables" >}}) added over time, in order to provide more real-time customization without the need to always recompile Sass. Our approach is to take our source Sass variables and transform them into CSS variables. That way, even if you don't use CSS variables, you still have all the power of Sass. **This is still in-progress and will take time to fully implement.** +With v5.1.1, we standardized our required `@import`s across all our CSS bundles (including `bootstrap.css`, `bootstrap-reboot.css`, and `bootstrap-grid.css`) to include `_root.scss`. This adds `:root` level CSS variables to all bundles, regardless of how many of them are used in that bundle. Ultimately Bootstrap 5 will continue to see more [CSS variables]([[docsref:/customize/css-variables]]) added over time, in order to provide more real-time customization without the need to always recompile Sass. Our approach is to take our source Sass variables and transform them into CSS variables. That way, even if you don’t use CSS variables, you still have all the power of Sass. **This is still in-progress and will take time to fully implement.** For example, consider these `:root` CSS variables for common `` styles: -{{< scss-docs name="root-body-variables" file="scss/_root.scss" >}} + In practice, those variables are then applied in Reboot like so: -{{< scss-docs name="reboot-body-rules" file="scss/_reboot.scss" >}} + Which allows you to make real-time customizations however you like: @@ -51,7 +49,7 @@ The `` and `` elements are updated to provide better page-wide defau ## Native font stack -Bootstrap utilizes a "native font stack" or "system font stack" for optimum text rendering on every device and OS. These system fonts have been designed specifically with today's devices in mind, with improved rendering on screens, variable font support, and more. Read more about [native font stacks in this *Smashing Magazine* article](https://www.smashingmagazine.com/2015/11/using-system-ui-fonts-practical-guide/). +Bootstrap utilizes a “native font stack” or “system font stack” for optimum text rendering on every device and OS. These system fonts have been designed specifically with today’s devices in mind, with improved rendering on screens, variable font support, and more. Read more about [native font stacks in this *Smashing Magazine* article](https://www.smashingmagazine.com/2015/11/using-system-ui-fonts-practical-guide/). ```scss $font-family-sans-serif: @@ -64,7 +62,7 @@ $font-family-sans-serif: // Android Roboto, // older macOS and iOS - "Helvetica Neue" + "Helvetica Neue", // Linux "Noto Sans", "Liberation Sans", @@ -76,15 +74,15 @@ $font-family-sans-serif: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !default; ``` -Note that because the font stack includes emoji fonts, many common symbol/dingbat Unicode characters will be rendered as multicolored pictographs. Their appearance will vary, depending on the style used in the browser/platform's native emoji font, and they won't be affected by any CSS `color` styles. +Note that because the font stack includes emoji fonts, many common symbol/dingbat Unicode characters will be rendered as multicolored pictographs. Their appearance will vary, depending on the style used in the browser/platform’s native emoji font, and they won’t be affected by any CSS `color` styles. This `font-family` is applied to the `` and automatically inherited globally throughout Bootstrap. To switch the global `font-family`, update `$font-family-base` and recompile Bootstrap. -## Headings and paragraphs +## Headings -All heading elements—e.g., `

    `—and `

    ` are reset to have their `margin-top` removed. Headings have `margin-bottom: .5rem` added and paragraphs `margin-bottom: 1rem` for easy spacing. +All heading elements—`

    `—`

    ` have their `margin-top` removed, `margin-bottom: .5rem` set, and `line-height` tightened. While headings inherit their `color` by default, you can also override it via optional CSS variable, `--bs-heading-color`. -{{< bs-table "table" >}} + | Heading | Example | | --- | --- | | `

    ` | h1. Bootstrap heading | @@ -93,29 +91,47 @@ All heading elements—e.g., `

    `—and `

    ` are reset to have their `margin- | `

    ` | h4. Bootstrap heading | | `
    ` | h5. Bootstrap heading | | `
    ` | h6. Bootstrap heading | -{{< /bs-table >}} +
    + +## Paragraphs + +All `

    ` elements have their `margin-top` removed and `margin-bottom: 1rem` set for easy spacing. + +This is an example paragraph.

    `} /> + +## Links + +Links have a default `color` and underline applied. While links change on `:hover`, they don’t change based on whether someone `:visited` the link. They also receive no special `:focus` styles. + +This is an example link`} /> + +As of v5.3.x, link `color` is set using `rgba()` and new `-rgb` CSS variables, allowing for easy customization of link color opacity. Change the link color opacity with the `--bs-link-opacity` CSS variable: + +This is an example link`} /> + + +Placeholder links—those without an `href`—are targeted with a more specific selector and have their `color` and `text-decoration` reset to their default values. + +This is a placeholder link`} /> ## Horizontal rules The `
    ` element has been simplified. Similar to browser defaults, `
    `s are styled via `border-top`, have a default `opacity: .25`, and automatically inherit their `border-color` via `color`, including when `color` is set via the parent. They can be modified with text, border, and opacity utilities. -{{< example >}} -
    + -
    -
    -
    +
    +
    +
    -
    -
    -{{< /example >}} +
    +
    `} /> ## Lists -All lists—`
      `, `
        `, and `
        `—have their `margin-top` removed and a `margin-bottom: 1rem`. Nested lists have no `margin-bottom`. We've also reset the `padding-left` on `
          ` and `
            ` elements. +All lists—`
              `, `
                `, and `
                `—have their `margin-top` removed and a `margin-bottom: 1rem`. Nested lists have no `margin-bottom`. We’ve also reset the `padding-left` on `
                  ` and `
                    ` elements.
                    -{{< markdown >}} * All lists have their top margin removed * And their bottom margin normalized * Nested lists have no bottom margin @@ -123,109 +139,96 @@ All lists—`
                      `, `
                        `, and `
                        `—have their `margin-top` removed and a ` * Particularly when followed by more list items * The left padding has also been reset -1. Here's an ordered list +1. Here’s an ordered list 2. With a few list items 3. It has the same overall look 4. As the previous unordered list -{{< /markdown >}}
                    For simpler styling, clear hierarchy, and better spacing, description lists have updated `margin`s. `
                    `s reset `margin-left` to `0` and add `margin-bottom: .5rem`. `
                    `s are **bolded**. -
                    -
                    -
                    Description lists
                    -
                    A description list is perfect for defining terms.
                    -
                    Term
                    -
                    Definition for the term.
                    -
                    A second definition for the same term.
                    -
                    Another term
                    -
                    Definition for this other term.
                    -
                    -
                    + +
                    Description lists
                    +
                    A description list is perfect for defining terms.
                    +
                    Term
                    +
                    Definition for the term.
                    +
                    A second definition for the same term.
                    +
                    Another term
                    +
                    Definition for this other term.
                    +
                +`} /> ## Inline code Wrap inline snippets of code with ``. Be sure to escape HTML angle brackets. -{{< example >}} -For example, <section> should be wrapped as inline. -{{< /example >}} +<section> should be wrapped as inline.`} /> ## Code blocks Use `
                `s for multiple lines of code. Once again, be sure to escape any angle brackets in the code for proper rendering. The `
                ` element is reset to remove its `margin-top` and use `rem` units for its `margin-bottom`.
                 
                -{{< example >}}
                -
                <p>Sample text here...</p>
                +<p>Sample text here...</p>
                 <p>And another line of sample text here...</p>
                -
                -{{< /example >}} +
                `} /> ## Variables For indicating variables use the `` tag. -{{< example >}} -y = mx + b -{{< /example >}} +y = mx + b`} /> ## User input Use the `` to indicate input that is typically entered via keyboard. -{{< example >}} -To switch directories, type cd followed by the name of the directory.
                -To edit settings, press ctrl + , -{{< /example >}} +cd
                followed by the name of the directory.
                +To edit settings, press Ctrl + ,`} /> ## Sample output For indicating sample output from a program use the `` tag. -{{< example >}} -This text is meant to be treated as sample output from a computer program. -{{< /example >}} +This text is meant to be treated as sample output from a computer program.`} /> ## Tables -Tables are slightly adjusted to style ``s, collapse borders, and ensure consistent `text-align` throughout. Additional changes for borders, padding, and more come with [the `.table` class]({{< docsref "/content/tables" >}}). +Tables are slightly adjusted to style ``s, collapse borders, and ensure consistent `text-align` throughout. Additional changes for borders, padding, and more come with [the `.table` class]([[docsref:/content/tables]]). -{{< example >}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                - This is an example table, and this is its caption to describe the contents. -
                Table headingTable headingTable headingTable heading
                Table cellTable cellTable cellTable cell
                Table cellTable cellTable cellTable cell
                Table cellTable cellTable cellTable cell
                -{{< /example >}} + + + This is an example table, and this is its caption to describe the contents. + + + + Table heading + Table heading + Table heading + Table heading + + + + + Table cell + Table cell + Table cell + Table cell + + + Table cell + Table cell + Table cell + Table cell + + + Table cell + Table cell + Table cell + Table cell + + + `} /> ## Forms @@ -235,45 +238,47 @@ Various form elements have been rebooted for simpler base styles. Here are some - ``s, like fieldsets, have also been restyled to be displayed as a heading of sorts. - ` +`} /> + +## Datalists + +Datalists allow you to create a group of `