Compare commits

..

36 Commits

Author SHA1 Message Date
Mark Otto d752a8d6e4 bundlewatch again 2025-10-02 12:29:12 -07:00
Mark Otto cc4d43664e Update theme life while here 2025-10-02 12:24:54 -07:00
Mark Otto dd5ef07881 update 2025-10-02 12:24:43 -07:00
Mark Otto 277997e1f7 more buttons, new emphasis variant 2025-09-30 23:38:12 -07:00
Mark Otto 79f83a9cdd fix rebase 2025-09-30 20:49:28 -07:00
Mark Otto 3c4beecd0c redo buttons 2025-09-30 20:48:49 -07:00
Mark Otto aaf730a893 Follow up: custom switches get themes 2025-09-29 21:08:15 -07:00
Mark Otto dd579b3636 Scope docs CSS to custom layer 2025-09-29 21:08:15 -07:00
Mark Otto a4af8c3fcf v6: Update colors and theme (#41763)
* new colors

* WIP: Redo some theming

* Fix sass warnings on unquoted map keys

* Revamp colors, update docs, couple new utils

* Remove key attributes

* Bump bundlewatch

* Bundlewatch

* Fix some things up

* Clean up tables, more color changes

* Fix more table color generation, simplify markup with new Table component prop

* More docs improvements, including utilities API, and checkbox and radio theme variants
2025-09-29 20:32:12 -07:00
Mark Otto ce2cca8601 New validator (#41775)
* New validator

* update

* remove

* update
2025-09-27 20:33:21 -07:00
Mark Otto 387ea724ee Fix instances of 2xl, fix utilities generation (#41774) 2025-09-27 18:57:05 -07:00
Mark Otto 4fecde40b8 v6: New lg, xl, and 2xl breakpoints, plus some renaming (#41770)
* Update xxl breakpoint and container, rename xxl to 2xl for better scaling

Co-Authored-By: mdo <98681+mdo@users.noreply.github.com>

* note for lg

* bump bundlewatch

---------

Co-authored-by: mdo <98681+mdo@users.noreply.github.com>
2025-09-26 14:52:16 -07:00
Mark Otto 5a54f29ae3 Start to redo generate-utility() (#41769)
* Start to redo generate-utility()

* fixes

* bundlewatch
2025-09-26 14:52:06 -07:00
Mark Otto f9c8e96f70 WIP: New form controls (#41740)
* New form controls

* Split Sass, update docs

* More migration docs

* basic migration, update changelog

* Bring back btn-check for now, but move to button stylesheet

* note

* Fix link

* lint
2025-09-24 11:54:21 -07:00
Mark Otto f0c163b917 Use @forward instead of @use for proper customization (#41762)
* Use `@forward` instead of `@use` for proper customization

* linty linterson

* woof
2025-09-23 23:10:02 -07:00
Mark Otto 956de4bbaf v6 Migration guide (#41683)
* wip

* More updates
2025-09-22 21:54:18 -07:00
Mark Otto c8e8d28d29 v6: Add sub-groups to Utilities docs (#41758)
* Split the flex.mdx file into separate pages

* Add subgroups to docs utils nav

* More new groups, split pages

* Update MDX linter

* fixes
2025-09-22 11:55:12 -07:00
Mark Otto 482b0eb333 v6: justify-items and place-items utilities (#41757)
* Add utilities for place-items and justify-items

* bump
2025-09-22 10:12:07 -07:00
Mark Otto e5a1ee3d3a Remove !important from utilities, make it opt-in per utility (#41755)
* Remove !important from utilities, make it opt-in per utility

* package-lock

* Fix test
2025-09-21 22:12:17 -07:00
Mark Otto 21491d1f2f v6: Add reference tables to utilities docs, add community links to some pages (MDN, CSS Tricks) (#41749)
* wip

* improve

* Add more utility refs

* Remove important flag from the utilities

* update

* Start on helpers

* fixes

* fix links
2025-09-20 22:15:23 -07:00
Mark Otto 98d6c80cd9 New forms and buttons (#41708) 2025-09-20 22:15:23 -07:00
Mark Otto 5f910f4a09 Colocate Sass vars in their respective files (#41706)
* Co-locate Sass variables in most files

* another

* fix

* Don't bring tables into reboot, temp remove some sass vars so we don't need the co-dependency

* Move vars

* bundlewatch

* scssdocs

* Fix scssdocs
2025-09-20 22:15:23 -07:00
Mark Otto 484c4357ea Refactor accordion button to use mask, remove some Sass vars and dark mode styles (#41703) 2025-09-20 22:15:23 -07:00
Mark Otto 334d68b75a Restore both grids , enable CSS grid by default, and update mixins (#41702)
* Restore both grids and update mixins

* Bundlewatch
2025-09-20 22:15:23 -07:00
Mark Otto dbf36bbd01 First pass at CSS layers (#41701)
* First pass at CSS layers

* bundlewatch

* more bundlewatch
2025-09-20 22:15:23 -07:00
Mark Otto 062e7425b8 Migrate to Sass modules (#41512)
* Reorganize scss folder

* Migrate to Sass modules

* Migrate docs to Sass modules, comment out docs grid CSS

* Give helpers folder an index.scss, migrate ratio helper to aspect-ratio utility

* Delete node sass Action

* Modify Sass tests to pass for new Sass modules implementation

* Don't disallow calc()

* Move heading classes back to Reboot to prevent a dependency

* Utilities, some helpers, and theme colors

* Temporary fix of docs compilation

* Temporary Bundlewatch fix

* docs fix import to use

* Restyle docs callouts

* Fix docs colors

* Revert typo

* Reintroduce `css-lint-vars` npm script

* Bump to Sass v1.90.x

* Fixes

* more

* Remove

---------

Co-authored-by: Julien Déramond <juderamond@gmail.com>
2025-09-20 22:15:23 -07:00
Mark Otto ae01f064b0 Co-locate heading and some type styles in Reboot to avoid some extends and extra dependencies once we migrate to Sass modules (#41697) 2025-09-20 22:15:23 -07:00
Mark Otto c0f88d638a Remove added badges from docs pages (#41694)
* Remove added badges from docs pages

* Remove AddedIn
2025-09-20 22:15:23 -07:00
Mark Otto ca74fc29fd Clean up deprecated Sass (#41693)
* Remove all deprecated Sass variables and deprecation notices from docs components

* Fix linter error

* fix
2025-09-20 22:15:23 -07:00
Mark Otto 85adb49027 Drop -prefix (#41692) 2025-09-20 22:15:23 -07:00
Mark Otto f1edacb405 Rename mh-* and mw-* to max-h/w-*, add additional width and height va… (#41687)
* Rename mh-* and mw-* to max-h/w-*, add additional width and height values

Fixes #41330, fixes #40674.

* Bump bundlewatch
2025-09-20 22:15:23 -07:00
Mark Otto 96491cf5c8 Drop clearfix for display: flow-root (#41686)
* Drop clearfix for display: flow-root

* Fix links
2025-09-20 22:15:23 -07:00
Mark Otto ff14a64d2d Add Markdownlint for our MDX (#41685) 2025-09-20 22:15:23 -07:00
Mark Otto ea8523a4f4 Remove jQuery support in plugins (#41682) 2025-09-20 22:15:23 -07:00
Mark Otto 1d3d4339ba Move ratio from helpers to utilties (#41684)
* Convert .ratio helper to new .ratio utility

* Fix up

* Fix links for now, even though they'll be deleted
2025-09-20 22:15:23 -07:00
Mark Otto 37d69e7c38 v6: Don't disallow calc (#41681)
* Don't disallow calc()

* Remove disables that aren't needed

* Remove custom subtract and add functions

* Remove more disables

* keep it here
2025-09-20 22:15:23 -07:00
481 changed files with 62488 additions and 58064 deletions
+6 -4
View File
@@ -1,10 +1,12 @@
# https://github.com/browserslist/browserslist#readme
>= 0.5%
last 2 major versions
not dead
Chrome >= 120
Firefox >= 121
iOS >= 15.6
Safari >= 15.6
Chrome >= 60
Firefox >= 60
Firefox ESR
iOS >= 12
Safari >= 12
not Explorer <= 11
not kaios <= 2.5 # fix floating label issues in Firefox (see https://github.com/postcss/autoprefixer/issues/1533)
+13 -13
View File
@@ -2,27 +2,27 @@
"files": [
{
"path": "./dist/css/bootstrap-grid.css",
"maxSize": "9.5 kB"
"maxSize": "9.00 kB"
},
{
"path": "./dist/css/bootstrap-grid.min.css",
"maxSize": "10.25 kB"
"maxSize": "8.25 kB"
},
{
"path": "./dist/css/bootstrap-reboot.css",
"maxSize": "5.25 kB"
"maxSize": "5.0 kB"
},
{
"path": "./dist/css/bootstrap-reboot.min.css",
"maxSize": "6.75 kB"
"maxSize": "4.5 kB"
},
{
"path": "./dist/css/bootstrap-utilities.css",
"maxSize": "14.25 kB"
"maxSize": "13.5 kB"
},
{
"path": "./dist/css/bootstrap-utilities.min.css",
"maxSize": "15.0 kB"
"maxSize": "12.0 kB"
},
{
"path": "./dist/css/bootstrap.css",
@@ -30,31 +30,31 @@
},
{
"path": "./dist/css/bootstrap.min.css",
"maxSize": "36.25 kB"
"maxSize": "33.75 kB"
},
{
"path": "./dist/js/bootstrap.bundle.js",
"maxSize": "67.75 kB"
"maxSize": "43.0 kB"
},
{
"path": "./dist/js/bootstrap.bundle.min.js",
"maxSize": "41.0 kB"
"maxSize": "23.5 kB"
},
{
"path": "./dist/js/bootstrap.esm.js",
"maxSize": "39.0 kB"
"maxSize": "28.0 kB"
},
{
"path": "./dist/js/bootstrap.esm.min.js",
"maxSize": "24.0 kB"
"maxSize": "18.25 kB"
},
{
"path": "./dist/js/bootstrap.js",
"maxSize": "39.75 kB"
"maxSize": "28.75 kB"
},
{
"path": "./dist/js/bootstrap.min.js",
"maxSize": "21.25 kB"
"maxSize": "16.25 kB"
}
],
"ci": {
-1
View File
@@ -48,7 +48,6 @@
"favicons",
"fieldsets",
"flexbox",
"frontmatter",
"fullscreen",
"getbootstrap",
"Grayscale",
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: "${{ env.NODE }}"
cache: npm
+1 -2
View File
@@ -4,7 +4,6 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -26,7 +25,7 @@ jobs:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: "${{ env.NODE }}"
cache: npm
@@ -18,8 +18,7 @@ jobs:
name: calibreapp/image-actions
runs-on: ubuntu-latest
permissions:
# allow calibreapp/image-actions to update PRs and commit compressed images
contents: write
# allow calibreapp/image-actions to update PRs
pull-requests: write
steps:
- name: Clone repository
+3 -5
View File
@@ -5,13 +5,11 @@ on:
branches:
- main
- v4-dev
- v6-dev
- "!dependabot/**"
pull_request:
branches:
- main
- v4-dev
- v6-dev
- "!dependabot/**"
schedule:
- cron: "0 2 * * 4"
@@ -31,16 +29,16 @@ jobs:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3
with:
config-file: ./.github/codeql/codeql-config.yml
languages: "javascript"
queries: +security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
uses: github/codeql-action/autobuild@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3
with:
category: "/language:javascript"
+1 -2
View File
@@ -4,7 +4,6 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -29,7 +28,7 @@ jobs:
persist-credentials: false
- name: Run cspell
uses: streetsidesoftware/cspell-action@3294df585d3d639e30f3bc019cb11940b9866e95 # v8.0.0
uses: streetsidesoftware/cspell-action@dcd03dc3e8a59ec2e360d0c62db517baa0b4bb6d # v7.2.0
with:
config: ".cspell.json"
files: "**/*.{md,mdx}"
+1 -2
View File
@@ -4,7 +4,6 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -26,7 +25,7 @@ jobs:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: "${{ env.NODE }}"
cache: npm
+2 -3
View File
@@ -4,7 +4,6 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -26,7 +25,7 @@ jobs:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: "${{ env.NODE }}"
cache: npm
@@ -41,7 +40,7 @@ jobs:
run: npm run docs-html-validate
- name: Run linkinator
uses: JustinBeckwith/linkinator-action@af984b9f30f63e796ae2ea5be5e07cb587f1bbd9 # v2.3
uses: JustinBeckwith/linkinator-action@3d5ba091319fa7b0ac14703761eebb7d100e6f6d # v1.11.0
with:
paths: _site
recurse: true
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
if: github.repository == 'twbs/bootstrap'
steps:
- name: awaiting reply
uses: actions-cool/issues-helper@9861779a695cf1898bd984c727f685f351cfc372 # v3.7.2
uses: actions-cool/issues-helper@45d75b6cf72bf4f254be6230cb887ad002702491 # v3.6.3
with:
actions: "close-issues"
labels: "awaiting-reply"
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
steps:
- name: awaiting reply
if: github.event.label.name == 'needs-example'
uses: actions-cool/issues-helper@9861779a695cf1898bd984c727f685f351cfc372 # v3.7.2
uses: actions-cool/issues-helper@45d75b6cf72bf4f254be6230cb887ad002702491 # v3.6.3
with:
actions: "create-comment"
token: ${{ secrets.GITHUB_TOKEN }}
+1 -2
View File
@@ -4,7 +4,6 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -31,7 +30,7 @@ jobs:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: ${{ env.NODE }}
cache: npm
+1 -2
View File
@@ -4,7 +4,6 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -26,7 +25,7 @@ jobs:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: "${{ env.NODE }}"
cache: npm
-1
View File
@@ -4,7 +4,6 @@ on:
push:
branches:
- main
- v6-dev
workflow_dispatch:
permissions:
+4 -4
View File
@@ -12,7 +12,7 @@ on:
schedule:
- cron: '27 12 * * 2'
push:
branches: [ "main", "v6-dev" ]
branches: [ "main" ]
# Declare default permissions as read only.
permissions: read-all
@@ -39,7 +39,7 @@ jobs:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
with:
results_file: results.sarif
results_format: sarif
@@ -64,7 +64,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: SARIF file
path: results.sarif
@@ -73,6 +73,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
uses: github/codeql-action/upload-sarif@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3
with:
sarif_file: results.sarif
-1
View File
@@ -18,7 +18,6 @@
.cache
.DS_Store
.idea
.nvmrc
.project
.settings
.tmproj
-15
View File
@@ -2,23 +2,8 @@
"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",
+19 -3
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/) for information on the framework contents, templates, examples, and more.
Read the [Getting started page](https://getbootstrap.com/docs/5.3/getting-started/introduction/) for information on the framework contents, templates, examples, and more.
## Status
@@ -87,18 +87,34 @@ Within the download youll find the following directories and files, logically
│ ├── bootstrap-grid.css.map
│ ├── bootstrap-grid.min.css
│ ├── bootstrap-grid.min.css.map
│ ├── bootstrap-grid.rtl.css
│ ├── bootstrap-grid.rtl.css.map
│ ├── bootstrap-grid.rtl.min.css
│ ├── bootstrap-grid.rtl.min.css.map
│ ├── bootstrap-reboot.css
│ ├── bootstrap-reboot.css.map
│ ├── bootstrap-reboot.min.css
│ ├── bootstrap-reboot.min.css.map
│ ├── bootstrap-reboot.rtl.css
│ ├── bootstrap-reboot.rtl.css.map
│ ├── bootstrap-reboot.rtl.min.css
│ ├── bootstrap-reboot.rtl.min.css.map
│ ├── bootstrap-utilities.css
│ ├── bootstrap-utilities.css.map
│ ├── bootstrap-utilities.min.css
│ ├── bootstrap-utilities.min.css.map
│ ├── bootstrap-utilities.rtl.css
│ ├── bootstrap-utilities.rtl.css.map
│ ├── bootstrap-utilities.rtl.min.css
│ ├── bootstrap-utilities.rtl.min.css.map
│ ├── bootstrap.css
│ ├── bootstrap.css.map
│ ├── bootstrap.min.css
── bootstrap.min.css.map
── bootstrap.min.css.map
│ ├── bootstrap.rtl.css
│ ├── bootstrap.rtl.css.map
│ ├── bootstrap.rtl.min.css
│ └── bootstrap.rtl.min.css.map
└── js/
├── bootstrap.bundle.js
├── bootstrap.bundle.js.map
@@ -115,7 +131,7 @@ Within the download youll find the following directories and files, logically
```
</details>
We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [Source maps](https://web.dev/articles/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/docs/v2/). All CSS files work for both LTR and RTL layouts thanks to logical properties—simply set `dir="rtl"` on your HTML element.
We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [Source maps](https://web.dev/articles/source-maps) (`bootstrap.*.map`) are available for use with certain browsers developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/docs/v2/).
## Bugs and feature requests
-93
View File
@@ -1,93 +0,0 @@
#!/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)
-61
View File
@@ -1,61 +0,0 @@
#!/usr/bin/env node
/**
* CSS minification script using lightningcss
*
* This replaces clean-css which doesn't support modern CSS features
* like light-dark(), color-mix(), @layer, etc.
*/
import fs from 'node:fs'
import path from 'node:path'
import { transform, browserslistToTargets } from 'lightningcss'
const distDir = path.join(process.cwd(), 'dist/css')
// Get all CSS files that need minification
const cssFiles = fs.readdirSync(distDir)
.filter(file => file.endsWith('.css') && !file.endsWith('.min.css'))
// Target browsers (matching Bootstrap's browser support)
const targets = browserslistToTargets(['> 0.5%', 'last 2 versions', 'Firefox ESR', 'not dead'])
for (const file of cssFiles) {
const inputPath = path.join(distDir, file)
const outputPath = path.join(distDir, file.replace('.css', '.min.css'))
const mapPath = `${outputPath}.map`
console.log(`Minifying ${file}...`)
const inputCss = fs.readFileSync(inputPath, 'utf8')
const inputMap = fs.existsSync(`${inputPath}.map`) ?
JSON.parse(fs.readFileSync(`${inputPath}.map`, 'utf8')) :
undefined
try {
const result = transform({
filename: file,
code: Buffer.from(inputCss),
minify: true,
sourceMap: true,
inputSourceMap: inputMap ? JSON.stringify(inputMap) : undefined,
targets
})
// Write minified CSS with source map reference
const minifiedCss = `${result.code.toString()}\n/*# sourceMappingURL=${path.basename(mapPath)} */`
fs.writeFileSync(outputPath, minifiedCss)
// Write source map
if (result.map) {
fs.writeFileSync(mapPath, result.map.toString())
}
console.log(`${file}${path.basename(outputPath)}`)
} catch (error) {
console.error(` ✗ Error minifying ${file}:`, error.message)
process.exit(1)
}
}
console.log('\nCSS minification complete!')
+6 -2
View File
@@ -29,6 +29,10 @@ const files = [
file: 'dist/css/bootstrap.min.css',
configPropertyName: 'css_hash'
},
{
file: 'dist/css/bootstrap.rtl.min.css',
configPropertyName: 'css_rtl_hash'
},
{
file: 'dist/js/bootstrap.min.js',
configPropertyName: 'js_hash'
@@ -38,8 +42,8 @@ const files = [
configPropertyName: 'js_bundle_hash'
},
{
file: 'node_modules/@floating-ui/dom/dist/floating-ui.dom.umd.min.js',
configPropertyName: 'floating_ui_hash'
file: 'node_modules/@popperjs/core/dist/umd/popper.min.js',
configPropertyName: 'popper_hash'
}
]
-87
View File
@@ -1,87 +0,0 @@
#!/usr/bin/env node
/**
* Generate utilities metadata JSON from Sass
* This script compiles a special Sass file that outputs utility information as CSS comments,
* then extracts and saves it as JSON for documentation use.
*/
import { readFileSync, writeFileSync, unlinkSync } from 'node:fs'
import { execSync } from 'node:child_process'
import { fileURLToPath } from 'node:url'
import path from 'node:path'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const rootDir = path.join(__dirname, '..')
// Compile the metadata generator SCSS file
console.log('Compiling utilities metadata...')
try {
execSync(
'sass --style expanded --no-source-map build/generate-utilities-metadata.scss:dist/css/utilities-metadata.tmp.css',
{ cwd: rootDir, stdio: 'inherit' }
)
} catch {
console.error('Failed to compile metadata SCSS')
process.exit(1)
}
// Read the compiled CSS
const cssPath = path.join(rootDir, 'dist/css/utilities-metadata.tmp.css')
const cssContent = readFileSync(cssPath, 'utf8')
// Extract JSON from the CSS comment
const startMarker = 'BOOTSTRAP-UTILITIES-METADATA-START'
const endMarker = 'BOOTSTRAP-UTILITIES-METADATA-END'
const startIndex = cssContent.indexOf(startMarker)
const endIndex = cssContent.indexOf(endMarker)
if (startIndex === -1 || endIndex === -1) {
console.error('Could not find metadata markers in compiled CSS')
process.exit(1)
}
// Extract JSON content between markers
const jsonContent = cssContent
.slice(startIndex + startMarker.length, endIndex)
.trim()
// Validate JSON
try {
const parsed = JSON.parse(jsonContent)
console.log(`✓ Extracted metadata for ${Object.keys(parsed.utilities).length} utilities`)
// Write to JSON file
const outputPath = path.join(rootDir, 'dist/css/bootstrap-utilities.metadata.json')
writeFileSync(outputPath, JSON.stringify(parsed, null, 2))
console.log(`✓ Wrote metadata to ${outputPath}`)
// Clean up temporary CSS files
try {
unlinkSync(cssPath)
} catch {
// File may not exist
}
// Also clean up any other temporary variants that may have been created
const tempFiles = [
'dist/css/utilities-metadata.tmp.min.css',
'dist/css/utilities-metadata.tmp.min.css.map'
]
for (const file of tempFiles) {
try {
unlinkSync(path.join(rootDir, file))
} catch {
// File may not exist, ignore
}
}
console.log('✓ Cleaned up temporary files')
} catch (error) {
console.error('Failed to parse extracted JSON:', error.message)
console.error('Extracted content:', jsonContent.slice(0, 500))
process.exit(1)
}
-124
View File
@@ -1,124 +0,0 @@
// Generate utilities metadata JSON for documentation
// This file is compiled to extract utility information without generating CSS
@use "sass:map";
@use "sass:list";
@use "sass:string";
@use "sass:meta";
@use "../scss/config" as *;
@use "../scss/colors" as *;
@use "../scss/variables" as *;
@use "../scss/functions" as *;
@use "../scss/theme" as *;
@use "../scss/utilities" as *;
// Access the utilities map
$utilities-map: $utilities !default;
// Start JSON output
$json: '{"utilities":{' !default;
$utility-count: 0 !default;
$total-utilities: list.length(map.keys($utilities-map)) !default;
@each $key, $utility in $utilities-map {
$utility-count: $utility-count + 1;
// Skip if utility is null or false (disabled)
@if $utility {
// Extract class prefix
$class: $key;
@if map.has-key($utility, "class") {
$class: map.get($utility, "class");
}
// Extract property
$property: null;
@if map.has-key($utility, "property") {
$property: map.get($utility, "property");
}
// Extract values
$values: null;
@if map.has-key($utility, "values") {
$values: map.get($utility, "values");
}
// Generate class list
$classes: "";
@if $values {
@if meta.type-of($values) == "map" {
$value-keys: map.keys($values);
$first: true;
@each $value-key in $value-keys {
@if not $first {
$classes: $classes + ", ";
}
$class-name: "#{$class}-#{$value-key}";
@if $value-key == "null" or $value-key == null {
$class-name: $class;
}
$classes: $classes + '"' + $class-name + '"';
$first: false;
}
} @else if meta.type-of($values) == "list" {
$first: true;
@each $value in $values {
@if not $first {
$classes: $classes + ", ";
}
$class-name: "#{$class}-#{$value}";
$classes: $classes + '"' + $class-name + '"';
$first: false;
}
}
}
// Build JSON entry
$json: $json + '"' + $key + '":{"class":"' + $class + '"';
@if $property {
@if meta.type-of($property) == "string" {
$json: $json + ',"property":"' + $property + '"';
} @else if meta.type-of($property) == "list" {
$property-str: "";
$first: true;
@each $prop in $property {
@if not $first {
$property-str: $property-str + " ";
}
$property-str: $property-str + $prop;
$first: false;
}
$json: $json + ',"property":"' + $property-str + '"';
}
// Skip map properties as they're complex and don't translate to JSON well
}
@if $classes != "" {
$json: $json + ',"classes":[' + $classes + "]";
} @else {
$json: $json + ',"classes":[]';
}
$json: $json + "}";
@if $utility-count < $total-utilities {
$json: $json + ",";
}
}
}
// stylelint-disable-next-line scss/dollar-variable-default
$json: $json + "}}";
// Output as CSS comment so it appears in compiled file
/*! BOOTSTRAP-UTILITIES-METADATA-START
#{$json}
BOOTSTRAP-UTILITIES-METADATA-END */
// Prevent any actual CSS output
.bootstrap-utilities-metadata-generator {
content: "This file should not generate CSS, only metadata comments";
}
+6 -10
View File
@@ -1,6 +1,3 @@
import postcssPrefixCustomProperties from 'postcss-prefix-custom-properties'
import autoprefixer from 'autoprefixer'
const mapConfig = {
inline: false,
annotation: true,
@@ -10,12 +7,11 @@ const mapConfig = {
export default context => {
return {
map: context.file.dirname.includes('examples') ? false : mapConfig,
plugins: [
postcssPrefixCustomProperties({
prefix: 'bs-',
ignore: [/^--bs-/, /^--bd-/]
}),
autoprefixer({ cascade: false })
]
plugins: {
autoprefixer: {
cascade: false
},
rtlcss: context.env === 'RTL'
}
}
}
+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 = ['@floating-ui/dom']
const external = ['@popperjs/core']
const plugins = [
babel({
// Only transpile our source code
@@ -22,14 +22,14 @@ const plugins = [
})
]
const globals = {
'@floating-ui/dom': 'FloatingUIDOM'
'@popperjs/core': 'Popper'
}
if (BUNDLE) {
destinationFile += '.bundle'
// Remove last entry in external array to bundle Floating UI
// Remove last entry in external array to bundle Popper
external.pop()
delete globals['@floating-ui/dom']
delete globals['@popperjs/core']
plugins.push(
replace({
'process.env.NODE_ENV': '"production"',
+3 -1
View File
@@ -26,7 +26,9 @@ const docsDir = `${rootDocsDir}/docs/${versionShort}/`
// these are the files we need in the examples
const cssFiles = [
'bootstrap.min.css',
'bootstrap.min.css.map'
'bootstrap.min.css.map',
'bootstrap.rtl.min.css',
'bootstrap.rtl.min.css.map'
]
const jsFiles = [
'bootstrap.bundle.min.js',
+8 -6
View File
@@ -35,14 +35,16 @@ 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-TDmpFhAO5TwSQwPF95I/odgwpTUuv0aaVm9/0fL7b+kKe7hFBp/+9cBCMkydgGOi"
css_hash: "sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB"
css_rtl: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.rtl.min.css"
css_rtl_hash: "sha384-CfCrinSRH2IR6a4e6fy2q6ioOX7O6Mtm1L9vRvFZ1trBncWmMePhzvafv7oIcWiW"
js: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.min.js"
js_hash: "sha384-Php492snRLTR5p+hMyxpV6gYwp1avWXn4AaX31MgANrvsjr9Dpodl3Nw60L7Pewl"
js_hash: "sha384-G/EV+4j2dNv+tEPo3++6LCgdCROaejBqfUeNjuKAiuXbjrxilcCdDz6ZAVfHWe1Y"
js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.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"
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"
anchors:
min: 2
+3803 -4352
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
+564 -806
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
@@ -0,0 +1,598 @@
/*!
* 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
+5159 -5368
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 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 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
+11674 -12092
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
+2402 -3897
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
+2 -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
+677 -2392
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
+699 -2398
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-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* 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) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap base-component.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* 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) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap button.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* 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) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap carousel.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* 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) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap collapse.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* 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) {
-443
View File
@@ -1,443 +0,0 @@
/*!
* 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
@@ -1,242 +0,0 @@
/*!
* 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-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* 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) {
+2 -2
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap event-handler.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* 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) {
@@ -196,7 +196,7 @@
for (const [key, value] of Object.entries(meta)) {
try {
obj[key] = value;
} catch {
} catch (_unused) {
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-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* 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) {
@@ -34,7 +34,7 @@
}
try {
return JSON.parse(decodeURIComponent(value));
} catch {
} catch (_unused) {
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","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;;;;;;;;"}
{"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;;;;;;;;"}
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap selector-engine.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* 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) {
+124 -607
View File
@@ -1,13 +1,32 @@
/*!
* Bootstrap dropdown.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* 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('@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';
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);
/**
* --------------------------------------------------------------------------
@@ -29,16 +48,8 @@
const TAB_KEY = 'Tab';
const ARROW_UP_KEY = 'ArrowUp';
const ARROW_DOWN_KEY = 'ArrowDown';
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;
const RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button
// 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}`;
@@ -47,54 +58,40 @@
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_SUBMENU = '.dropdown-submenu';
const SELECTOR_SUBMENU_TOGGLE = '.dropdown-submenu > .dropdown-item';
const SELECTOR_NAVBAR = '.navbar';
const SELECTOR_NAVBAR_NAV = '.navbar-nav';
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 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 Default = {
autoClose: true,
boundary: 'clippingParents',
display: 'dynamic',
offset: [0, 2],
floatingConfig: null,
placement: DEFAULT_PLACEMENT,
reference: 'toggle',
// Submenu options
submenuTrigger: 'both',
// 'click', 'hover', or 'both'
submenuDelay: SUBMENU_CLOSE_DELAY
popperConfig: null,
reference: 'toggle'
};
const DefaultType = {
autoClose: '(boolean|string)',
boundary: '(string|element)',
display: 'string',
offset: '(array|string|function)',
floatingConfig: '(null|object|function)',
placement: 'string',
reference: '(string|element|object)',
submenuTrigger: 'string',
submenuDelay: 'number'
popperConfig: '(null|object|function)',
reference: '(string|element|object)'
};
/**
@@ -103,27 +100,12 @@
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._floatingCleanup = null;
this._mediaQueryListeners = [];
this._responsivePlacements = null;
this._popper = 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);
// Parse responsive placements on init
this._parseResponsivePlacements();
// Set up submenu event listeners
this._setupSubmenuListeners();
this._inNavbar = this._detectNavbar();
}
// Getters
@@ -152,7 +134,7 @@
if (showEvent.defaultPrevented) {
return;
}
this._createFloating();
this._createPopper();
// If this is a touch-enabled device we add extra
// empty mouseover listeners to the body's immediate children;
@@ -164,10 +146,9 @@
}
}
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() {
@@ -180,15 +161,15 @@
this._completeHide(relatedTarget);
}
dispose() {
this._disposeFloating();
this._disposeMediaQueryListeners();
this._closeAllSubmenus();
this._clearAllSubmenuTimeouts();
if (this._popper) {
this._popper.destroy();
}
super.dispose();
}
update() {
if (this._floatingCleanup) {
this._updateFloatingPosition();
this._inNavbar = this._detectNavbar();
if (this._popper) {
this._popper.update();
}
}
@@ -199,9 +180,6 @@
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) {
@@ -209,27 +187,26 @@
EventHandler.off(element, 'mouseover', index_js.noop);
}
}
this._disposeFloating();
if (this._popper) {
this._popper.destroy();
}
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, 'placement');
Manipulator.removeDataAttribute(this._menu, 'display');
Manipulator.removeDataAttribute(this._menu, 'popper');
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') {
// Floating UI virtual elements require a getBoundingClientRect method
// Popper virtual elements require a getBoundingClientRect method
throw new TypeError(`${NAME.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);
}
return config;
}
_createFloating() {
if (this._config.display === 'static') {
Manipulator.setDataAttribute(this._menu, 'display', 'static');
return;
_createPopper() {
if (typeof Popper__namespace === 'undefined') {
throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org/docs/v2/)');
}
let referenceElement = this._element;
if (this._config.reference === 'parent') {
@@ -239,430 +216,83 @@
} else if (typeof this._config.reference === 'object') {
referenceElement = this._config.reference;
}
// 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);
const popperConfig = this._getPopperConfig();
this._popper = Popper__namespace.createPopper(referenceElement, this._menu, popperConfig);
}
_isShown() {
return this._menu.classList.contains(CLASS_NAME_SHOW);
}
_getPlacement() {
// 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;
// Resolve logical placements (start/end) to physical (left/right) based on RTL
return resolveLogicalPlacement(placement);
}
_parseResponsivePlacements() {
this._responsivePlacements = floatingUi_js.parseResponsivePlacement(this._config.placement, DEFAULT_PLACEMENT);
if (this._responsivePlacements) {
this._setupMediaQueryListeners();
const parentDropdown = this._parent;
if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {
return PLACEMENT_RIGHT;
}
if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {
return PLACEMENT_LEFT;
}
if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {
return PLACEMENT_TOPCENTER;
}
if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {
return PLACEMENT_BOTTOMCENTER;
}
// We need to trim the value because custom properties can also include spaces
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;
}
_setupMediaQueryListeners() {
this._disposeMediaQueryListeners();
this._mediaQueryListeners = floatingUi_js.createBreakpointListeners(() => {
if (this._isShown()) {
this._updateFloatingPosition();
}
});
}
_disposeMediaQueryListeners() {
floatingUi_js.disposeBreakpointListeners(this._mediaQueryListeners);
this._mediaQueryListeners = [];
_detectNavbar() {
return this._element.closest(SELECTOR_NAVBAR) !== null;
}
_getOffset() {
const {
offset: offsetConfig
offset
} = this._config;
if (typeof offsetConfig === 'string') {
return offsetConfig.split(',').map(value => Number.parseInt(value, 10));
if (typeof offset === 'string') {
return offset.split(',').map(value => Number.parseInt(value, 10));
}
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;
};
if (typeof offset === 'function') {
return popperData => offset(popperData, this._element);
}
return offsetConfig;
return offset;
}
_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();
_getPopperConfig() {
const defaultBsPopperConfig = {
placement: this._getPlacement(),
modifiers: [{
name: 'preventOverflow',
options: {
boundary: this._config.boundary
}
}, {
name: 'offset',
options: {
offset: this._getOffset()
}
}]
};
// 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
};
// 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 {
...defaultConfig,
...index_js.execute(this._config.floatingConfig, [undefined, defaultConfig])
...defaultBsPopperConfig,
...index_js.execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])
};
}
_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
};
// 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
}) {
// 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));
const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => index_js.isVisible(element));
if (!items.length) {
return;
}
@@ -671,89 +301,6 @@
// 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;
@@ -784,62 +331,32 @@
}
}
static dataApiKeydownHandler(event) {
// If not a relevant key => not a dropdown command
// If not an UP | DOWN | ESCAPE key => not a dropdown command
// If input/textarea && if key is other than ESCAPE => not a dropdown command
const isInput = /input|textarea/i.test(event.target.tagName);
const isEscapeEvent = event.key === ESCAPE_KEY;
const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key);
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)) {
if (!isUpOrDownEvent && !isEscapeEvent) {
return;
}
if (isInput && !isEscapeEvent) {
return;
}
event.preventDefault();
// TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/
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;
}
// Handle Escape
if (isEscapeEvent && instance._isShown()) {
event.preventDefault();
if (instance._isShown()) {
// else is escape and we check if it is shown
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
@@ -0,0 +1,300 @@
/*!
* 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-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* 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) {
-238
View File
@@ -1,238 +0,0 @@
/*!
* 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
+5 -41
View File
@@ -1,13 +1,13 @@
/*!
* Bootstrap popover.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* 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('./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';
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';
/**
* --------------------------------------------------------------------------
@@ -24,10 +24,6 @@
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: '',
@@ -74,38 +70,6 @@
}
}
/**
* 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-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* 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) {
-245
View File
@@ -1,245 +0,0 @@
/*!
* 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

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