Compare commits

...

49 Commits

Author SHA1 Message Date
Mark Otto 418529f336 New avatar component 2026-01-06 12:06:58 -08:00
Mark Otto 65a4072b55 Fix some scss-docs 2026-01-05 23:07:16 -08:00
Mark Otto 627a295ef6 Run dist while here 2026-01-05 23:07:16 -08:00
Mark Otto 7cfdeeabef Bump bundlewatch while here 2026-01-05 23:07:16 -08:00
Mark Otto 6b27acfc85 Add stylelint order plugin 2026-01-05 23:07:16 -08:00
Mark Otto 1438f19f7f Follow-up fix to datepicker I forgot to push 2026-01-05 22:46:05 -08:00
Mark Otto 7f774f4f99 New Datepicker plugin via Vanilla Calendar Pro, new .form-adorn component (#41965)
* First pass at datepicker via Vanilla Calendar Pro

* fixes

* optimize

* Docs updates, add advanced config

* rename attr

* edits

* Update datepicker docs, improve color modes, add tests

* New .form-adorn component for overlaying icons and text with inputs

* temp

* bump limits

* cleanup and simpler selectors

* few more tweaks

* Remove comment

* Fix multi-month, reorg some docs content, fix selections
2026-01-05 22:43:56 -08:00
Mark Otto 540a2ed46c Add tests for new form components 2026-01-05 22:39:08 -08:00
Mark Otto a79ca065d8 New OTP input (#41981)
* feat: add OTP input component

- Add OtpInput JavaScript component with keyboard navigation and paste support
- Add SCSS styles for OTP input fields
- Add documentation page for OTP input
- Add unit tests for OTP input

* Bump bundlewatch

* Missed file
2025-12-29 14:59:21 -08:00
Mark Otto eebf75b98b Password strength plugin (#41980)
* feat: add password strength component

- Add Strength JavaScript component with customizable scoring
- Add SCSS styles for strength meter and bar variants
- Add documentation page for password strength
- Add unit tests for strength component

* Bundle bump

* More bundle
2025-12-29 14:47:01 -08:00
Mark Otto f0788d5f98 New Toggler plugin (#41966)
* Toggler compontent from upstrea PR

* Update docs

* Fix comments, data_key, and cleanup some code; add tests too

* bump

* edits
2025-12-29 11:03:26 -08:00
Mark Otto 8cfc08d23f First pass at submenu support (#41967)
* First pass at submenu support

* Remove unused constants

* Fix up linter errors

* Logical properties for placement

* Better docs playground for dropdowns

* refactor and update bundles

* more tests, fix broken tests

* more tests

* more

* more
2025-12-28 17:45:22 -08:00
Mark Otto 5b1a686d3a Additional .theme-* utilities to replace color background helpers and more (#41978)
* New .theme-* utilities for consuming the existing .theme styles, replace color-background helpers

* Update blue to be less indigo

* Rebuild metadata

* mdx lint

* Fix docs link while here

* Fix up some docs work while here

* links
2025-12-27 12:59:42 -08:00
Mark Otto 983f589253 More button cleanup (#41968)
* Clean up button vars, docs, fix up .btn-link

* more buttons docs cleanup

* Remove colored links for theme utilities

* Fix scssdocs

* Fix broken link

* Fix another link
2025-12-27 11:27:00 -08:00
Mark Otto d67059f02d Update cards for v6 (#41964)
* Start process of redoing Cards

* better

* So much better

* fix link
2025-12-21 21:06:53 -08:00
Mark Otto 31f30256f7 Breadcrumbs (#41956)
* Redo breadcrumb

* Cleanup
2025-12-21 20:57:32 -08:00
Mark Otto 4b90546519 Add a tabular bundlewatch script (#41957)
* Script for better bundlewatch locally

* Fix linter
2025-12-21 11:16:35 -08:00
Mark Otto 2d1e26710b Update some badge stuff (#41955) 2025-12-18 22:36:41 -08:00
Mark Otto 3b9e01f4d1 Improve accordion (#41953) 2025-12-18 22:32:07 -08:00
Mark Otto c3c3a9f815 Update Alert and tweak some divider styles (#41954)
* Redo some alerts CSS, fix hr and vr components

* fix link
2025-12-18 22:31:46 -08:00
Mark Otto e8516ff87b Fix broke thing 2025-12-18 14:55:48 -08:00
Mark Otto 975a8a5230 Prefix CSS variables with PostCSS (#41951)
* Add custom property prefixing postcss plugin

* Remove $prefix variable, replace with PostCSS plugin to prefix CSS variables
2025-12-18 14:54:44 -08:00
Mark Otto 498de08235 Rename some components to be singular (#41952)
* Singular spinner

* Rename some component files to be singular
2025-12-18 14:46:26 -08:00
Mark Otto cd7474164c Standardize focus styles using focus-ring mixin (#41950)
* Standardize focus styles using focus-ring mixin

Replace box-shadow focus styles with consistent focus-ring() mixin across components:
- Accordion, nav, pagination now use @include focus-ring(true)
- Forms (checkboxes, radios, switches, range, controls) use focus-ring mixin
- Update focus-ring-offset default from -1px to 1px
- Remove deprecated *-focus-box-shadow variables

* Fix some focus styles

* Remove unused CSS

* Remove more box-shadow on buttons
2025-12-18 13:13:09 -08:00
Mark Otto b82c8e4018 Upgrade to Sass v1.95.0, redo if() style (#41943)
* Migrate to latest Sass, redo if() style

* Refactor and disable
2025-12-18 09:15:26 -08:00
Mark Otto 64d106f104 Generate dist in v6-dev for first time (#41945)
* Generate dist in v6-dev for first time

* Rebuild after browserslist upgrade
2025-12-18 08:55:49 -08:00
Mark Otto 82b0a25b17 Update browserslistrc (#41947) 2025-12-18 08:49:30 -08:00
Mark Otto 3d9900bd04 Generate badge variants (#41942)
* Generate badge variants

* Docs example
2025-12-17 09:24:30 -08:00
Mark Otto 7f8ba319c8 Migrate from Popper to Floating UI (#41941)
* Migrate to Floating UI for tooltips, popovers, dropdowns

* Bump bundlewatch

* Dropdown tests

* add floating ui tests from claude

* more

* build sri

* more tests while here
2025-12-17 09:23:57 -08:00
Mark Otto 5d30bd84fb Remove RTLCSS
- Remove RTL CSS files from dist and zip script
- Update Sass to use more logical properties
- Misc docs and CSS fixes
2025-12-16 20:03:26 -08:00
Mark Otto 7c99bef822 Switch from xlink:href to href on SVG use 2025-12-16 20:02:59 -08:00
Mark Otto fd77fa3a25 Remove RFS from v6 (#41938)
* Unrelated: fix link

* Clean up elsewhere

* Remove RFS from Sass and docs

* Linter errors
2025-12-16 16:41:53 -08:00
Mark Otto 047932ad58 More close button updates (#41937)
* Clean up close button more

* New placeholder for docs examples

* CSS lint fix
2025-12-16 14:05:02 -08:00
Mark Otto 2bf91e5434 Fix some sidebar sizing (#41936) 2025-12-16 12:58:15 -08:00
Mark Otto 4ad3c27893 Rebuild Close button component (#41935)
- Use inline SVGs instead of embedded for CSP
- Also allows greater customization
- Modifies examples to use the latest
2025-12-16 12:02:35 -08:00
Mark Otto d57273bb91 Update Prose to use flexbox, reduce overhead with docs styles (#41934)
* Update Prose to use flexbox, reduce overhead with docs styles

* Fix up some table styles
2025-12-16 12:02:03 -08:00
Mark Otto 37161d79bf Fix up button group with latest buttons (#41933) 2025-12-16 11:19:22 -08:00
Mark Otto 2f20144270 Replace Modal with new Dialog component (#41917)
* Add Dialog component using native HTML dialog element

New component that leverages the native HTML <dialog> element for modals
and non-modal dialogs with built-in backdrop and accessibility support.

Features:
- Modal dialogs using showModal() with automatic backdrop
- Non-modal dialogs using show() for persistent UI elements
- Static backdrop option (prevents close on outside click)
- Keyboard support (Escape to close, focus trapping for modals)
- Smooth open/close animations via CSS
- Events: show, shown, hide, hidden, hidePrevented
- Data API for toggling with data-bs-toggle="dialog"

JavaScript:
- js/src/dialog.js - Main component class
- js/tests/unit/dialog.spec.js - Unit tests
- js/tests/visual/dialog.html - Visual test page

SCSS:
- scss/_dialog.scss - Component styles

Docs:
- Add dialog component documentation
- Update modal docs with dialog references

* Modal examples now Dialog examples, needs improvement

* Remove all of Modal

* real words

* Fix layout while I'm here

* Lint Markdown

* New dialog size options
2025-12-16 11:19:06 -08:00
Mark Otto 5645c69826 Button cleanup (#41928) 2025-12-15 15:54:20 -08:00
Mark Otto 0ab2ffbe35 Set max-width for bd-content to 100% 2025-12-15 15:33:37 -08:00
Mark Otto 353324bd94 Fix badge theme variable fallback order (#41920)
Move --bs-theme-* variable usage from CSS variable definitions to property
declarations. This ensures the fallback works correctly:
- color: var(--bs-theme-contrast, var(--bs-badge-color))
- background-color: var(--bs-theme-bg, var(--bs-badge-bg))

Also fixes .badge-subtle variant to use same pattern.
2025-12-11 15:46:27 -08:00
Mark Otto 94bfbe5528 Refactor CSS variables and design tokens (#41922)
- Rename --bs-color-* to --bs-fg-* for foreground colors
- Simplify font-size variables (remove clamp for xs/sm/md)
- Remove deprecated secondary/tertiary color variables
- Clean up commented-out legacy variable declarations
2025-12-11 15:46:09 -08:00
Mark Otto 23381465c3 New docs structure (#41919)
* Reorganize documentation structure

Getting Started:
- Simplify introduction.mdx to focus on basics
- Add install.mdx with package manager instructions
- Move approach.mdx from Extend to Getting Started
- Delete best-practices.mdx (merged elsewhere)
- Update javascript.mdx

Guides:
- Add quickstart.mdx for CDN-based quick start
- Add npm.mdx guide with screenshots
- Update parcel, vite, webpack guides

Extend:
- Remove approach.mdx (moved to Getting Started)
- Remove icons.mdx

Sidebar:
- Restructure navigation to reflect new organization
- Add Install and Approach to Getting Started
- Add Quickstart and npm to Guides

Also includes minor doc updates:
- customize/sass.mdx
- utilities/background.mdx
- utilities/colors.mdx

* Optimised images with calibre/image-actions

* fix broken link and update sidebar

* Optimised images with calibre/image-actions

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-11 15:45:35 -08:00
Mark Otto a95c4fcbcf Run jobs on v6-dev too 2025-12-11 14:53:17 -08:00
Mark Otto a98aacc9b6 Docs design updates (#41918)
* Refactor docs site styling and Shiki integration

Code Highlighting:
- Enhance Code.astro with Shiki transformers and Bootstrap theme
- Add tab support for multi-language code examples
- Add toolbar with language labels and copy button
- Support diff highlighting via @shikijs/transformers

Components:
- Update Example.astro styling and structure
- Refine DocsSidebar.astro layout
- Update Navigation, Versions, and GetStarted components
- Update ThemeToggler.astro
- Minor updates to ReferenceTable, Swatch components

Site SCSS:
- Restyle code snippets and examples (_component-examples.scss)
- Update syntax highlighting styles (_syntax.scss)
- Refine sidebar styling (_sidebar.scss)
- Update search component styles (_search.scss)
- Update navbar styles (_navbar.scss)
- Refresh callouts, clipboard, content styles

Config & Assets:
- Update astro.config.ts for Shiki
- Add sticky.js partial
- Update application.js
- Update guide screenshots (parcel, vite, webpack)

* fixes

* update images

* update permissions

* Optimised images with calibre/image-actions

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-11 14:10:51 -08:00
Mark Otto 54808a9187 Refactor nav and navbar components
- Add $nav-gap and $nav-link-gap variables for flexbox gap layout
- Rename nav link color variables to use --bs-fg-* tokens
- Add active state variables ($nav-link-active-color, $nav-link-active-bg)
- Add hover background variable ($nav-link-hover-bg)
- Update .nav and .nav-link to use flexbox with gap
- Update related documentation
2025-12-11 10:26:46 -08:00
Mark Otto 881ad1d055 Migrate from Prism to Shiki (#41821) 2025-12-11 09:54:53 -08:00
Copilot acdda008c3 Remove browsers and devices documentation page (#41875)
* Initial plan

* Initial commit - planning to remove browsers-devices page

Co-authored-by: mdo <98681+mdo@users.noreply.github.com>

* Remove browsers-devices page and all references

Co-authored-by: mdo <98681+mdo@users.noreply.github.com>

* Add browser support info to Introduction page

Co-authored-by: mdo <98681+mdo@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mdo <98681+mdo@users.noreply.github.com>
Co-authored-by: Mark Otto <markd.otto@gmail.com>
2025-11-14 22:05:52 -08:00
Copilot 43a07ccc19 Remove Contents page from documentation (#41876)
* Initial plan

* Remove Contents page from docs and all references

Co-authored-by: mdo <98681+mdo@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mdo <98681+mdo@users.noreply.github.com>
2025-11-14 22:00:47 -08:00
391 changed files with 51751 additions and 57095 deletions
+4 -6
View File
@@ -1,12 +1,10 @@
# https://github.com/browserslist/browserslist#readme
>= 0.5%
last 2 major versions
not dead
Chrome >= 60
Firefox >= 60
Firefox ESR
iOS >= 12
Safari >= 12
Chrome >= 120
Firefox >= 121
iOS >= 15.6
Safari >= 15.6
not Explorer <= 11
not kaios <= 2.5 # fix floating label issues in Firefox (see https://github.com/postcss/autoprefixer/issues/1533)
+14 -14
View File
@@ -2,59 +2,59 @@
"files": [
{
"path": "./dist/css/bootstrap-grid.css",
"maxSize": "10.0 kB"
"maxSize": "9.5 kB"
},
{
"path": "./dist/css/bootstrap-grid.min.css",
"maxSize": "9.0 kB"
"maxSize": "8.5 kB"
},
{
"path": "./dist/css/bootstrap-reboot.css",
"maxSize": "5.5 kB"
"maxSize": "5.25 kB"
},
{
"path": "./dist/css/bootstrap-reboot.min.css",
"maxSize": "4.5 kB"
"maxSize": "4.25 kB"
},
{
"path": "./dist/css/bootstrap-utilities.css",
"maxSize": "15.25 kB"
"maxSize": "14.25 kB"
},
{
"path": "./dist/css/bootstrap-utilities.min.css",
"maxSize": "13.5 kB"
"maxSize": "12.5 kB"
},
{
"path": "./dist/css/bootstrap.css",
"maxSize": "37.5 kB"
"maxSize": "37.75 kB"
},
{
"path": "./dist/css/bootstrap.min.css",
"maxSize": "33.75 kB"
"maxSize": "34.0 kB"
},
{
"path": "./dist/js/bootstrap.bundle.js",
"maxSize": "43.0 kB"
"maxSize": "67.75 kB"
},
{
"path": "./dist/js/bootstrap.bundle.min.js",
"maxSize": "23.5 kB"
"maxSize": "41.0 kB"
},
{
"path": "./dist/js/bootstrap.esm.js",
"maxSize": "28.0 kB"
"maxSize": "39.0 kB"
},
{
"path": "./dist/js/bootstrap.esm.min.js",
"maxSize": "18.25 kB"
"maxSize": "24.0 kB"
},
{
"path": "./dist/js/bootstrap.js",
"maxSize": "28.75 kB"
"maxSize": "39.75 kB"
},
{
"path": "./dist/js/bootstrap.min.js",
"maxSize": "16.25 kB"
"maxSize": "21.25 kB"
}
],
"ci": {
+1
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -18,7 +18,8 @@ jobs:
name: calibreapp/image-actions
runs-on: ubuntu-latest
permissions:
# allow calibreapp/image-actions to update PRs
# allow calibreapp/image-actions to update PRs and commit compressed images
contents: write
pull-requests: write
steps:
- name: Clone repository
+2
View File
@@ -5,11 +5,13 @@ on:
branches:
- main
- v4-dev
- v6-dev
- "!dependabot/**"
pull_request:
branches:
- main
- v4-dev
- v6-dev
- "!dependabot/**"
schedule:
- cron: "0 2 * * 4"
+1
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
+1
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
+1
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
+1
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
+1
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
+1
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- main
- v6-dev
workflow_dispatch:
permissions:
+1 -1
View File
@@ -12,7 +12,7 @@ on:
schedule:
- cron: '27 12 * * 2'
push:
branches: [ "main" ]
branches: [ "main", "v6-dev" ]
# Declare default permissions as read only.
permissions: read-all
+15
View File
@@ -2,8 +2,23 @@
"extends": [
"stylelint-config-twbs-bootstrap"
],
"plugins": [
"stylelint-order"
],
"reportInvalidScopeDisables": true,
"reportNeedlessDisables": true,
"rules": {
"order/order": [
[
{ "type": "at-rule", "name": "use" },
{ "type": "at-rule", "name": "forward" },
"dollar-variables",
"custom-properties",
"declarations",
"rules"
]
]
},
"overrides": [
{
"files": "**/*.scss",
+1 -1
View File
@@ -52,7 +52,7 @@ Several quick start options are available:
- 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.3/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/) for information on the framework contents, templates, examples, and more.
## Status
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env node
import { execSync } from 'node:child_process'
// Run bundlewatch and capture output
let stdout
let exitCode = 0
try {
stdout = execSync('npx bundlewatch --config .bundlewatch.config.json', {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe']
})
} catch (error) {
stdout = error.stdout || ''
exitCode = error.status || 1
}
// Parse lines that contain PASS or FAIL
const lines = stdout.split('\n').filter(l => l.startsWith('PASS') || l.startsWith('FAIL'))
if (lines.length === 0) {
console.log(stdout)
process.exit(exitCode)
}
// Parse size string to number (KB)
const parseSize = str => Number.parseFloat(str.replace('KB', ''))
// Calculate column widths and headroom
const rows = lines.map(line => {
const match = line.match(/(PASS|FAIL)\s+(.+?):\s+([\d.]+KB)\s+([<>])\s+([\d.]+KB)/)
if (match) {
const sizeNum = parseSize(match[3])
const maxNum = parseSize(match[5])
const headroomNum = maxNum - sizeNum
const headroom = `${headroomNum.toFixed(2)}KB`
return {
status: match[1],
file: match[2],
size: match[3],
max: match[5],
headroomNum,
headroom: match[1] === 'PASS' ? `+${headroom}` : `-${Math.abs(headroomNum).toFixed(2)}KB`
}
}
return null
}).filter(Boolean)
const maxFileLen = Math.max(...rows.map(r => r.file.length), 4)
const maxSizeLen = Math.max(...rows.map(r => r.size.length), 4)
const maxMaxLen = Math.max(...rows.map(r => r.max.length), 3)
const maxHeadroomLen = Math.max(...rows.map(r => r.headroom.length), 8)
// Build table
const hr = `+-${'-'.repeat(maxFileLen)}-+-${'-'.repeat(maxSizeLen)}-+-${'-'.repeat(maxMaxLen)}-+-${'-'.repeat(maxHeadroomLen)}-+`
console.log('')
console.log('bundlewatch results')
console.log(hr)
console.log(`| ${'File'.padEnd(maxFileLen)} | ${'Size'.padStart(maxSizeLen)} | ${'Max'.padStart(maxMaxLen)} | ${'Headroom'.padStart(maxHeadroomLen)} |`)
console.log(hr)
const green = '\u001B[32m'
const red = '\u001B[31m'
const reset = '\u001B[0m'
for (const row of rows) {
const sizeColor = row.status === 'PASS' ? green : red
const coloredSize = `${sizeColor}${row.size.padStart(maxSizeLen)}${reset}`
const headroomColor = row.headroomNum > 0.25 ? red : ''
const headroomReset = row.headroomNum > 0.25 ? reset : ''
const coloredHeadroom = `${headroomColor}${row.headroom.padStart(maxHeadroomLen)}${headroomReset}`
console.log(`| ${row.file.padEnd(maxFileLen)} | ${coloredSize} | ${row.max.padStart(maxMaxLen)} | ${coloredHeadroom} |`)
}
console.log(hr)
// Summary
const passed = rows.filter(r => r.status === 'PASS').length
const failed = rows.filter(r => r.status === 'FAIL').length
console.log('')
if (failed > 0) {
console.log(`\u001B[31mbundlewatch FAIL\u001B[0m - ${passed} passed, ${failed} failed`)
} else {
console.log(`\u001B[32mbundlewatch PASS\u001B[0m - ${passed}/${rows.length} files within limits`)
}
console.log('')
process.exit(exitCode)
+2 -2
View File
@@ -38,8 +38,8 @@ const files = [
configPropertyName: 'js_bundle_hash'
},
{
file: 'node_modules/@popperjs/core/dist/umd/popper.min.js',
configPropertyName: 'popper_hash'
file: 'node_modules/@floating-ui/dom/dist/floating-ui.dom.umd.min.js',
configPropertyName: 'floating_ui_hash'
}
]
+16 -4
View File
@@ -27,13 +27,22 @@ $total-utilities: list.length(map.keys($utilities-map)) !default;
// Skip if utility is null or false (disabled)
@if $utility {
// Extract class prefix
$class: if(map.has-key($utility, "class"), map.get($utility, "class"), $key);
$class: $key;
@if map.has-key($utility, "class") {
$class: map.get($utility, "class");
}
// Extract property
$property: if(map.has-key($utility, "property"), map.get($utility, "property"), null);
$property: null;
@if map.has-key($utility, "property") {
$property: map.get($utility, "property");
}
// Extract values
$values: if(map.has-key($utility, "values"), map.get($utility, "values"), null);
$values: null;
@if map.has-key($utility, "values") {
$values: map.get($utility, "values");
}
// Generate class list
$classes: "";
@@ -45,7 +54,10 @@ $total-utilities: list.length(map.keys($utilities-map)) !default;
@if not $first {
$classes: $classes + ", ";
}
$class-name: if($value-key == "null" or $value-key == null, $class, "#{$class}-#{$value-key}");
$class-name: "#{$class}-#{$value-key}";
@if $value-key == "null" or $value-key == null {
$class-name: $class;
}
$classes: $classes + '"' + $class-name + '"';
$first: false;
}
+10 -5
View File
@@ -1,3 +1,6 @@
import postcssPrefixCustomProperties from 'postcss-prefix-custom-properties'
import autoprefixer from 'autoprefixer'
const mapConfig = {
inline: false,
annotation: true,
@@ -7,10 +10,12 @@ const mapConfig = {
export default context => {
return {
map: context.file.dirname.includes('examples') ? false : mapConfig,
plugins: {
autoprefixer: {
cascade: false
}
}
plugins: [
postcssPrefixCustomProperties({
prefix: 'bs-',
ignore: [/^--bs-/, /^--bd-/]
}),
autoprefixer({ cascade: false })
]
}
}
+4 -4
View File
@@ -12,7 +12,7 @@ const BUNDLE = process.env.BUNDLE === 'true'
const ESM = process.env.ESM === 'true'
let destinationFile = `bootstrap${ESM ? '.esm' : ''}`
const external = ['@popperjs/core']
const external = ['@floating-ui/dom']
const plugins = [
babel({
// Only transpile our source code
@@ -22,14 +22,14 @@ const plugins = [
})
]
const globals = {
'@popperjs/core': 'Popper'
'@floating-ui/dom': 'FloatingUIDOM'
}
if (BUNDLE) {
destinationFile += '.bundle'
// Remove last entry in external array to bundle Popper
// Remove last entry in external array to bundle Floating UI
external.pop()
delete globals['@popperjs/core']
delete globals['@floating-ui/dom']
plugins.push(
replace({
'process.env.NODE_ENV': '"production"',
+1 -3
View File
@@ -26,9 +26,7 @@ const docsDir = `${rootDocsDir}/docs/${versionShort}/`
// these are the files we need in the examples
const cssFiles = [
'bootstrap.min.css',
'bootstrap.min.css.map',
'bootstrap.rtl.min.css',
'bootstrap.rtl.min.css.map'
'bootstrap.min.css.map'
]
const jsFiles = [
'bootstrap.bundle.min.js',
+6 -6
View File
@@ -35,14 +35,14 @@ download:
cdn:
# See https://www.srihash.org for info on how to generate the hashes
css: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css"
css_hash: "sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB"
css_hash: "sha384-TDmpFhAO5TwSQwPF95I/odgwpTUuv0aaVm9/0fL7b+kKe7hFBp/+9cBCMkydgGOi"
js: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.min.js"
js_hash: "sha384-G/EV+4j2dNv+tEPo3++6LCgdCROaejBqfUeNjuKAiuXbjrxilcCdDz6ZAVfHWe1Y"
js_hash: "sha384-Php492snRLTR5p+hMyxpV6gYwp1avWXn4AaX31MgANrvsjr9Dpodl3Nw60L7Pewl"
js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
js_bundle_hash: "sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
popper: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"
popper_hash: "sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r"
popper_esm: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/esm/popper.min.js"
js_bundle_hash: "sha384-I2J4jlw924JZXHU9un9Mcuixq/rKhd5A8/B1NQ6ifPAiBFacZjwNcec8d6L38jQv"
floating_ui: "https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.7.0/dist/floating-ui.dom.umd.min.js"
floating_ui_hash: "sha384-R7p1RqabZNhI+RdPNIzTouzd/LBVorZ0Tn3ApcogSOk+HF3o+P0HIenrUw/n0MOj"
floating_ui_esm: "https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.7.0/dist/floating-ui.dom.esm.min.js"
anchors:
min: 2
+4401 -3852
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -4
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-4084
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+803 -561
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -4
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-598
View File
@@ -1,598 +0,0 @@
/*!
* Bootstrap Reboot v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
line-height: inherit;
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type=search]::-webkit-search-cancel-button {
cursor: pointer;
filter: grayscale(1);
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+5057 -4848
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+59 -32
View File
@@ -19,7 +19,7 @@
},
"aspect-ratio": {
"class": "ratio",
"property": "--bs-ratio",
"property": "--ratio",
"classes": [
"ratio-auto",
"ratio-1x1",
@@ -120,7 +120,7 @@
},
"focus-ring": {
"class": "focus-ring",
"property": "--bs-focus-ring-color",
"property": "--focus-ring-color",
"classes": [
"focus-ring-primary",
"focus-ring-accent",
@@ -522,7 +522,7 @@
},
"grid-column-counts": {
"class": "grid-cols",
"property": "--bs-columns",
"property": "--columns",
"classes": [
"grid-cols-3",
"grid-cols-4",
@@ -574,7 +574,7 @@
},
"margin-x": {
"class": "mx",
"property": "margin-right margin-left",
"property": "margin-inline",
"classes": [
"mx-0",
"mx-1",
@@ -587,7 +587,7 @@
},
"margin-y": {
"class": "my",
"property": "margin-top margin-bottom",
"property": "margin-block",
"classes": [
"my-0",
"my-1",
@@ -600,7 +600,7 @@
},
"margin-top": {
"class": "mt",
"property": "margin-top",
"property": "margin-block-start",
"classes": [
"mt-0",
"mt-1",
@@ -613,7 +613,7 @@
},
"margin-end": {
"class": "me",
"property": "margin-right",
"property": "margin-inline-end",
"classes": [
"me-0",
"me-1",
@@ -626,7 +626,7 @@
},
"margin-bottom": {
"class": "mb",
"property": "margin-bottom",
"property": "margin-block-end",
"classes": [
"mb-0",
"mb-1",
@@ -639,7 +639,7 @@
},
"margin-start": {
"class": "ms",
"property": "margin-left",
"property": "margin-inline-start",
"classes": [
"ms-0",
"ms-1",
@@ -664,7 +664,7 @@
},
"padding-x": {
"class": "px",
"property": "padding-right padding-left",
"property": "padding-inline",
"classes": [
"px-0",
"px-1",
@@ -676,7 +676,7 @@
},
"padding-y": {
"class": "py",
"property": "padding-top padding-bottom",
"property": "padding-block",
"classes": [
"py-0",
"py-1",
@@ -688,7 +688,7 @@
},
"padding-top": {
"class": "pt",
"property": "padding-top",
"property": "padding-block-start",
"classes": [
"pt-0",
"pt-1",
@@ -700,7 +700,7 @@
},
"padding-end": {
"class": "pe",
"property": "padding-right",
"property": "padding-inline-end",
"classes": [
"pe-0",
"pe-1",
@@ -712,7 +712,7 @@
},
"padding-bottom": {
"class": "pb",
"property": "padding-bottom",
"property": "padding-block-end",
"classes": [
"pb-0",
"pb-1",
@@ -724,7 +724,7 @@
},
"padding-start": {
"class": "ps",
"property": "padding-left",
"property": "padding-inline-start",
"classes": [
"ps-0",
"ps-1",
@@ -899,6 +899,7 @@
"fg-1",
"fg-2",
"fg-3",
"fg-4",
"fg-white",
"fg-black",
"fg-inherit"
@@ -917,6 +918,19 @@
"fg-emphasis-secondary"
]
},
"fg-contrast": {
"class": "fg-contrast",
"classes": [
"fg-contrast-primary",
"fg-contrast-accent",
"fg-contrast-success",
"fg-contrast-danger",
"fg-contrast-warning",
"fg-contrast-info",
"fg-contrast-inverse",
"fg-contrast-secondary"
]
},
"fg-opacity": {
"class": "fg",
"property": "color",
@@ -933,19 +947,6 @@
"fg-100"
]
},
"fg-contrast": {
"class": "fg-contrast",
"classes": [
"fg-contrast-primary",
"fg-contrast-accent",
"fg-contrast-success",
"fg-contrast-danger",
"fg-contrast-warning",
"fg-contrast-info",
"fg-contrast-inverse",
"fg-contrast-secondary"
]
},
"link-opacity": {
"class": "link",
"property": "color",
@@ -1027,6 +1028,7 @@
"bg-1",
"bg-2",
"bg-3",
"bg-4",
"bg-white",
"bg-black",
"bg-transparent",
@@ -1075,6 +1077,31 @@
"bg-100"
]
},
"theme-contrast": {
"class": "theme-contrast",
"classes": [
"theme-contrast"
]
},
"theme-subtle": {
"class": "theme-subtle",
"classes": [
"theme-subtle"
]
},
"theme-muted": {
"class": "theme-muted",
"classes": [
"theme-muted"
]
},
"theme-border": {
"class": "theme-border",
"property": "border",
"classes": [
"theme-border"
]
},
"gradient": {
"class": "bg",
"property": "background-image",
@@ -1116,7 +1143,7 @@
},
"rounded-top": {
"class": "rounded-top",
"property": "border-top-left-radius border-top-right-radius",
"property": "border-start-start-radius border-start-end-radius",
"classes": [
"rounded-top",
"rounded-top-0",
@@ -1131,7 +1158,7 @@
},
"rounded-end": {
"class": "rounded-end",
"property": "border-top-right-radius border-bottom-right-radius",
"property": "border-end-end-radius border-end-start-radius",
"classes": [
"rounded-end",
"rounded-end-0",
@@ -1146,7 +1173,7 @@
},
"rounded-bottom": {
"class": "rounded-bottom",
"property": "border-bottom-right-radius border-bottom-left-radius",
"property": "border-end-end-radius border-end-start-radius",
"classes": [
"rounded-bottom",
"rounded-bottom-0",
@@ -1161,7 +1188,7 @@
},
"rounded-start": {
"class": "rounded-start",
"property": "border-bottom-left-radius border-top-left-radius",
"property": "border-start-start-radius border-start-end-radius",
"classes": [
"rounded-start",
"rounded-start-0",
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+11669 -11251
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -4
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
-12021
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3889 -2394
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2391 -676
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+2397 -698
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap alert.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap base-component.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap button.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap carousel.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap collapse.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+443
View File
@@ -0,0 +1,443 @@
/*!
* Bootstrap datepicker.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('vanilla-calendar-pro'), require('./base-component.js'), require('./dom/event-handler.js'), require('./util/index.js')) :
typeof define === 'function' && define.amd ? define(['vanilla-calendar-pro', './base-component', './dom/event-handler', './util/index'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Datepicker = factory(global["vanilla-calendar-pro"], global.BaseComponent, global.EventHandler, global.Index));
})(this, (function (vanillaCalendarPro, BaseComponent, EventHandler, index_js) { 'use strict';
/**
* --------------------------------------------------------------------------
* Bootstrap datepicker.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
const NAME = 'datepicker';
const DATA_KEY = 'bs.datepicker';
const EVENT_KEY = `.${DATA_KEY}`;
const DATA_API_KEY = '.data-api';
const EVENT_CHANGE = `change${EVENT_KEY}`;
const EVENT_SHOW = `show${EVENT_KEY}`;
const EVENT_SHOWN = `shown${EVENT_KEY}`;
const EVENT_HIDE = `hide${EVENT_KEY}`;
const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
const EVENT_FOCUSIN_DATA_API = `focusin${EVENT_KEY}${DATA_API_KEY}`;
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="datepicker"]';
const HIDE_DELAY = 100; // ms delay before hiding after selection
const Default = {
datepickerTheme: null,
// 'light', 'dark', 'auto' - explicit theme for datepicker popover only
dateMin: null,
dateMax: null,
dateFormat: null,
// Intl.DateTimeFormat options, or function(date, locale) => string
displayElement: null,
// Element to show formatted date (defaults to element for buttons)
displayMonthsCount: 1,
// Number of months to display side-by-side
firstWeekday: 1,
// Monday
inline: false,
// Render calendar inline (no popup)
locale: 'default',
positionElement: null,
// Element to position calendar relative to (defaults to input)
selectedDates: [],
selectionMode: 'single',
// 'single', 'multiple', 'multiple-ranged'
placement: 'left',
// 'left', 'center', 'right', 'auto'
vcpOptions: {} // Pass-through for any VCP option
};
const DefaultType = {
datepickerTheme: '(null|string)',
dateMin: '(null|string|number|object)',
dateMax: '(null|string|number|object)',
dateFormat: '(null|object|function)',
displayElement: '(null|string|element|boolean)',
displayMonthsCount: 'number',
firstWeekday: 'number',
inline: 'boolean',
locale: 'string',
positionElement: '(null|string|element)',
selectedDates: 'array',
selectionMode: 'string',
placement: 'string',
vcpOptions: 'object'
};
/**
* Class definition
*/
class Datepicker extends BaseComponent {
constructor(element, config) {
super(element, config);
this._calendar = null;
this._isShown = false;
this._initCalendar();
}
// Getters
static get Default() {
return Default;
}
static get DefaultType() {
return DefaultType;
}
static get NAME() {
return NAME;
}
// Public
toggle() {
if (this._config.inline) {
return; // Inline calendars are always visible
}
return this._isShown ? this.hide() : this.show();
}
show() {
if (this._config.inline) {
return; // Inline calendars are always visible
}
if (!this._calendar || index_js.isDisabled(this._element) || this._isShown) {
return;
}
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW);
if (showEvent.defaultPrevented) {
return;
}
this._calendar.show();
this._isShown = true;
EventHandler.trigger(this._element, EVENT_SHOWN);
}
hide() {
if (this._config.inline) {
return; // Inline calendars are always visible
}
if (!this._calendar || !this._isShown) {
return;
}
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);
if (hideEvent.defaultPrevented) {
return;
}
this._calendar.hide();
this._isShown = false;
EventHandler.trigger(this._element, EVENT_HIDDEN);
}
dispose() {
if (this._themeObserver) {
this._themeObserver.disconnect();
this._themeObserver = null;
}
if (this._calendar) {
this._calendar.destroy();
}
this._calendar = null;
super.dispose();
}
getSelectedDates() {
const dates = this._calendar?.context?.selectedDates;
return dates ? [...dates] : [];
}
setSelectedDates(dates) {
if (this._calendar) {
this._calendar.set({
selectedDates: dates
});
}
}
// Private
_initCalendar() {
this._isInput = this._element.tagName === 'INPUT';
this._isInline = this._config.inline;
// For inline mode, look for a hidden input child to bind to
if (this._isInline && !this._isInput) {
this._boundInput = this._element.querySelector('input[type="hidden"], input[name]');
}
this._positionElement = this._resolvePositionElement();
this._displayElement = this._resolveDisplayElement();
const calendarOptions = this._buildCalendarOptions();
// Create calendar on the position element (for correct popup positioning)
// but value updates still go to this._element (the input)
this._calendar = new vanillaCalendarPro.Calendar(this._positionElement, calendarOptions);
this._calendar.init();
// Watch for theme changes on ancestor elements (for live theme switching)
this._setupThemeObserver();
// Set initial value if input has a value
if (this._isInput && this._element.value) {
this._parseInputValue();
}
// Populate input/display with preselected dates
this._updateDisplayWithSelectedDates();
}
_updateDisplayWithSelectedDates() {
const {
selectedDates
} = this._config;
if (!selectedDates || selectedDates.length === 0) {
return;
}
const formattedDate = this._formatDateForInput(selectedDates);
if (this._isInput) {
this._element.value = formattedDate;
}
if (this._boundInput) {
this._boundInput.value = selectedDates.join(',');
}
if (this._displayElement) {
this._displayElement.textContent = formattedDate;
}
}
_resolvePositionElement() {
let {
positionElement
} = this._config;
if (typeof positionElement === 'string') {
positionElement = document.querySelector(positionElement);
}
// Use input's parent if in form-adorn
if (!positionElement && this._isInput && !this._isInline) {
const parent = this._element.closest('.form-adorn');
if (parent) {
positionElement = parent;
}
}
return positionElement || this._element;
}
_resolveDisplayElement() {
const {
displayElement
} = this._config;
if (typeof displayElement === 'string') {
return document.querySelector(displayElement);
}
// For buttons/non-inputs (not inline), look for a [data-bs-datepicker-display] child
if (displayElement === true || displayElement === null && !this._isInput && !this._isInline) {
const displayChild = this._element.querySelector('[data-bs-datepicker-display]');
return displayChild || this._element;
}
return displayElement;
}
_getThemeAncestor() {
return this._element.closest('[data-bs-theme]');
}
_getEffectiveTheme() {
// Priority: explicit datepickerTheme config > inherited from ancestor > none
const {
datepickerTheme
} = this._config;
if (datepickerTheme) {
return datepickerTheme;
}
const ancestor = this._getThemeAncestor();
return ancestor?.getAttribute('data-bs-theme') || null;
}
_syncThemeAttribute(element) {
if (!element) {
return;
}
const theme = this._getEffectiveTheme();
if (theme) {
// Copy theme to popover (needed because VCP appends to body, breaking CSS inheritance)
element.setAttribute('data-bs-theme', theme);
} else {
// No theme - remove attribute to allow natural inheritance
element.removeAttribute('data-bs-theme');
}
}
_setupThemeObserver() {
// Watch for theme changes on ancestor elements
const ancestor = this._getThemeAncestor();
if (!ancestor || this._config.datepickerTheme) {
// No ancestor to watch, or explicit datepickerTheme overrides
return;
}
this._themeObserver = new MutationObserver(() => {
this._syncThemeAttribute(this._calendar?.context?.mainElement);
});
this._themeObserver.observe(ancestor, {
attributes: true,
attributeFilter: ['data-bs-theme']
});
}
_buildCalendarOptions() {
// Get theme for VCP - use 'system' for auto-detection if no explicit theme
const theme = this._getEffectiveTheme();
// VCP uses 'system' for auto, Bootstrap uses 'auto'
const vcpTheme = !theme || theme === 'auto' ? 'system' : theme;
const calendarOptions = {
...this._config.vcpOptions,
inputMode: !this._isInline,
positionToInput: this._config.placement,
firstWeekday: this._config.firstWeekday,
locale: this._config.locale,
selectionDatesMode: this._config.selectionMode,
selectedDates: this._config.selectedDates,
displayMonthsCount: this._config.displayMonthsCount,
type: this._config.displayMonthsCount > 1 ? 'multiple' : 'default',
selectedTheme: vcpTheme,
themeAttrDetect: '[data-bs-theme]',
onClickDate: (self, event) => this._handleDateClick(self, event),
onInit: self => {
this._syncThemeAttribute(self.context.mainElement);
},
onShow: () => {
this._isShown = true;
this._syncThemeAttribute(this._calendar.context.mainElement);
},
onHide: () => {
this._isShown = false;
}
};
// Navigate to the month of the first selected date
if (this._config.selectedDates.length > 0) {
const firstDate = this._parseDate(this._config.selectedDates[0]);
calendarOptions.selectedMonth = firstDate.getMonth();
calendarOptions.selectedYear = firstDate.getFullYear();
}
if (this._config.dateMin) {
calendarOptions.dateMin = this._config.dateMin;
}
if (this._config.dateMax) {
calendarOptions.dateMax = this._config.dateMax;
}
return calendarOptions;
}
_handleDateClick(self, event) {
const selectedDates = [...self.context.selectedDates];
if (selectedDates.length > 0) {
const formattedDate = this._formatDateForInput(selectedDates);
if (this._isInput) {
this._element.value = formattedDate;
}
if (this._boundInput) {
this._boundInput.value = selectedDates.join(',');
}
if (this._displayElement) {
this._displayElement.textContent = formattedDate;
}
}
EventHandler.trigger(this._element, EVENT_CHANGE, {
dates: selectedDates,
event
});
this._maybeHideAfterSelection(selectedDates);
}
_maybeHideAfterSelection(selectedDates) {
if (this._isInline) {
return;
}
const shouldHide = this._config.selectionMode === 'single' && selectedDates.length > 0 || this._config.selectionMode === 'multiple-ranged' && selectedDates.length >= 2;
if (shouldHide) {
setTimeout(() => this.hide(), HIDE_DELAY);
}
}
_parseDate(dateStr) {
const [year, month, day] = dateStr.split('-');
return new Date(year, month - 1, day);
}
_formatDate(dateStr) {
const date = this._parseDate(dateStr);
const locale = this._config.locale === 'default' ? undefined : this._config.locale;
const {
dateFormat
} = this._config;
// Custom function formatter
if (typeof dateFormat === 'function') {
return dateFormat(date, locale);
}
// Intl.DateTimeFormat options object
if (dateFormat && typeof dateFormat === 'object') {
return new Intl.DateTimeFormat(locale, dateFormat).format(date);
}
// Default: locale-aware formatting
return date.toLocaleDateString(locale);
}
_formatDateForInput(dates) {
if (dates.length === 0) {
return '';
}
if (dates.length === 1) {
return this._formatDate(dates[0]);
}
// For date ranges, use en-dash; for multiple dates, use comma
const separator = this._config.selectionMode === 'multiple-ranged' ? ' ' : ', ';
return dates.map(d => this._formatDate(d)).join(separator);
}
_parseInputValue() {
// Try to parse the input value as a date
const value = this._element.value.trim();
if (!value) {
return;
}
const date = new Date(value);
if (!Number.isNaN(date.getTime())) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const formatted = `${year}-${month}-${day}`;
this._calendar.set({
selectedDates: [formatted]
});
}
}
}
/**
* Data API implementation
*/
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
// Only handle if not an input (inputs use focus)
// Skip inline datepickers (they're always visible)
if (this.tagName === 'INPUT' || this.dataset.bsInline === 'true') {
return;
}
event.preventDefault();
Datepicker.getOrCreateInstance(this).toggle();
});
EventHandler.on(document, EVENT_FOCUSIN_DATA_API, SELECTOR_DATA_TOGGLE, function () {
// Handle focus for input elements
if (this.tagName !== 'INPUT') {
return;
}
Datepicker.getOrCreateInstance(this).show();
});
// Auto-initialize inline datepickers on DOMContentLoaded
EventHandler.on(document, `DOMContentLoaded${EVENT_KEY}${DATA_API_KEY}`, () => {
for (const element of document.querySelectorAll(`${SELECTOR_DATA_TOGGLE}[data-bs-inline="true"]`)) {
Datepicker.getOrCreateInstance(element);
}
});
return Datepicker;
}));
//# sourceMappingURL=datepicker.js.map
+1
View File
File diff suppressed because one or more lines are too long
+242
View File
@@ -0,0 +1,242 @@
/*!
* Bootstrap dialog.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./util/component-functions.js'), require('./util/index.js')) :
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/component-functions', './util/index'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Dialog = factory(global.BaseComponent, global.EventHandler, global.Manipulator, global.SelectorEngine, global.ComponentFunctions, global.Index));
})(this, (function (BaseComponent, EventHandler, Manipulator, SelectorEngine, componentFunctions_js, index_js) { 'use strict';
/**
* --------------------------------------------------------------------------
* Bootstrap dialog.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
const NAME = 'dialog';
const DATA_KEY = 'bs.dialog';
const EVENT_KEY = `.${DATA_KEY}`;
const DATA_API_KEY = '.data-api';
const EVENT_SHOW = `show${EVENT_KEY}`;
const EVENT_SHOWN = `shown${EVENT_KEY}`;
const EVENT_HIDE = `hide${EVENT_KEY}`;
const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`;
const EVENT_CANCEL = `cancel${EVENT_KEY}`;
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
const CLASS_NAME_STATIC = 'dialog-static';
const CLASS_NAME_OPEN = 'dialog-open';
const CLASS_NAME_NONMODAL = 'dialog-nonmodal';
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dialog"]';
const SELECTOR_OPEN_MODAL_DIALOG = 'dialog.dialog[open]:not(.dialog-nonmodal)';
const Default = {
backdrop: true,
// true (click dismisses) or 'static' (click does nothing) - only applies to modal dialogs
keyboard: true,
modal: true // true uses showModal(), false uses show() for non-modal dialogs
};
const DefaultType = {
backdrop: '(boolean|string)',
keyboard: 'boolean',
modal: 'boolean'
};
/**
* Class definition
*/
class Dialog extends BaseComponent {
constructor(element, config) {
super(element, config);
this._isTransitioning = false;
this._addEventListeners();
}
// Getters
static get Default() {
return Default;
}
static get DefaultType() {
return DefaultType;
}
static get NAME() {
return NAME;
}
// Public
toggle(relatedTarget) {
return this._element.open ? this.hide() : this.show(relatedTarget);
}
show(relatedTarget) {
if (this._element.open || this._isTransitioning) {
return;
}
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {
relatedTarget
});
if (showEvent.defaultPrevented) {
return;
}
this._isTransitioning = true;
if (this._config.modal) {
// Modal dialog: use showModal() for focus trapping, backdrop, and top layer
this._element.showModal();
// Prevent body scroll for modal dialogs
document.body.classList.add(CLASS_NAME_OPEN);
} else {
// Non-modal dialog: use show() - no backdrop, no focus trap, no top layer
this._element.classList.add(CLASS_NAME_NONMODAL);
this._element.show();
}
this._queueCallback(() => {
this._isTransitioning = false;
EventHandler.trigger(this._element, EVENT_SHOWN, {
relatedTarget
});
}, this._element, this._isAnimated());
}
hide() {
if (!this._element.open || this._isTransitioning) {
return;
}
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);
if (hideEvent.defaultPrevented) {
return;
}
this._isTransitioning = true;
this._queueCallback(() => this._hideDialog(), this._element, this._isAnimated());
}
dispose() {
EventHandler.off(this._element, EVENT_KEY);
super.dispose();
}
handleUpdate() {
// Provided for API consistency with Modal.
// Native dialogs handle their own positioning.
}
// Private
_hideDialog() {
this._element.close();
this._element.classList.remove(CLASS_NAME_NONMODAL);
this._isTransitioning = false;
// Only restore body scroll if no other modal dialogs are open
if (!document.querySelector(SELECTOR_OPEN_MODAL_DIALOG)) {
document.body.classList.remove(CLASS_NAME_OPEN);
}
EventHandler.trigger(this._element, EVENT_HIDDEN);
}
_isAnimated() {
return this._element.classList.contains('fade');
}
_triggerBackdropTransition() {
const hidePreventedEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);
if (hidePreventedEvent.defaultPrevented) {
return;
}
this._element.classList.add(CLASS_NAME_STATIC);
this._queueCallback(() => {
this._element.classList.remove(CLASS_NAME_STATIC);
}, this._element);
}
_addEventListeners() {
// Handle native cancel event (Escape key) - only fires for modal dialogs
EventHandler.on(this._element, 'cancel', event => {
// Prevent native close behavior - we'll handle it
event.preventDefault();
if (!this._config.keyboard) {
this._triggerBackdropTransition();
return;
}
EventHandler.trigger(this._element, EVENT_CANCEL);
this.hide();
});
// Handle Escape key for non-modal dialogs (native cancel doesn't fire for show())
EventHandler.on(this._element, 'keydown', event => {
if (event.key !== 'Escape' || this._config.modal) {
return;
}
event.preventDefault();
if (!this._config.keyboard) {
return;
}
EventHandler.trigger(this._element, EVENT_CANCEL);
this.hide();
});
// Handle backdrop clicks (only applies to modal dialogs)
// Native <dialog> fires click on the dialog element when backdrop is clicked
EventHandler.on(this._element, 'click', event => {
// Only handle clicks directly on the dialog (backdrop area)
// Non-modal dialogs don't have a backdrop
if (event.target !== this._element || !this._config.modal) {
return;
}
if (this._config.backdrop === 'static') {
this._triggerBackdropTransition();
return;
}
// Default: click backdrop to dismiss
this.hide();
});
}
}
/**
* Data API implementation
*/
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
const target = SelectorEngine.getElementFromSelector(this);
if (['A', 'AREA'].includes(this.tagName)) {
event.preventDefault();
}
EventHandler.one(target, EVENT_SHOW, showEvent => {
if (showEvent.defaultPrevented) {
return;
}
EventHandler.one(target, EVENT_HIDDEN, () => {
if (index_js.isVisible(this)) {
this.focus();
}
});
});
// Get config from trigger's data attributes
const config = Manipulator.getDataAttributes(this);
// Check if trigger is inside an open dialog
const currentDialog = this.closest('dialog[open]');
const shouldSwap = currentDialog && currentDialog !== target;
if (shouldSwap) {
// Open new dialog first (its backdrop appears over current)
const newDialog = Dialog.getOrCreateInstance(target, config);
newDialog.show(this);
// Close the current dialog (no backdrop flash since new one is already open)
const currentInstance = Dialog.getInstance(currentDialog);
if (currentInstance) {
currentInstance.hide();
}
return;
}
const data = Dialog.getOrCreateInstance(target, config);
data.toggle(this);
});
componentFunctions_js.enableDismissTrigger(Dialog);
return Dialog;
}));
//# sourceMappingURL=dialog.js.map
+1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap data.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+2 -2
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap event-handler.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
@@ -196,7 +196,7 @@
for (const [key, value] of Object.entries(meta)) {
try {
obj[key] = value;
} catch (_unused) {
} catch {
Object.defineProperty(obj, key, {
configurable: true,
get() {
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap manipulator.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
@@ -34,7 +34,7 @@
}
try {
return JSON.parse(decodeURIComponent(value));
} catch (_unused) {
} catch {
return value;
}
}
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"manipulator.js","sources":["../../src/dom/manipulator.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n"],"names":["normalizeData","value","Number","toString","JSON","parse","decodeURIComponent","_unused","normalizeDataKey","key","replace","chr","toLowerCase","Manipulator","setDataAttribute","element","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","Object","keys","dataset","filter","startsWith","pureKey","charAt","slice","getDataAttribute","getAttribute"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;EAEA,SAASA,aAAaA,CAACC,KAAK,EAAE;IAC5B,IAAIA,KAAK,KAAK,MAAM,EAAE;EACpB,IAAA,OAAO,IAAI;EACb,EAAA;IAEA,IAAIA,KAAK,KAAK,OAAO,EAAE;EACrB,IAAA,OAAO,KAAK;EACd,EAAA;IAEA,IAAIA,KAAK,KAAKC,MAAM,CAACD,KAAK,CAAC,CAACE,QAAQ,EAAE,EAAE;MACtC,OAAOD,MAAM,CAACD,KAAK,CAAC;EACtB,EAAA;EAEA,EAAA,IAAIA,KAAK,KAAK,EAAE,IAAIA,KAAK,KAAK,MAAM,EAAE;EACpC,IAAA,OAAO,IAAI;EACb,EAAA;EAEA,EAAA,IAAI,OAAOA,KAAK,KAAK,QAAQ,EAAE;EAC7B,IAAA,OAAOA,KAAK;EACd,EAAA;IAEA,IAAI;MACF,OAAOG,IAAI,CAACC,KAAK,CAACC,kBAAkB,CAACL,KAAK,CAAC,CAAC;IAC9C,CAAC,CAAC,OAAAM,OAAA,EAAM;EACN,IAAA,OAAON,KAAK;EACd,EAAA;EACF;EAEA,SAASO,gBAAgBA,CAACC,GAAG,EAAE;EAC7B,EAAA,OAAOA,GAAG,CAACC,OAAO,CAAC,QAAQ,EAAEC,GAAG,IAAI,CAAA,CAAA,EAAIA,GAAG,CAACC,WAAW,EAAE,EAAE,CAAC;EAC9D;AAEA,QAAMC,WAAW,GAAG;EAClBC,EAAAA,gBAAgBA,CAACC,OAAO,EAAEN,GAAG,EAAER,KAAK,EAAE;MACpCc,OAAO,CAACC,YAAY,CAAC,CAAA,QAAA,EAAWR,gBAAgB,CAACC,GAAG,CAAC,CAAA,CAAE,EAAER,KAAK,CAAC;IACjE,CAAC;EAEDgB,EAAAA,mBAAmBA,CAACF,OAAO,EAAEN,GAAG,EAAE;MAChCM,OAAO,CAACG,eAAe,CAAC,CAAA,QAAA,EAAWV,gBAAgB,CAACC,GAAG,CAAC,CAAA,CAAE,CAAC;IAC7D,CAAC;IAEDU,iBAAiBA,CAACJ,OAAO,EAAE;MACzB,IAAI,CAACA,OAAO,EAAE;EACZ,MAAA,OAAO,EAAE;EACX,IAAA;MAEA,MAAMK,UAAU,GAAG,EAAE;EACrB,IAAA,MAAMC,MAAM,GAAGC,MAAM,CAACC,IAAI,CAACR,OAAO,CAACS,OAAO,CAAC,CAACC,MAAM,CAAChB,GAAG,IAAIA,GAAG,CAACiB,UAAU,CAAC,IAAI,CAAC,IAAI,CAACjB,GAAG,CAACiB,UAAU,CAAC,UAAU,CAAC,CAAC;EAE9G,IAAA,KAAK,MAAMjB,GAAG,IAAIY,MAAM,EAAE;QACxB,IAAIM,OAAO,GAAGlB,GAAG,CAACC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;EACpCiB,MAAAA,OAAO,GAAGA,OAAO,CAACC,MAAM,CAAC,CAAC,CAAC,CAAChB,WAAW,EAAE,GAAGe,OAAO,CAACE,KAAK,CAAC,CAAC,CAAC;EAC5DT,MAAAA,UAAU,CAACO,OAAO,CAAC,GAAG3B,aAAa,CAACe,OAAO,CAACS,OAAO,CAACf,GAAG,CAAC,CAAC;EAC3D,IAAA;EAEA,IAAA,OAAOW,UAAU;IACnB,CAAC;EAEDU,EAAAA,gBAAgBA,CAACf,OAAO,EAAEN,GAAG,EAAE;EAC7B,IAAA,OAAOT,aAAa,CAACe,OAAO,CAACgB,YAAY,CAAC,CAAA,QAAA,EAAWvB,gBAAgB,CAACC,GAAG,CAAC,CAAA,CAAE,CAAC,CAAC;EAChF,EAAA;EACF;;;;;;;;"}
{"version":3,"file":"manipulator.js","sources":["../../src/dom/manipulator.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n"],"names":["normalizeData","value","Number","toString","JSON","parse","decodeURIComponent","normalizeDataKey","key","replace","chr","toLowerCase","Manipulator","setDataAttribute","element","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","Object","keys","dataset","filter","startsWith","pureKey","charAt","slice","getDataAttribute","getAttribute"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;EAEA,SAASA,aAAaA,CAACC,KAAK,EAAE;IAC5B,IAAIA,KAAK,KAAK,MAAM,EAAE;EACpB,IAAA,OAAO,IAAI;EACb,EAAA;IAEA,IAAIA,KAAK,KAAK,OAAO,EAAE;EACrB,IAAA,OAAO,KAAK;EACd,EAAA;IAEA,IAAIA,KAAK,KAAKC,MAAM,CAACD,KAAK,CAAC,CAACE,QAAQ,EAAE,EAAE;MACtC,OAAOD,MAAM,CAACD,KAAK,CAAC;EACtB,EAAA;EAEA,EAAA,IAAIA,KAAK,KAAK,EAAE,IAAIA,KAAK,KAAK,MAAM,EAAE;EACpC,IAAA,OAAO,IAAI;EACb,EAAA;EAEA,EAAA,IAAI,OAAOA,KAAK,KAAK,QAAQ,EAAE;EAC7B,IAAA,OAAOA,KAAK;EACd,EAAA;IAEA,IAAI;MACF,OAAOG,IAAI,CAACC,KAAK,CAACC,kBAAkB,CAACL,KAAK,CAAC,CAAC;EAC9C,EAAA,CAAC,CAAC,MAAM;EACN,IAAA,OAAOA,KAAK;EACd,EAAA;EACF;EAEA,SAASM,gBAAgBA,CAACC,GAAG,EAAE;EAC7B,EAAA,OAAOA,GAAG,CAACC,OAAO,CAAC,QAAQ,EAAEC,GAAG,IAAI,CAAA,CAAA,EAAIA,GAAG,CAACC,WAAW,EAAE,EAAE,CAAC;EAC9D;AAEA,QAAMC,WAAW,GAAG;EAClBC,EAAAA,gBAAgBA,CAACC,OAAO,EAAEN,GAAG,EAAEP,KAAK,EAAE;MACpCa,OAAO,CAACC,YAAY,CAAC,CAAA,QAAA,EAAWR,gBAAgB,CAACC,GAAG,CAAC,CAAA,CAAE,EAAEP,KAAK,CAAC;IACjE,CAAC;EAEDe,EAAAA,mBAAmBA,CAACF,OAAO,EAAEN,GAAG,EAAE;MAChCM,OAAO,CAACG,eAAe,CAAC,CAAA,QAAA,EAAWV,gBAAgB,CAACC,GAAG,CAAC,CAAA,CAAE,CAAC;IAC7D,CAAC;IAEDU,iBAAiBA,CAACJ,OAAO,EAAE;MACzB,IAAI,CAACA,OAAO,EAAE;EACZ,MAAA,OAAO,EAAE;EACX,IAAA;MAEA,MAAMK,UAAU,GAAG,EAAE;EACrB,IAAA,MAAMC,MAAM,GAAGC,MAAM,CAACC,IAAI,CAACR,OAAO,CAACS,OAAO,CAAC,CAACC,MAAM,CAAChB,GAAG,IAAIA,GAAG,CAACiB,UAAU,CAAC,IAAI,CAAC,IAAI,CAACjB,GAAG,CAACiB,UAAU,CAAC,UAAU,CAAC,CAAC;EAE9G,IAAA,KAAK,MAAMjB,GAAG,IAAIY,MAAM,EAAE;QACxB,IAAIM,OAAO,GAAGlB,GAAG,CAACC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;EACpCiB,MAAAA,OAAO,GAAGA,OAAO,CAACC,MAAM,CAAC,CAAC,CAAC,CAAChB,WAAW,EAAE,GAAGe,OAAO,CAACE,KAAK,CAAC,CAAC,CAAC;EAC5DT,MAAAA,UAAU,CAACO,OAAO,CAAC,GAAG1B,aAAa,CAACc,OAAO,CAACS,OAAO,CAACf,GAAG,CAAC,CAAC;EAC3D,IAAA;EAEA,IAAA,OAAOW,UAAU;IACnB,CAAC;EAEDU,EAAAA,gBAAgBA,CAACf,OAAO,EAAEN,GAAG,EAAE;EAC7B,IAAA,OAAOR,aAAa,CAACc,OAAO,CAACgB,YAAY,CAAC,CAAA,QAAA,EAAWvB,gBAAgB,CAACC,GAAG,CAAC,CAAA,CAAE,CAAC,CAAC;EAChF,EAAA;EACF;;;;;;;;"}
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap selector-engine.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+607 -124
View File
@@ -1,32 +1,13 @@
/*!
* Bootstrap dropdown.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@popperjs/core'), require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./util/index.js')) :
typeof define === 'function' && define.amd ? define(['@popperjs/core', './base-component', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/index'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Dropdown = factory(global["@popperjs/core"], global.BaseComponent, global.EventHandler, global.Manipulator, global.SelectorEngine, global.Index));
})(this, (function (Popper, BaseComponent, EventHandler, Manipulator, SelectorEngine, index_js) { 'use strict';
function _interopNamespaceDefault(e) {
const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });
if (e) {
for (const k in e) {
if (k !== 'default') {
const d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: () => e[k]
});
}
}
}
n.default = e;
return Object.freeze(n);
}
const Popper__namespace = /*#__PURE__*/_interopNamespaceDefault(Popper);
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@floating-ui/dom'), require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./util/index.js'), require('./util/floating-ui.js')) :
typeof define === 'function' && define.amd ? define(['@floating-ui/dom', './base-component', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/index', './util/floating-ui'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Dropdown = factory(global["@floating-ui/dom"], global.BaseComponent, global.EventHandler, global.Manipulator, global.SelectorEngine, global.Index, global.FloatingUi));
})(this, (function (dom, BaseComponent, EventHandler, Manipulator, SelectorEngine, index_js, floatingUi_js) { 'use strict';
/**
* --------------------------------------------------------------------------
@@ -48,8 +29,16 @@
const TAB_KEY = 'Tab';
const ARROW_UP_KEY = 'ArrowUp';
const ARROW_DOWN_KEY = 'ArrowDown';
const RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button
const ARROW_LEFT_KEY = 'ArrowLeft';
const ARROW_RIGHT_KEY = 'ArrowRight';
const HOME_KEY = 'Home';
const END_KEY = 'End';
const ENTER_KEY = 'Enter';
const SPACE_KEY = ' ';
const RIGHT_MOUSE_BUTTON = 2;
// Hover intent delay (ms) - grace period before closing submenu
const SUBMENU_CLOSE_DELAY = 100;
const EVENT_HIDE = `hide${EVENT_KEY}`;
const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
const EVENT_SHOW = `show${EVENT_KEY}`;
@@ -58,40 +47,54 @@
const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`;
const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`;
const CLASS_NAME_SHOW = 'show';
const CLASS_NAME_DROPUP = 'dropup';
const CLASS_NAME_DROPEND = 'dropend';
const CLASS_NAME_DROPSTART = 'dropstart';
const CLASS_NAME_DROPUP_CENTER = 'dropup-center';
const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center';
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)';
const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`;
const SELECTOR_MENU = '.dropdown-menu';
const SELECTOR_NAVBAR = '.navbar';
const SELECTOR_SUBMENU = '.dropdown-submenu';
const SELECTOR_SUBMENU_TOGGLE = '.dropdown-submenu > .dropdown-item';
const SELECTOR_NAVBAR_NAV = '.navbar-nav';
const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)';
const PLACEMENT_TOP = index_js.isRTL() ? 'top-end' : 'top-start';
const PLACEMENT_TOPEND = index_js.isRTL() ? 'top-start' : 'top-end';
const PLACEMENT_BOTTOM = index_js.isRTL() ? 'bottom-end' : 'bottom-start';
const PLACEMENT_BOTTOMEND = index_js.isRTL() ? 'bottom-start' : 'bottom-end';
const PLACEMENT_RIGHT = index_js.isRTL() ? 'left-start' : 'right-start';
const PLACEMENT_LEFT = index_js.isRTL() ? 'right-start' : 'left-start';
const PLACEMENT_TOPCENTER = 'top';
const PLACEMENT_BOTTOMCENTER = 'bottom';
const SELECTOR_VISIBLE_ITEMS = '.dropdown-item:not(.disabled):not(:disabled)';
// Default logical placement (uses start/end which get resolved to left/right based on RTL)
const DEFAULT_PLACEMENT = 'bottom-start';
const SUBMENU_PLACEMENT = 'end-start';
// Resolve logical placement (start/end) to physical (left/right) based on RTL
const resolveLogicalPlacement = placement => {
if (index_js.isRTL()) {
// RTL: start → right, end → left
return placement.replace(/^start(?=-|$)/, 'right').replace(/^end(?=-|$)/, 'left');
}
// LTR: start → left, end → right
return placement.replace(/^start(?=-|$)/, 'left').replace(/^end(?=-|$)/, 'right');
};
// Helper for barycentric coordinate calculation (point in triangle check)
const triangleSign = (p1, p2, p3) => (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
const Default = {
autoClose: true,
boundary: 'clippingParents',
display: 'dynamic',
offset: [0, 2],
popperConfig: null,
reference: 'toggle'
floatingConfig: null,
placement: DEFAULT_PLACEMENT,
reference: 'toggle',
// Submenu options
submenuTrigger: 'both',
// 'click', 'hover', or 'both'
submenuDelay: SUBMENU_CLOSE_DELAY
};
const DefaultType = {
autoClose: '(boolean|string)',
boundary: '(string|element)',
display: 'string',
offset: '(array|string|function)',
popperConfig: '(null|object|function)',
reference: '(string|element|object)'
floatingConfig: '(null|object|function)',
placement: 'string',
reference: '(string|element|object)',
submenuTrigger: 'string',
submenuDelay: 'number'
};
/**
@@ -100,12 +103,27 @@
class Dropdown extends BaseComponent {
constructor(element, config) {
if (typeof dom.computePosition === 'undefined') {
throw new TypeError('Bootstrap\'s dropdowns require Floating UI (https://floating-ui.com)');
}
super(element, config);
this._popper = null;
this._floatingCleanup = null;
this._mediaQueryListeners = [];
this._responsivePlacements = null;
this._parent = this._element.parentNode; // dropdown wrapper
this._isSubmenu = this._parent.classList.contains('dropdown-submenu');
this._openSubmenus = new Map(); // Map of submenu element -> cleanup function
this._submenuCloseTimeouts = new Map(); // Map of submenu element -> timeout ID
this._hoverIntentData = null; // For safe triangle calculation
// 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();
// Parse responsive placements on init
this._parseResponsivePlacements();
// Set up submenu event listeners
this._setupSubmenuListeners();
}
// Getters
@@ -134,7 +152,7 @@
if (showEvent.defaultPrevented) {
return;
}
this._createPopper();
this._createFloating();
// If this is a touch-enabled device we add extra
// empty mouseover listeners to the body's immediate children;
@@ -146,9 +164,10 @@
}
}
this._element.focus();
this._element.setAttribute('aria-expanded', true);
this._element.setAttribute('aria-expanded', 'true');
this._menu.classList.add(CLASS_NAME_SHOW);
this._element.classList.add(CLASS_NAME_SHOW);
this._parent.classList.add(CLASS_NAME_SHOW);
EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget);
}
hide() {
@@ -161,15 +180,15 @@
this._completeHide(relatedTarget);
}
dispose() {
if (this._popper) {
this._popper.destroy();
}
this._disposeFloating();
this._disposeMediaQueryListeners();
this._closeAllSubmenus();
this._clearAllSubmenuTimeouts();
super.dispose();
}
update() {
this._inNavbar = this._detectNavbar();
if (this._popper) {
this._popper.update();
if (this._floatingCleanup) {
this._updateFloatingPosition();
}
}
@@ -180,6 +199,9 @@
return;
}
// Close all open submenus first
this._closeAllSubmenus();
// If this is a touch-enabled device we remove the extra
// empty mouseover listeners we added for iOS support
if ('ontouchstart' in document.documentElement) {
@@ -187,26 +209,27 @@
EventHandler.off(element, 'mouseover', index_js.noop);
}
}
if (this._popper) {
this._popper.destroy();
}
this._disposeFloating();
this._menu.classList.remove(CLASS_NAME_SHOW);
this._element.classList.remove(CLASS_NAME_SHOW);
this._parent.classList.remove(CLASS_NAME_SHOW);
this._element.setAttribute('aria-expanded', 'false');
Manipulator.removeDataAttribute(this._menu, 'popper');
Manipulator.removeDataAttribute(this._menu, 'placement');
Manipulator.removeDataAttribute(this._menu, 'display');
EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget);
}
_getConfig(config) {
config = super._getConfig(config);
if (typeof config.reference === 'object' && !index_js.isElement(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') {
// Popper virtual elements require a getBoundingClientRect method
// Floating UI virtual elements require a getBoundingClientRect method
throw new TypeError(`${NAME.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);
}
return config;
}
_createPopper() {
if (typeof Popper__namespace === 'undefined') {
throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org/docs/v2/)');
_createFloating() {
if (this._config.display === 'static') {
Manipulator.setDataAttribute(this._menu, 'display', 'static');
return;
}
let referenceElement = this._element;
if (this._config.reference === 'parent') {
@@ -216,83 +239,430 @@
} else if (typeof this._config.reference === 'object') {
referenceElement = this._config.reference;
}
const popperConfig = this._getPopperConfig();
this._popper = Popper__namespace.createPopper(referenceElement, this._menu, popperConfig);
// Initial position update
this._updateFloatingPosition(referenceElement);
// Set up auto-update for scroll/resize
this._floatingCleanup = dom.autoUpdate(referenceElement, this._menu, () => this._updateFloatingPosition(referenceElement));
}
async _updateFloatingPosition(referenceElement = null) {
if (!this._menu) {
return;
}
if (!referenceElement) {
if (this._config.reference === 'parent') {
referenceElement = this._parent;
} else if (index_js.isElement(this._config.reference)) {
referenceElement = index_js.getElement(this._config.reference);
} else if (typeof this._config.reference === 'object') {
referenceElement = this._config.reference;
} else {
referenceElement = this._element;
}
}
const placement = this._getPlacement();
const middleware = this._getFloatingMiddleware();
const floatingConfig = this._getFloatingConfig(placement, middleware);
await this._applyFloatingPosition(referenceElement, this._menu, floatingConfig.placement, floatingConfig.middleware);
}
_isShown() {
return this._menu.classList.contains(CLASS_NAME_SHOW);
}
_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;
}
// If we have responsive placements, find the appropriate one for current viewport
const placement = this._responsivePlacements ? floatingUi_js.getResponsivePlacement(this._responsivePlacements, DEFAULT_PLACEMENT) : this._config.placement;
// 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;
// Resolve logical placements (start/end) to physical (left/right) based on RTL
return resolveLogicalPlacement(placement);
}
_detectNavbar() {
return this._element.closest(SELECTOR_NAVBAR) !== null;
_parseResponsivePlacements() {
this._responsivePlacements = floatingUi_js.parseResponsivePlacement(this._config.placement, DEFAULT_PLACEMENT);
if (this._responsivePlacements) {
this._setupMediaQueryListeners();
}
}
_setupMediaQueryListeners() {
this._disposeMediaQueryListeners();
this._mediaQueryListeners = floatingUi_js.createBreakpointListeners(() => {
if (this._isShown()) {
this._updateFloatingPosition();
}
});
}
_disposeMediaQueryListeners() {
floatingUi_js.disposeBreakpointListeners(this._mediaQueryListeners);
this._mediaQueryListeners = [];
}
_getOffset() {
const {
offset
offset: offsetConfig
} = this._config;
if (typeof offset === 'string') {
return offset.split(',').map(value => Number.parseInt(value, 10));
if (typeof offsetConfig === 'string') {
return offsetConfig.split(',').map(value => Number.parseInt(value, 10));
}
if (typeof offset === 'function') {
return popperData => offset(popperData, this._element);
if (typeof offsetConfig === 'function') {
// Floating UI passes different args, adapt the interface for offset function callbacks
return ({
placement,
rects
}) => {
const result = offsetConfig({
placement,
reference: rects.reference,
floating: rects.floating
}, this._element);
return result;
};
}
return offset;
return offsetConfig;
}
_getPopperConfig() {
const defaultBsPopperConfig = {
placement: this._getPlacement(),
modifiers: [{
name: 'preventOverflow',
options: {
boundary: this._config.boundary
}
}, {
name: 'offset',
options: {
offset: this._getOffset()
}
}]
_getFloatingMiddleware() {
const offsetValue = this._getOffset();
const middleware = [
// Offset middleware - handles distance from reference
dom.offset(typeof offsetValue === 'function' ? offsetValue : {
mainAxis: offsetValue[1] || 0,
crossAxis: offsetValue[0] || 0
}),
// Flip middleware - handles fallback placements
dom.flip({
fallbackPlacements: this._getFallbackPlacements()
}),
// Shift middleware - prevents overflow
dom.shift({
boundary: this._config.boundary === 'clippingParents' ? 'clippingAncestors' : this._config.boundary
})];
return middleware;
}
_getFallbackPlacements() {
// Get appropriate fallback placements based on current placement
// Fallbacks should preserve alignment (start/end) when possible
const placement = this._getPlacement();
// Handle all possible Floating UI placements
const fallbackMap = {
bottom: ['top', 'bottom-start', 'bottom-end', 'top-start', 'top-end'],
'bottom-start': ['top-start', 'bottom-end', 'top-end'],
'bottom-end': ['top-end', 'bottom-start', 'top-start'],
top: ['bottom', 'top-start', 'top-end', 'bottom-start', 'bottom-end'],
'top-start': ['bottom-start', 'top-end', 'bottom-end'],
'top-end': ['bottom-end', 'top-start', 'bottom-start'],
right: ['left', 'right-start', 'right-end', 'left-start', 'left-end'],
'right-start': ['left-start', 'right-end', 'left-end', 'top-start', 'bottom-start'],
'right-end': ['left-end', 'right-start', 'left-start', 'top-end', 'bottom-end'],
left: ['right', 'left-start', 'left-end', 'right-start', 'right-end'],
'left-start': ['right-start', 'left-end', 'right-end', 'top-start', 'bottom-start'],
'left-end': ['right-end', 'left-start', 'right-start', 'top-end', 'bottom-end']
};
return fallbackMap[placement] || ['top', 'bottom', 'right', 'left'];
}
_getFloatingConfig(placement, middleware) {
const defaultConfig = {
placement,
middleware
};
return {
...defaultConfig,
...index_js.execute(this._config.floatingConfig, [undefined, defaultConfig])
};
}
_disposeFloating() {
if (this._floatingCleanup) {
this._floatingCleanup();
this._floatingCleanup = null;
}
}
// Shared helper for positioning any floating element
async _applyFloatingPosition(reference, floating, placement, middleware) {
if (!floating.isConnected) {
return null;
}
const {
x,
y,
placement: finalPlacement
} = await dom.computePosition(reference, floating, {
placement,
middleware
});
if (!floating.isConnected) {
return null;
}
Object.assign(floating.style, {
position: 'absolute',
left: `${x}px`,
top: `${y}px`,
margin: '0'
});
Manipulator.setDataAttribute(floating, 'placement', finalPlacement);
return finalPlacement;
}
// -------------------------------------------------------------------------
// Submenu handling
// -------------------------------------------------------------------------
_setupSubmenuListeners() {
// Set up hover listeners for submenu triggers
if (this._config.submenuTrigger === 'hover' || this._config.submenuTrigger === 'both') {
EventHandler.on(this._menu, 'mouseenter', SELECTOR_SUBMENU_TOGGLE, event => {
this._onSubmenuTriggerEnter(event);
});
EventHandler.on(this._menu, 'mouseleave', SELECTOR_SUBMENU, event => {
this._onSubmenuLeave(event);
});
// Track mouse movement for safe triangle calculation
EventHandler.on(this._menu, 'mousemove', event => {
this._trackMousePosition(event);
});
}
// Set up click listener for submenu triggers
if (this._config.submenuTrigger === 'click' || this._config.submenuTrigger === 'both') {
EventHandler.on(this._menu, 'click', SELECTOR_SUBMENU_TOGGLE, event => {
this._onSubmenuTriggerClick(event);
});
}
}
_onSubmenuTriggerEnter(event) {
const trigger = event.target.closest(SELECTOR_SUBMENU_TOGGLE);
if (!trigger) {
return;
}
const submenuWrapper = trigger.closest(SELECTOR_SUBMENU);
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
if (!submenu) {
return;
}
// Cancel any pending close timeout for this submenu
this._cancelSubmenuCloseTimeout(submenu);
// Close other open submenus at the same level
this._closeSiblingSubmenus(submenuWrapper);
// Open this submenu
this._openSubmenu(trigger, submenu, submenuWrapper);
}
_onSubmenuLeave(event) {
const submenuWrapper = event.target.closest(SELECTOR_SUBMENU);
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
if (!submenu || !this._openSubmenus.has(submenu)) {
return;
}
// Check if we're moving toward the submenu (safe triangle)
if (this._isMovingTowardSubmenu(event, submenu)) {
return;
}
// Schedule submenu close with delay
this._scheduleSubmenuClose(submenu, submenuWrapper);
}
_onSubmenuTriggerClick(event) {
const trigger = event.target.closest(SELECTOR_SUBMENU_TOGGLE);
if (!trigger) {
return;
}
event.preventDefault();
event.stopPropagation();
const submenuWrapper = trigger.closest(SELECTOR_SUBMENU);
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
if (!submenu) {
return;
}
// Toggle submenu
if (this._openSubmenus.has(submenu)) {
this._closeSubmenu(submenu, submenuWrapper);
} else {
this._closeSiblingSubmenus(submenuWrapper);
this._openSubmenu(trigger, submenu, submenuWrapper);
}
}
_openSubmenu(trigger, submenu, submenuWrapper) {
if (this._openSubmenus.has(submenu)) {
return;
}
// Set ARIA attributes
trigger.setAttribute('aria-expanded', 'true');
trigger.setAttribute('aria-haspopup', 'true');
// Position and show submenu
submenu.classList.add(CLASS_NAME_SHOW);
submenuWrapper.classList.add(CLASS_NAME_SHOW);
// Set up Floating UI positioning for submenu
const cleanup = this._createSubmenuFloating(trigger, submenu, submenuWrapper);
this._openSubmenus.set(submenu, cleanup);
// Set up mouseenter on submenu to cancel close timeout
EventHandler.on(submenu, 'mouseenter', () => {
this._cancelSubmenuCloseTimeout(submenu);
});
}
_closeSubmenu(submenu, submenuWrapper) {
if (!this._openSubmenus.has(submenu)) {
return;
}
// Close any nested submenus first
const nestedSubmenus = SelectorEngine.find(`${SELECTOR_SUBMENU} ${SELECTOR_MENU}.${CLASS_NAME_SHOW}`, submenu);
for (const nested of nestedSubmenus) {
const nestedWrapper = nested.closest(SELECTOR_SUBMENU);
this._closeSubmenu(nested, nestedWrapper);
}
// Get the trigger
const trigger = SelectorEngine.findOne(SELECTOR_SUBMENU_TOGGLE, submenuWrapper);
// Clean up Floating UI
const cleanup = this._openSubmenus.get(submenu);
if (cleanup) {
cleanup();
}
this._openSubmenus.delete(submenu);
// Remove event listeners
EventHandler.off(submenu, 'mouseenter');
// Update ARIA and visibility
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
submenu.classList.remove(CLASS_NAME_SHOW);
submenuWrapper.classList.remove(CLASS_NAME_SHOW);
// Clear inline styles
submenu.style.position = '';
submenu.style.left = '';
submenu.style.top = '';
submenu.style.margin = '';
}
_closeAllSubmenus() {
for (const [submenu] of this._openSubmenus) {
const submenuWrapper = submenu.closest(SELECTOR_SUBMENU);
this._closeSubmenu(submenu, submenuWrapper);
}
}
_closeSiblingSubmenus(currentSubmenuWrapper) {
// Find all sibling submenu wrappers and close their menus
const parent = currentSubmenuWrapper.parentNode;
const siblingSubmenus = SelectorEngine.find(`${SELECTOR_SUBMENU} > ${SELECTOR_MENU}.${CLASS_NAME_SHOW}`, parent);
for (const siblingMenu of siblingSubmenus) {
const siblingWrapper = siblingMenu.closest(SELECTOR_SUBMENU);
if (siblingWrapper !== currentSubmenuWrapper) {
this._closeSubmenu(siblingMenu, siblingWrapper);
}
}
}
_createSubmenuFloating(trigger, submenu, submenuWrapper) {
const referenceElement = submenuWrapper;
const placement = resolveLogicalPlacement(SUBMENU_PLACEMENT);
const middleware = [dom.offset({
mainAxis: 0,
crossAxis: -4
}), dom.flip({
fallbackPlacements: [resolveLogicalPlacement('start-start'), resolveLogicalPlacement('end-end'), resolveLogicalPlacement('start-end')]
}), dom.shift({
padding: 8
})];
const updatePosition = () => this._applyFloatingPosition(referenceElement, submenu, placement, middleware);
updatePosition();
return dom.autoUpdate(referenceElement, submenu, updatePosition);
}
_scheduleSubmenuClose(submenu, submenuWrapper) {
this._cancelSubmenuCloseTimeout(submenu);
const timeoutId = setTimeout(() => {
this._closeSubmenu(submenu, submenuWrapper);
this._submenuCloseTimeouts.delete(submenu);
}, this._config.submenuDelay);
this._submenuCloseTimeouts.set(submenu, timeoutId);
}
_cancelSubmenuCloseTimeout(submenu) {
const timeoutId = this._submenuCloseTimeouts.get(submenu);
if (timeoutId) {
clearTimeout(timeoutId);
this._submenuCloseTimeouts.delete(submenu);
}
}
_clearAllSubmenuTimeouts() {
for (const timeoutId of this._submenuCloseTimeouts.values()) {
clearTimeout(timeoutId);
}
this._submenuCloseTimeouts.clear();
}
// -------------------------------------------------------------------------
// Hover intent / Safe triangle
// -------------------------------------------------------------------------
_trackMousePosition(event) {
this._hoverIntentData = {
x: event.clientX,
y: event.clientY,
timestamp: Date.now()
};
}
_isMovingTowardSubmenu(event, submenu) {
if (!this._hoverIntentData) {
return false;
}
const submenuRect = submenu.getBoundingClientRect();
const currentPos = {
x: event.clientX,
y: event.clientY
};
const lastPos = {
x: this._hoverIntentData.x,
y: this._hoverIntentData.y
};
// 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
defaultBsPopperConfig.modifiers = [{
name: 'applyStyles',
enabled: false
}];
}
return {
...defaultBsPopperConfig,
...index_js.execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])
// Create a triangle from current position to submenu edges
// The triangle represents the "safe zone" for diagonal movement
const isRtl = index_js.isRTL();
// Determine which edge of the submenu to target based on direction
const targetX = isRtl ? submenuRect.right : submenuRect.left;
const topCorner = {
x: targetX,
y: submenuRect.top
};
const bottomCorner = {
x: targetX,
y: submenuRect.bottom
};
// Check if cursor is moving toward the submenu
// by checking if the current position is within the safe triangle
return this._pointInTriangle(currentPos, lastPos, topCorner, bottomCorner);
}
_pointInTriangle(point, v1, v2, v3) {
// Barycentric coordinate method to check if point is inside triangle
const d1 = triangleSign(point, v1, v2);
const d2 = triangleSign(point, v2, v3);
const d3 = triangleSign(point, v3, v1);
const hasNeg = d1 < 0 || d2 < 0 || d3 < 0;
const hasPos = d1 > 0 || d2 > 0 || d3 > 0;
return !(hasNeg && hasPos);
}
// -------------------------------------------------------------------------
// Keyboard navigation
// -------------------------------------------------------------------------
_selectMenuItem({
key,
target
}) {
const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => index_js.isVisible(element));
// Get items only from the current menu level (not nested submenus)
// If target is inside a menu, use that menu; otherwise use the main menu
const currentMenu = target.closest(SELECTOR_MENU) || this._menu;
const items = SelectorEngine.find(`:scope > li > ${SELECTOR_VISIBLE_ITEMS}, :scope > ${SELECTOR_VISIBLE_ITEMS}`, currentMenu).filter(element => index_js.isVisible(element));
if (!items.length) {
return;
}
@@ -301,6 +671,89 @@
// allow cycling to get the last item in case key equals ARROW_UP_KEY
index_js.getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus();
}
_handleSubmenuKeydown(event) {
const {
key,
target
} = event;
const isRtl = index_js.isRTL();
// Determine the "enter submenu" and "exit submenu" keys based on RTL
const enterKey = isRtl ? ARROW_LEFT_KEY : ARROW_RIGHT_KEY;
const exitKey = isRtl ? ARROW_RIGHT_KEY : ARROW_LEFT_KEY;
// Check if target is a submenu trigger
const submenuWrapper = target.closest(SELECTOR_SUBMENU);
const isSubmenuTrigger = submenuWrapper && target.matches(SELECTOR_SUBMENU_TOGGLE);
// Handle Enter/Space on submenu trigger
if ((key === ENTER_KEY || key === SPACE_KEY) && isSubmenuTrigger) {
event.preventDefault();
event.stopPropagation();
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
if (submenu) {
this._closeSiblingSubmenus(submenuWrapper);
this._openSubmenu(target, submenu, submenuWrapper);
// Focus first item in submenu
requestAnimationFrame(() => {
const firstItem = SelectorEngine.findOne(SELECTOR_VISIBLE_ITEMS, submenu);
if (firstItem) {
firstItem.focus();
}
});
}
return true;
}
// Handle Right arrow (or Left in RTL) - enter submenu
if (key === enterKey && isSubmenuTrigger) {
event.preventDefault();
event.stopPropagation();
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
if (submenu) {
this._closeSiblingSubmenus(submenuWrapper);
this._openSubmenu(target, submenu, submenuWrapper);
// Focus first item in submenu
requestAnimationFrame(() => {
const firstItem = SelectorEngine.findOne(SELECTOR_VISIBLE_ITEMS, submenu);
if (firstItem) {
firstItem.focus();
}
});
}
return true;
}
// Handle Left arrow (or Right in RTL) - exit submenu
if (key === exitKey) {
const currentMenu = target.closest(SELECTOR_MENU);
const parentSubmenuWrapper = currentMenu?.closest(SELECTOR_SUBMENU);
if (parentSubmenuWrapper) {
event.preventDefault();
event.stopPropagation();
const parentTrigger = SelectorEngine.findOne(SELECTOR_SUBMENU_TOGGLE, parentSubmenuWrapper);
this._closeSubmenu(currentMenu, parentSubmenuWrapper);
if (parentTrigger) {
parentTrigger.focus();
}
return true;
}
}
// Handle Home/End keys
if (key === HOME_KEY || key === END_KEY) {
event.preventDefault();
event.stopPropagation();
const currentMenu = target.closest(SELECTOR_MENU);
const items = SelectorEngine.find(`:scope > li > ${SELECTOR_VISIBLE_ITEMS}, :scope > ${SELECTOR_VISIBLE_ITEMS}`, currentMenu).filter(element => index_js.isVisible(element));
if (items.length) {
const targetItem = key === HOME_KEY ? items[0] : items[items.length - 1];
targetItem.focus();
}
return true;
}
return false;
}
static clearMenus(event) {
if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY) {
return;
@@ -331,32 +784,62 @@
}
}
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
// If not a relevant key => not a dropdown command
const isInput = /input|textarea/i.test(event.target.tagName);
const isEscapeEvent = event.key === ESCAPE_KEY;
const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key);
if (!isUpOrDownEvent && !isEscapeEvent) {
const isLeftOrRightEvent = [ARROW_LEFT_KEY, ARROW_RIGHT_KEY].includes(event.key);
const isHomeOrEndEvent = [HOME_KEY, END_KEY].includes(event.key);
const isEnterOrSpaceEvent = [ENTER_KEY, SPACE_KEY].includes(event.key);
// Allow Enter/Space only on submenu triggers
const isSubmenuTrigger = event.target.matches(SELECTOR_SUBMENU_TOGGLE);
if (!isUpOrDownEvent && !isEscapeEvent && !isLeftOrRightEvent && !isHomeOrEndEvent && !(isEnterOrSpaceEvent && isSubmenuTrigger)) {
return;
}
if (isInput && !isEscapeEvent) {
return;
}
event.preventDefault();
// TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/
const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode);
if (!getToggleButton) {
return;
}
const instance = Dropdown.getOrCreateInstance(getToggleButton);
// Handle submenu navigation first
if ((isLeftOrRightEvent || isHomeOrEndEvent || isEnterOrSpaceEvent && isSubmenuTrigger) && instance._handleSubmenuKeydown(event)) {
return;
}
// Handle Up/Down navigation
if (isUpOrDownEvent) {
event.preventDefault();
event.stopPropagation();
instance.show();
instance._selectMenuItem(event);
return;
}
if (instance._isShown()) {
// else is escape and we check if it is shown
// Handle Escape
if (isEscapeEvent && instance._isShown()) {
event.preventDefault();
event.stopPropagation();
// If in a submenu, close just that submenu
const currentMenu = event.target.closest(SELECTOR_MENU);
const parentSubmenuWrapper = currentMenu?.closest(SELECTOR_SUBMENU);
if (parentSubmenuWrapper && instance._openSubmenus.size > 0) {
const parentTrigger = SelectorEngine.findOne(SELECTOR_SUBMENU_TOGGLE, parentSubmenuWrapper);
instance._closeSubmenu(currentMenu, parentSubmenuWrapper);
if (parentTrigger) {
parentTrigger.focus();
}
return;
}
// Otherwise close the whole dropdown
instance.hide();
getToggleButton.focus();
}
+1 -1
View File
File diff suppressed because one or more lines are too long
-300
View File
@@ -1,300 +0,0 @@
/*!
* Bootstrap modal.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/selector-engine.js'), require('./util/backdrop.js'), require('./util/component-functions.js'), require('./util/focustrap.js'), require('./util/index.js'), require('./util/scrollbar.js')) :
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine', './util/backdrop', './util/component-functions', './util/focustrap', './util/index', './util/scrollbar'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Modal = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine, global.Backdrop, global.ComponentFunctions, global.Focustrap, global.Index, global.Scrollbar));
})(this, (function (BaseComponent, EventHandler, SelectorEngine, Backdrop, componentFunctions_js, FocusTrap, index_js, ScrollBarHelper) { 'use strict';
/**
* --------------------------------------------------------------------------
* Bootstrap modal.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
const NAME = 'modal';
const DATA_KEY = 'bs.modal';
const EVENT_KEY = `.${DATA_KEY}`;
const DATA_API_KEY = '.data-api';
const ESCAPE_KEY = 'Escape';
const EVENT_HIDE = `hide${EVENT_KEY}`;
const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`;
const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
const EVENT_SHOW = `show${EVENT_KEY}`;
const EVENT_SHOWN = `shown${EVENT_KEY}`;
const EVENT_RESIZE = `resize${EVENT_KEY}`;
const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`;
const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`;
const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`;
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
const CLASS_NAME_OPEN = 'modal-open';
const CLASS_NAME_FADE = 'fade';
const CLASS_NAME_SHOW = 'show';
const CLASS_NAME_STATIC = 'modal-static';
const OPEN_SELECTOR = '.modal.show';
const SELECTOR_DIALOG = '.modal-dialog';
const SELECTOR_MODAL_BODY = '.modal-body';
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="modal"]';
const Default = {
backdrop: true,
focus: true,
keyboard: true
};
const DefaultType = {
backdrop: '(boolean|string)',
focus: 'boolean',
keyboard: 'boolean'
};
/**
* Class definition
*/
class Modal extends BaseComponent {
constructor(element, config) {
super(element, config);
this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element);
this._backdrop = this._initializeBackDrop();
this._focustrap = this._initializeFocusTrap();
this._isShown = false;
this._isTransitioning = false;
this._scrollBar = new ScrollBarHelper();
this._addEventListeners();
}
// Getters
static get Default() {
return Default;
}
static get DefaultType() {
return DefaultType;
}
static get NAME() {
return NAME;
}
// 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, {
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);
if (hideEvent.defaultPrevented) {
return;
}
this._isShown = false;
this._isTransitioning = true;
this._focustrap.deactivate();
this._element.classList.remove(CLASS_NAME_SHOW);
this._queueCallback(() => this._hideModal(), this._element, this._isAnimated());
}
dispose() {
EventHandler.off(window, EVENT_KEY);
EventHandler.off(this._dialog, EVENT_KEY);
this._backdrop.dispose();
this._focustrap.deactivate();
super.dispose();
}
handleUpdate() {
this._adjustDialog();
}
// Private
_initializeBackDrop() {
return new Backdrop({
isVisible: Boolean(this._config.backdrop),
// 'static' option will be translated to true, and booleans will keep their value,
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;
}
index_js.reflow(this._element);
this._element.classList.add(CLASS_NAME_SHOW);
const transitionComplete = () => {
if (this._config.focus) {
this._focustrap.activate();
}
this._isTransitioning = false;
EventHandler.trigger(this._element, EVENT_SHOWN, {
relatedTarget
});
};
this._queueCallback(transitionComplete, this._dialog, this._isAnimated());
}
_addEventListeners() {
EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {
if (event.key !== ESCAPE_KEY) {
return;
}
if (this._config.keyboard) {
this.hide();
return;
}
this._triggerBackdropTransition();
});
EventHandler.on(window, EVENT_RESIZE, () => {
if (this._isShown && !this._isTransitioning) {
this._adjustDialog();
}
});
EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {
// a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks
EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {
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);
});
}
_isAnimated() {
return this._element.classList.contains(CLASS_NAME_FADE);
}
_triggerBackdropTransition() {
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);
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
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 = index_js.isRTL() ? 'paddingLeft' : 'paddingRight';
this._element.style[property] = `${scrollbarWidth}px`;
}
if (!isBodyOverflowing && isModalOverflowing) {
const property = index_js.isRTL() ? 'paddingRight' : 'paddingLeft';
this._element.style[property] = `${scrollbarWidth}px`;
}
}
_resetAdjustments() {
this._element.style.paddingLeft = '';
this._element.style.paddingRight = '';
}
}
/**
* Data API implementation
*/
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
const target = SelectorEngine.getElementFromSelector(this);
if (['A', 'AREA'].includes(this.tagName)) {
event.preventDefault();
}
EventHandler.one(target, EVENT_SHOW, showEvent => {
if (showEvent.defaultPrevented) {
// only register focus restorer if modal will actually get shown
return;
}
EventHandler.one(target, EVENT_HIDDEN, () => {
if (index_js.isVisible(this)) {
this.focus();
}
});
});
// avoid conflict when clicking modal toggler while another one is open
const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR);
if (alreadyOpen) {
Modal.getInstance(alreadyOpen).hide();
}
const data = Modal.getOrCreateInstance(target);
data.toggle(this);
});
componentFunctions_js.enableDismissTrigger(Modal);
return Modal;
}));
//# sourceMappingURL=modal.js.map
-1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap offcanvas.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+238
View File
@@ -0,0 +1,238 @@
/*!
* Bootstrap otp-input.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/selector-engine.js')) :
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.OtpInput = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine));
})(this, (function (BaseComponent, EventHandler, SelectorEngine) { 'use strict';
/**
* --------------------------------------------------------------------------
* Bootstrap otp-input.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
const NAME = 'otpInput';
const DATA_KEY = 'bs.otp-input';
const EVENT_KEY = `.${DATA_KEY}`;
const DATA_API_KEY = '.data-api';
const EVENT_COMPLETE = `complete${EVENT_KEY}`;
const EVENT_INPUT = `input${EVENT_KEY}`;
const SELECTOR_DATA_OTP = '[data-bs-otp]';
const SELECTOR_INPUT = 'input';
const Default = {
length: 6,
mask: false
};
const DefaultType = {
length: 'number',
mask: 'boolean'
};
/**
* Class definition
*/
class OtpInput extends BaseComponent {
constructor(element, config) {
super(element, config);
this._inputs = SelectorEngine.find(SELECTOR_INPUT, this._element);
this._setupInputs();
this._addEventListeners();
}
// Getters
static get Default() {
return Default;
}
static get DefaultType() {
return DefaultType;
}
static get NAME() {
return NAME;
}
// Public
getValue() {
return this._inputs.map(input => input.value).join('');
}
setValue(value) {
const chars = String(value).split('');
for (const [index, input] of this._inputs.entries()) {
input.value = chars[index] || '';
}
this._checkComplete();
}
clear() {
for (const input of this._inputs) {
input.value = '';
}
this._inputs[0]?.focus();
}
focus() {
// Focus first empty input, or last input if all filled
const emptyInput = this._inputs.find(input => !input.value);
if (emptyInput) {
emptyInput.focus();
} else {
this._inputs.at(-1)?.focus();
}
}
// Private
_setupInputs() {
for (const input of this._inputs) {
// Set attributes for proper OTP handling
input.setAttribute('maxlength', '1');
input.setAttribute('inputmode', 'numeric');
input.setAttribute('pattern', '\\d*');
// First input gets autocomplete for browser OTP autofill
if (input === this._inputs[0]) {
input.setAttribute('autocomplete', 'one-time-code');
} else {
input.setAttribute('autocomplete', 'off');
}
// Mask input if configured
if (this._config.mask) {
input.setAttribute('type', 'password');
}
}
}
_addEventListeners() {
for (const [index, input] of this._inputs.entries()) {
EventHandler.on(input, 'input', event => this._handleInput(event, index));
EventHandler.on(input, 'keydown', event => this._handleKeydown(event, index));
EventHandler.on(input, 'paste', event => this._handlePaste(event));
EventHandler.on(input, 'focus', event => this._handleFocus(event));
}
}
_handleInput(event, index) {
const input = event.target;
// Only allow digits
if (!/^\d*$/.test(input.value)) {
input.value = input.value.replace(/\D/g, '');
}
const {
value
} = input;
// Handle multi-character input (some browsers/autofill)
if (value.length > 1) {
// Distribute characters across inputs
const chars = value.split('');
input.value = chars[0] || '';
for (let i = 1; i < chars.length && index + i < this._inputs.length; i++) {
this._inputs[index + i].value = chars[i];
}
// Focus appropriate input
const nextIndex = Math.min(index + chars.length, this._inputs.length - 1);
this._inputs[nextIndex].focus();
} else if (value && index < this._inputs.length - 1) {
// Auto-advance to next input
this._inputs[index + 1].focus();
}
EventHandler.trigger(this._element, EVENT_INPUT, {
value: this.getValue(),
index
});
this._checkComplete();
}
_handleKeydown(event, index) {
const {
key
} = event;
switch (key) {
case 'Backspace':
{
if (!this._inputs[index].value && index > 0) {
// Move to previous input and clear it
event.preventDefault();
this._inputs[index - 1].value = '';
this._inputs[index - 1].focus();
}
break;
}
case 'Delete':
{
// Clear current and shift remaining values left
event.preventDefault();
for (let i = index; i < this._inputs.length - 1; i++) {
this._inputs[i].value = this._inputs[i + 1].value;
}
this._inputs.at(-1).value = '';
break;
}
case 'ArrowLeft':
{
if (index > 0) {
event.preventDefault();
this._inputs[index - 1].focus();
}
break;
}
case 'ArrowRight':
{
if (index < this._inputs.length - 1) {
event.preventDefault();
this._inputs[index + 1].focus();
}
break;
}
// No default
}
}
_handlePaste(event) {
event.preventDefault();
const pastedData = (event.clipboardData || window.clipboardData).getData('text');
const digits = pastedData.replace(/\D/g, '').slice(0, this._inputs.length);
if (digits) {
this.setValue(digits);
// Focus last filled input or last input
const lastIndex = Math.min(digits.length, this._inputs.length) - 1;
this._inputs[lastIndex].focus();
}
}
_handleFocus(event) {
// Select the content on focus for easy replacement
event.target.select();
}
_checkComplete() {
const value = this.getValue();
const isComplete = value.length === this._inputs.length && this._inputs.every(input => input.value !== '');
if (isComplete) {
EventHandler.trigger(this._element, EVENT_COMPLETE, {
value
});
}
}
}
/**
* Data API implementation
*/
EventHandler.on(document, `DOMContentLoaded${EVENT_KEY}${DATA_API_KEY}`, () => {
for (const element of SelectorEngine.find(SELECTOR_DATA_OTP)) {
OtpInput.getOrCreateInstance(element);
}
});
return OtpInput;
}));
//# sourceMappingURL=otp-input.js.map
+1
View File
File diff suppressed because one or more lines are too long
+41 -5
View File
@@ -1,13 +1,13 @@
/*!
* Bootstrap popover.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./tooltip.js')) :
typeof define === 'function' && define.amd ? define(['./tooltip'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Popover = factory(global.Tooltip));
})(this, (function (Tooltip) { 'use strict';
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./tooltip.js'), require('./dom/event-handler.js')) :
typeof define === 'function' && define.amd ? define(['./tooltip', './dom/event-handler'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Popover = factory(global.Tooltip, global.EventHandler));
})(this, (function (Tooltip, EventHandler) { 'use strict';
/**
* --------------------------------------------------------------------------
@@ -24,6 +24,10 @@
const NAME = 'popover';
const SELECTOR_TITLE = '.popover-header';
const SELECTOR_CONTENT = '.popover-body';
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="popover"]';
const EVENT_CLICK = 'click';
const EVENT_FOCUSIN = 'focusin';
const EVENT_MOUSEENTER = 'mouseenter';
const Default = {
...Tooltip.Default,
content: '',
@@ -70,6 +74,38 @@
}
}
/**
* Data API implementation - auto-initialize popovers
*/
const initPopover = event => {
const target = event.target.closest(SELECTOR_DATA_TOGGLE);
if (!target) {
return;
}
// Prevent default for click events to avoid navigation
if (event.type === 'click') {
event.preventDefault();
}
// Get or create instance
const popover = Popover.getOrCreateInstance(target);
// Trigger the appropriate action based on event type
if (event.type === 'click') {
popover.toggle();
} else if (event.type === 'focusin') {
popover._activeTrigger.focus = true;
popover._enter();
}
};
// Support click (default), hover, and focus triggers
EventHandler.on(document, EVENT_CLICK, SELECTOR_DATA_TOGGLE, initPopover);
EventHandler.on(document, EVENT_FOCUSIN, SELECTOR_DATA_TOGGLE, initPopover);
EventHandler.on(document, EVENT_MOUSEENTER, SELECTOR_DATA_TOGGLE, initPopover);
return Popover;
}));
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap scrollspy.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+245
View File
@@ -0,0 +1,245 @@
/*!
* Bootstrap strength.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/selector-engine.js')) :
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Strength = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine));
})(this, (function (BaseComponent, EventHandler, SelectorEngine) { 'use strict';
/**
* --------------------------------------------------------------------------
* Bootstrap strength.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
const NAME = 'strength';
const DATA_KEY = 'bs.strength';
const EVENT_KEY = `.${DATA_KEY}`;
const DATA_API_KEY = '.data-api';
const EVENT_STRENGTH_CHANGE = `strengthChange${EVENT_KEY}`;
const SELECTOR_DATA_STRENGTH = '[data-bs-strength]';
const STRENGTH_LEVELS = ['weak', 'fair', 'good', 'strong'];
const Default = {
input: null,
// Selector or element for password input
minLength: 8,
messages: {
weak: 'Weak',
fair: 'Fair',
good: 'Good',
strong: 'Strong'
},
weights: {
minLength: 1,
extraLength: 1,
lowercase: 1,
uppercase: 1,
numbers: 1,
special: 1,
multipleSpecial: 1,
longPassword: 1
},
thresholds: [2, 4, 6],
// weak ≤2, fair ≤4, good ≤6, strong >6
scorer: null // Custom scoring function (password) => number
};
const DefaultType = {
input: '(string|element|null)',
minLength: 'number',
messages: 'object',
weights: 'object',
thresholds: 'array',
scorer: '(function|null)'
};
/**
* Class definition
*/
class Strength extends BaseComponent {
constructor(element, config) {
super(element, config);
this._input = this._getInput();
this._segments = SelectorEngine.find('.strength-segment', this._element);
this._textElement = SelectorEngine.findOne('.strength-text', this._element.parentElement);
this._currentStrength = null;
if (this._input) {
this._addEventListeners();
// Check initial value
this._evaluate();
}
}
// Getters
static get Default() {
return Default;
}
static get DefaultType() {
return DefaultType;
}
static get NAME() {
return NAME;
}
// Public
getStrength() {
return this._currentStrength;
}
evaluate() {
this._evaluate();
}
// Private
_getInput() {
if (this._config.input) {
return typeof this._config.input === 'string' ? SelectorEngine.findOne(this._config.input) : this._config.input;
}
// Look for preceding password input
const parent = this._element.parentElement;
return SelectorEngine.findOne('input[type="password"]', parent);
}
_addEventListeners() {
EventHandler.on(this._input, 'input', () => this._evaluate());
EventHandler.on(this._input, 'change', () => this._evaluate());
}
_evaluate() {
const password = this._input.value;
const score = this._calculateScore(password);
const strength = this._scoreToStrength(score);
if (strength !== this._currentStrength) {
this._currentStrength = strength;
this._updateUI(strength, score);
EventHandler.trigger(this._element, EVENT_STRENGTH_CHANGE, {
strength,
score,
password: password.length > 0 ? '***' : '' // Don't expose actual password
});
}
}
_calculateScore(password) {
if (!password) {
return 0;
}
// Use custom scorer if provided
if (typeof this._config.scorer === 'function') {
return this._config.scorer(password);
}
const {
weights
} = this._config;
let score = 0;
// Length scoring
if (password.length >= this._config.minLength) {
score += weights.minLength;
}
if (password.length >= this._config.minLength + 4) {
score += weights.extraLength;
}
// Character variety
if (/[a-z]/.test(password)) {
score += weights.lowercase;
}
if (/[A-Z]/.test(password)) {
score += weights.uppercase;
}
if (/\d/.test(password)) {
score += weights.numbers;
}
// Special characters
if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
score += weights.special;
}
// Extra points for more special chars or length
if (/[!@#$%^&*(),.?":{}|<>].*[!@#$%^&*(),.?":{}|<>]/.test(password)) {
score += weights.multipleSpecial;
}
if (password.length >= 16) {
score += weights.longPassword;
}
return score;
}
_scoreToStrength(score) {
if (score === 0) {
return null;
}
const [weak, fair, good] = this._config.thresholds;
if (score <= weak) {
return 'weak';
}
if (score <= fair) {
return 'fair';
}
if (score <= good) {
return 'good';
}
return 'strong';
}
_updateUI(strength) {
// Update data attribute on element
if (strength) {
this._element.dataset.bsStrength = strength;
} else {
delete this._element.dataset.bsStrength;
}
// Update segmented meter
const strengthIndex = strength ? STRENGTH_LEVELS.indexOf(strength) : -1;
for (const [index, segment] of this._segments.entries()) {
if (index <= strengthIndex) {
segment.classList.add('active');
} else {
segment.classList.remove('active');
}
}
// Update text feedback
if (this._textElement) {
if (strength && this._config.messages[strength]) {
this._textElement.textContent = this._config.messages[strength];
this._textElement.dataset.bsStrength = strength;
// Also set the color via inheriting from parent or using CSS variable
const colorMap = {
weak: 'danger',
fair: 'warning',
good: 'info',
strong: 'success'
};
this._textElement.style.setProperty('--strength-color', `var(--${colorMap[strength]}-text)`);
} else {
this._textElement.textContent = '';
delete this._textElement.dataset.bsStrength;
}
}
}
}
/**
* Data API implementation
*/
EventHandler.on(document, `DOMContentLoaded${EVENT_KEY}${DATA_API_KEY}`, () => {
for (const element of SelectorEngine.find(SELECTOR_DATA_STRENGTH)) {
Strength.getOrCreateInstance(element);
}
});
return Strength;
}));
//# sourceMappingURL=strength.js.map
+1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap tab.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap toast.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+98
View File
@@ -0,0 +1,98 @@
/*!
* Bootstrap toggler.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./util/component-functions.js')) :
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './util/component-functions'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Toggler = factory(global.BaseComponent, global.EventHandler, global.ComponentFunctions));
})(this, (function (BaseComponent, EventHandler, componentFunctions_js) { 'use strict';
/**
* --------------------------------------------------------------------------
* Bootstrap toggler.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
const NAME = 'toggler';
const DATA_KEY = 'bs.toggler';
const EVENT_KEY = `.${DATA_KEY}`;
const EVENT_TOGGLE = `toggle${EVENT_KEY}`;
const EVENT_TOGGLED = `toggled${EVENT_KEY}`;
const EVENT_CLICK = 'click';
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="toggler"]';
const DefaultType = {
attribute: 'string',
value: '(string|number|boolean)'
};
const Default = {
attribute: 'class',
value: null
};
/**
* Class definition
*/
class Toggler extends BaseComponent {
// Getters
static get Default() {
return Default;
}
static get DefaultType() {
return DefaultType;
}
static get NAME() {
return NAME;
}
// Public
toggle() {
const toggleEvent = EventHandler.trigger(this._element, EVENT_TOGGLE);
if (toggleEvent.defaultPrevented) {
return;
}
this._execute();
EventHandler.trigger(this._element, EVENT_TOGGLED);
}
// Private
_execute() {
const {
attribute,
value
} = this._config;
if (attribute === 'id') {
return; // You have to be kidding
}
if (attribute === 'class') {
this._element.classList.toggle(value);
return;
}
// Compare as strings since getAttribute() always returns a string
if (this._element.getAttribute(attribute) === String(value)) {
this._element.removeAttribute(attribute);
return;
}
this._element.setAttribute(attribute, value);
}
}
/**
* Data API implementation
*/
componentFunctions_js.eventActionOnPlugin(Toggler, EVENT_CLICK, SELECTOR_DATA_TOGGLE, 'toggle');
return Toggler;
}));
//# sourceMappingURL=toggler.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"toggler.js","sources":["../src/toggler.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap toggler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { eventActionOnPlugin } from './util/component-functions.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'toggler'\nconst DATA_KEY = 'bs.toggler'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_TOGGLE = `toggle${EVENT_KEY}`\nconst EVENT_TOGGLED = `toggled${EVENT_KEY}`\nconst EVENT_CLICK = 'click'\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"toggler\"]'\n\nconst DefaultType = {\n attribute: 'string',\n value: '(string|number|boolean)'\n}\n\nconst Default = {\n attribute: 'class',\n value: null\n}\n\n/**\n * Class definition\n */\n\nclass Toggler extends BaseComponent {\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n const toggleEvent = EventHandler.trigger(this._element, EVENT_TOGGLE)\n\n if (toggleEvent.defaultPrevented) {\n return\n }\n\n this._execute()\n\n EventHandler.trigger(this._element, EVENT_TOGGLED)\n }\n\n // Private\n _execute() {\n const { attribute, value } = this._config\n\n if (attribute === 'id') {\n return // You have to be kidding\n }\n\n if (attribute === 'class') {\n this._element.classList.toggle(value)\n return\n }\n\n // Compare as strings since getAttribute() always returns a string\n if (this._element.getAttribute(attribute) === String(value)) {\n this._element.removeAttribute(attribute)\n return\n }\n\n this._element.setAttribute(attribute, value)\n }\n}\n\n/**\n * Data API implementation\n */\n\neventActionOnPlugin(Toggler, EVENT_CLICK, SELECTOR_DATA_TOGGLE, 'toggle')\n\nexport default Toggler\n"],"names":["NAME","DATA_KEY","EVENT_KEY","EVENT_TOGGLE","EVENT_TOGGLED","EVENT_CLICK","SELECTOR_DATA_TOGGLE","DefaultType","attribute","value","Default","Toggler","BaseComponent","toggle","toggleEvent","EventHandler","trigger","_element","defaultPrevented","_execute","_config","classList","getAttribute","String","removeAttribute","setAttribute","eventActionOnPlugin"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;;EAMA;EACA;EACA;;EAEA,MAAMA,IAAI,GAAG,SAAS;EACtB,MAAMC,QAAQ,GAAG,YAAY;EAC7B,MAAMC,SAAS,GAAG,CAAA,CAAA,EAAID,QAAQ,CAAA,CAAE;EAEhC,MAAME,YAAY,GAAG,CAAA,MAAA,EAASD,SAAS,CAAA,CAAE;EACzC,MAAME,aAAa,GAAG,CAAA,OAAA,EAAUF,SAAS,CAAA,CAAE;EAC3C,MAAMG,WAAW,GAAG,OAAO;EAE3B,MAAMC,oBAAoB,GAAG,4BAA4B;EAEzD,MAAMC,WAAW,GAAG;EAClBC,EAAAA,SAAS,EAAE,QAAQ;EACnBC,EAAAA,KAAK,EAAE;EACT,CAAC;EAED,MAAMC,OAAO,GAAG;EACdF,EAAAA,SAAS,EAAE,OAAO;EAClBC,EAAAA,KAAK,EAAE;EACT,CAAC;;EAED;EACA;EACA;;EAEA,MAAME,OAAO,SAASC,aAAa,CAAC;EAClC;IACA,WAAWF,OAAOA,GAAG;EACnB,IAAA,OAAOA,OAAO;EAChB,EAAA;IAEA,WAAWH,WAAWA,GAAG;EACvB,IAAA,OAAOA,WAAW;EACpB,EAAA;IAEA,WAAWP,IAAIA,GAAG;EAChB,IAAA,OAAOA,IAAI;EACb,EAAA;;EAEA;EACAa,EAAAA,MAAMA,GAAG;MACP,MAAMC,WAAW,GAAGC,YAAY,CAACC,OAAO,CAAC,IAAI,CAACC,QAAQ,EAAEd,YAAY,CAAC;MAErE,IAAIW,WAAW,CAACI,gBAAgB,EAAE;EAChC,MAAA;EACF,IAAA;MAEA,IAAI,CAACC,QAAQ,EAAE;MAEfJ,YAAY,CAACC,OAAO,CAAC,IAAI,CAACC,QAAQ,EAAEb,aAAa,CAAC;EACpD,EAAA;;EAEA;EACAe,EAAAA,QAAQA,GAAG;MACT,MAAM;QAAEX,SAAS;EAAEC,MAAAA;OAAO,GAAG,IAAI,CAACW,OAAO;MAEzC,IAAIZ,SAAS,KAAK,IAAI,EAAE;EACtB,MAAA,OAAM;EACR,IAAA;MAEA,IAAIA,SAAS,KAAK,OAAO,EAAE;QACzB,IAAI,CAACS,QAAQ,CAACI,SAAS,CAACR,MAAM,CAACJ,KAAK,CAAC;EACrC,MAAA;EACF,IAAA;;EAEA;EACA,IAAA,IAAI,IAAI,CAACQ,QAAQ,CAACK,YAAY,CAACd,SAAS,CAAC,KAAKe,MAAM,CAACd,KAAK,CAAC,EAAE;EAC3D,MAAA,IAAI,CAACQ,QAAQ,CAACO,eAAe,CAAChB,SAAS,CAAC;EACxC,MAAA;EACF,IAAA;MAEA,IAAI,CAACS,QAAQ,CAACQ,YAAY,CAACjB,SAAS,EAAEC,KAAK,CAAC;EAC9C,EAAA;EACF;;EAEA;EACA;EACA;;AAEAiB,2CAAmB,CAACf,OAAO,EAAEN,WAAW,EAAEC,oBAAoB,EAAE,QAAQ,CAAC;;;;;;;;"}
+188 -82
View File
@@ -1,32 +1,13 @@
/*!
* Bootstrap tooltip.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@popperjs/core'), require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./util/index.js'), require('./util/sanitizer.js'), require('./util/template-factory.js')) :
typeof define === 'function' && define.amd ? define(['@popperjs/core', './base-component', './dom/event-handler', './dom/manipulator', './util/index', './util/sanitizer', './util/template-factory'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Tooltip = factory(global["@popperjs/core"], global.BaseComponent, global.EventHandler, global.Manipulator, global.Index, global.Sanitizer, global.TemplateFactory));
})(this, (function (Popper, BaseComponent, EventHandler, Manipulator, index_js, sanitizer_js, TemplateFactory) { 'use strict';
function _interopNamespaceDefault(e) {
const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });
if (e) {
for (const k in e) {
if (k !== 'default') {
const d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: () => e[k]
});
}
}
}
n.default = e;
return Object.freeze(n);
}
const Popper__namespace = /*#__PURE__*/_interopNamespaceDefault(Popper);
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@floating-ui/dom'), require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./util/index.js'), require('./util/sanitizer.js'), require('./util/template-factory.js'), require('./util/floating-ui.js')) :
typeof define === 'function' && define.amd ? define(['@floating-ui/dom', './base-component', './dom/event-handler', './dom/manipulator', './util/index', './util/sanitizer', './util/template-factory', './util/floating-ui'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Tooltip = factory(global["@floating-ui/dom"], global.BaseComponent, global.EventHandler, global.Manipulator, global.Index, global.Sanitizer, global.TemplateFactory, global.FloatingUi));
})(this, (function (dom, BaseComponent, EventHandler, Manipulator, index_js, sanitizer_js, TemplateFactory, floatingUi_js) { 'use strict';
/**
* --------------------------------------------------------------------------
@@ -47,6 +28,7 @@
const CLASS_NAME_SHOW = 'show';
const SELECTOR_TOOLTIP_INNER = '.tooltip-inner';
const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`;
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="tooltip"]';
const EVENT_MODAL_HIDE = 'hide.bs.modal';
const TRIGGER_HOVER = 'hover';
const TRIGGER_FOCUS = 'focus';
@@ -80,7 +62,7 @@
html: false,
offset: [0, 6],
placement: 'top',
popperConfig: null,
floatingConfig: null,
sanitize: true,
sanitizeFn: null,
selector: false,
@@ -99,7 +81,7 @@
html: 'boolean',
offset: '(array|string|function)',
placement: '(string|function)',
popperConfig: '(null|object|function)',
floatingConfig: '(null|object|function)',
sanitize: 'boolean',
sanitizeFn: '(null|function)',
selector: '(string|boolean)',
@@ -114,8 +96,8 @@
class Tooltip extends BaseComponent {
constructor(element, config) {
if (typeof Popper__namespace === 'undefined') {
throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org/docs/v2/)');
if (typeof dom.computePosition === 'undefined') {
throw new TypeError('Bootstrap\'s tooltips require Floating UI (https://floating-ui.com)');
}
super(element, config);
@@ -124,12 +106,15 @@
this._timeout = 0;
this._isHovered = null;
this._activeTrigger = {};
this._popper = null;
this._floatingCleanup = null;
this._templateFactory = null;
this._newContent = null;
this._mediaQueryListeners = [];
this._responsivePlacements = null;
// Protected
this.tip = null;
this._parseResponsivePlacements();
this._setListeners();
if (!this._config.selector) {
this._fixTitle();
@@ -173,10 +158,11 @@
if (this._element.getAttribute('data-bs-original-title')) {
this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'));
}
this._disposePopper();
this._disposeFloating();
this._disposeMediaQueryListeners();
super.dispose();
}
show() {
async show() {
if (this._element.style.display === 'none') {
throw new Error('Please use show on visible elements');
}
@@ -189,9 +175,7 @@
if (showEvent.defaultPrevented || !isInTheDom) {
return;
}
// TODO: v6 remove this or make it optional
this._disposePopper();
this._disposeFloating();
const tip = this._getTipElement();
this._element.setAttribute('aria-describedby', tip.getAttribute('id'));
const {
@@ -201,7 +185,7 @@
container.append(tip);
EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED));
}
this._popper = this._createPopper(tip);
await this._createFloating(tip);
tip.classList.add(CLASS_NAME_SHOW);
// If this is a touch-enabled device we add extra
@@ -250,7 +234,7 @@
return;
}
if (!this._isHovered) {
this._disposePopper();
this._disposeFloating();
}
this._element.removeAttribute('aria-describedby');
EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN));
@@ -258,8 +242,8 @@
this._queueCallback(complete, this.tip, this._isAnimated());
}
update() {
if (this._popper) {
this._popper.update();
if (this._floatingCleanup && this.tip) {
this._updateFloatingPosition();
}
}
@@ -293,7 +277,7 @@
setContent(content) {
this._newContent = content;
if (this._isShown()) {
this._disposePopper();
this._disposeFloating();
this.show();
}
}
@@ -330,10 +314,103 @@
_isShown() {
return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW);
}
_createPopper(tip) {
_getPlacement(tip) {
// If we have responsive placements, get the one for current viewport
if (this._responsivePlacements) {
const placement = floatingUi_js.getResponsivePlacement(this._responsivePlacements, 'top');
return AttachmentMap[placement.toUpperCase()] || placement;
}
// Execute placement (can be a function)
const placement = index_js.execute(this._config.placement, [this, tip, this._element]);
const attachment = AttachmentMap[placement.toUpperCase()];
return Popper__namespace.createPopper(this._element, tip, this._getPopperConfig(attachment));
return AttachmentMap[placement.toUpperCase()] || placement;
}
_parseResponsivePlacements() {
// Only parse if placement is a string (not a function)
if (typeof this._config.placement !== 'string') {
this._responsivePlacements = null;
return;
}
this._responsivePlacements = floatingUi_js.parseResponsivePlacement(this._config.placement, 'top');
if (this._responsivePlacements) {
this._setupMediaQueryListeners();
}
}
_setupMediaQueryListeners() {
this._disposeMediaQueryListeners();
this._mediaQueryListeners = floatingUi_js.createBreakpointListeners(() => {
if (this._isShown()) {
this._updateFloatingPosition();
}
});
}
_disposeMediaQueryListeners() {
floatingUi_js.disposeBreakpointListeners(this._mediaQueryListeners);
this._mediaQueryListeners = [];
}
async _createFloating(tip) {
const placement = this._getPlacement(tip);
const arrowElement = tip.querySelector(`.${this.constructor.NAME}-arrow`);
// Initial position update
await this._updateFloatingPosition(tip, placement, arrowElement);
// Set up auto-update for scroll/resize
this._floatingCleanup = dom.autoUpdate(this._element, tip, () => this._updateFloatingPosition(tip, null, arrowElement));
}
async _updateFloatingPosition(tip = this.tip, placement = null, arrowElement = null) {
if (!tip) {
return;
}
if (!placement) {
placement = this._getPlacement(tip);
}
if (!arrowElement) {
arrowElement = tip.querySelector(`.${this.constructor.NAME}-arrow`);
}
const middleware = this._getFloatingMiddleware(arrowElement);
const floatingConfig = this._getFloatingConfig(placement, middleware);
const {
x,
y,
placement: finalPlacement,
middlewareData
} = await dom.computePosition(this._element, tip, floatingConfig);
// Apply position to tooltip
Object.assign(tip.style, {
position: 'absolute',
left: `${x}px`,
top: `${y}px`
});
// Ensure arrow is absolutely positioned within tooltip
if (arrowElement) {
arrowElement.style.position = 'absolute';
}
// Set placement attribute for CSS arrow styling
Manipulator.setDataAttribute(tip, 'placement', finalPlacement);
// Position arrow along the edge (center it) if present
// The CSS handles which edge to place it on via data-bs-placement
if (arrowElement && middlewareData.arrow) {
const {
x: arrowX,
y: arrowY
} = middlewareData.arrow;
const isVertical = finalPlacement.startsWith('top') || finalPlacement.startsWith('bottom');
// Only set the cross-axis position (centering along the edge)
// The main-axis position (which edge) is handled by CSS
Object.assign(arrowElement.style, {
left: isVertical && arrowX !== null ? `${arrowX}px` : '',
top: !isVertical && arrowY !== null ? `${arrowY}px` : '',
// Reset the other axis to let CSS handle it
right: '',
bottom: ''
});
}
}
_getOffset() {
const {
@@ -343,50 +420,57 @@
return offset.split(',').map(value => Number.parseInt(value, 10));
}
if (typeof offset === 'function') {
return popperData => offset(popperData, this._element);
// Floating UI passes different args, adapt the interface for offset function callbacks
return ({
placement,
rects
}) => {
const result = offset({
placement,
reference: rects.reference,
floating: rects.floating
}, this._element);
return result;
};
}
return offset;
}
_resolvePossibleFunction(arg) {
return index_js.execute(arg, [this._element, this._element]);
}
_getPopperConfig(attachment) {
const defaultBsPopperConfig = {
placement: attachment,
modifiers: [{
name: 'flip',
options: {
fallbackPlacements: this._config.fallbackPlacements
}
}, {
name: 'offset',
options: {
offset: this._getOffset()
}
}, {
name: 'preventOverflow',
options: {
boundary: this._config.boundary
}
}, {
name: 'arrow',
options: {
element: `.${this.constructor.NAME}-arrow`
}
}, {
name: 'preSetPlacement',
enabled: true,
phase: 'beforeMain',
fn: data => {
// Pre-set Popper's placement attribute in order to read the arrow sizes properly.
// Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement
this._getTipElement().setAttribute('data-popper-placement', data.state.placement);
}
}]
_getFloatingMiddleware(arrowElement) {
const offsetValue = this._getOffset();
const middleware = [
// Offset middleware - handles distance from reference
dom.offset(typeof offsetValue === 'function' ? offsetValue : {
mainAxis: offsetValue[1] || 0,
crossAxis: offsetValue[0] || 0
}),
// Flip middleware - handles fallback placements
dom.flip({
fallbackPlacements: this._config.fallbackPlacements
}),
// Shift middleware - prevents overflow
dom.shift({
boundary: this._config.boundary === 'clippingParents' ? 'clippingAncestors' : this._config.boundary
})];
// Arrow middleware - positions the arrow element
if (arrowElement) {
middleware.push(dom.arrow({
element: arrowElement
}));
}
return middleware;
}
_getFloatingConfig(placement, middleware) {
const defaultConfig = {
placement,
middleware
};
return {
...defaultBsPopperConfig,
...index_js.execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])
...defaultConfig,
...index_js.execute(this._config.floatingConfig, [undefined, defaultConfig])
};
}
_setListeners() {
@@ -508,10 +592,10 @@
// `Object.fromEntries(keysWithDifferentValues)`
return config;
}
_disposePopper() {
if (this._popper) {
this._popper.destroy();
this._popper = null;
_disposeFloating() {
if (this._floatingCleanup) {
this._floatingCleanup();
this._floatingCleanup = null;
}
if (this.tip) {
this.tip.remove();
@@ -520,6 +604,28 @@
}
}
/**
* Data API implementation - auto-initialize tooltips
*/
const initTooltip = event => {
const target = event.target.closest(SELECTOR_DATA_TOGGLE);
if (!target) {
return;
}
// Get or create instance and trigger the appropriate action
const tooltip = Tooltip.getOrCreateInstance(target);
// For focus events, manually trigger enter to show
if (event.type === 'focusin') {
tooltip._activeTrigger.focus = true;
tooltip._enter();
}
};
EventHandler.on(document, EVENT_FOCUSIN, SELECTOR_DATA_TOGGLE, initTooltip);
EventHandler.on(document, EVENT_MOUSEENTER, SELECTOR_DATA_TOGGLE, initTooltip);
return Tooltip;
}));
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap backdrop.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {

Some files were not shown because too many files have changed in this diff Show More