Compare commits

..

87 Commits

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

* fixes

* optimize

* Docs updates, add advanced config

* rename attr

* edits

* Update datepicker docs, improve color modes, add tests

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

* temp

* bump limits

* cleanup and simpler selectors

* few more tweaks

* Remove comment

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

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

* Bump bundlewatch

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

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

* Bundle bump

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

* Update docs

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

* bump

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

* Remove unused constants

* Fix up linter errors

* Logical properties for placement

* Better docs playground for dropdowns

* refactor and update bundles

* more tests, fix broken tests

* more tests

* more

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

* Update blue to be less indigo

* Rebuild metadata

* mdx lint

* Fix docs link while here

* Fix up some docs work while here

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

* more buttons docs cleanup

* Remove colored links for theme utilities

* Fix scssdocs

* Fix broken link

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

* better

* So much better

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

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

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

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

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

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

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

* Fix some focus styles

* Remove unused CSS

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

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

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

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

* Bump bundlewatch

* Dropdown tests

* add floating ui tests from claude

* more

* build sri

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

* Clean up elsewhere

* Remove RFS from Sass and docs

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

* New placeholder for docs examples

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

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

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

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

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

SCSS:
- scss/_dialog.scss - Component styles

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

* Modal examples now Dialog examples, needs improvement

* Remove all of Modal

* real words

* Fix layout while I'm here

* Lint Markdown

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

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

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

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

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

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

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

* Optimised images with calibre/image-actions

* fix broken link and update sidebar

* Optimised images with calibre/image-actions

---------

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

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

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

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

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

* fixes

* update images

* update permissions

* Optimised images with calibre/image-actions

---------

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

* Initial commit - planning to remove browsers-devices page

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

* Remove browsers-devices page and all references

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

* Add browser support info to Introduction page

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

---------

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

* Remove Contents page from docs and all references

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

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mdo <98681+mdo@users.noreply.github.com>
2025-11-14 22:00:47 -08:00
Mark Otto 14c1a4e0a1 Remove RTLCSS dependency, update docs and CSS to use native RTL (#41858)
* Remove RTLCSS dependency, update docs and CSS to use native RTL

* Rewrite more RTL stuff, was too verbose
2025-11-14 19:08:01 -08:00
Mark Otto a40f420eca v6: More logical properties (#41857)
* Logical properties for borders

* More logical properties for text, position, and more

* More logical for the docs
2025-11-14 12:07:45 -08:00
Mark Otto 995a74b708 v6: Switch to logical properties (#41854)
* Convert remaining utilities to all logical properties

* Move to logical properties

* Fix linter errors, bump bundlewatch
2025-11-14 12:07:45 -08:00
Mark Otto 1a2ac90d7d v6: Cleanup utilities (#41818)
* Split font utilities, update API to do more pseudo classes, drop display type classes

* Improve display docs, remove print display utils

* Document gap and grid utils, rename a couple classes

* better url

* Fix color-mix

* Undo utilities API change for now

* fix api changes

* Bundlewatch

* Linter cleanup

* Massive utilities update

- Split several utilities across smaller pages
- New underline thickness utility
- Updated text-wrap utility to use `text-wrap` property, removes white-space utils
- Adds a dozen new width utilities for now—TBD if we keep this, they're not documented yet
- Redoes the color utilities—color, background color, border color, link color—to use color-mix without attribute selectors. Faster, simpler, better support for overriding other components.
- Redesigns margin, padding, gap utils pages—shoutout Tailwind for having a better visualization here. Cribbed their approach to emphasize spacing utilities.
- Fixed up a lot of usages of color utilities, likely more to do
- Fixed up a lot of broken links, probably also more to do

* New details component, simpler a11y color contrast warning, updated docs bottom nav, updated ref tables

* Avoid name collision, update to demo thickness hover

* Rename $new-font-sizes to $font-sizes

* Update width and height docs

* Few cleanup tweaks

* Remove unused attribute selector for borders

* Fixes

* more bundle

* fixes

* spelling

* add json
2025-11-14 12:07:45 -08:00
Mark Otto db59235797 v6: Drop component variants for global .theme-* classes (#41789)
* New theme classes, revamped buttons

* bundle, lint, more cleanup

* update checks and radios

* Improve table theme variant usage

* remove table-border-factor
2025-11-14 12:07:45 -08:00
Mark Otto 08df8eb648 v6: Move some Getting Started content into new Guides section (#41797)
* Move some docs content around

* Update links
2025-11-14 12:07:45 -08:00
Mark Otto 1f768360b8 Add .prose class (#41791) 2025-11-14 12:07:45 -08:00
Mark Otto 41a1d9bdd0 v6: Range media queries (#41786)
* wip

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

* linty linterton

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mdo <98681+mdo@users.noreply.github.com>
2025-11-14 12:07:45 -08:00
Mark Otto 19effcdc50 Follow up: custom switches get themes 2025-11-14 12:07:45 -08:00
Mark Otto 77a5c263bd Scope docs CSS to custom layer 2025-11-14 12:07:45 -08:00
Mark Otto d2e1fcc15e 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-11-14 12:07:45 -08:00
Mark Otto b92d380b34 New validator (#41775)
* New validator

* update

* remove

* update
2025-11-14 12:07:44 -08:00
Mark Otto 7157c3eb5b Fix instances of 2xl, fix utilities generation (#41774) 2025-11-14 12:07:23 -08:00
Mark Otto 3ef010c876 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-11-14 12:07:23 -08:00
Mark Otto 5444ac5f64 Start to redo generate-utility() (#41769)
* Start to redo generate-utility()

* fixes

* bundlewatch
2025-11-14 12:07:23 -08:00
Mark Otto cd4bb6711e 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-11-14 12:07:23 -08:00
Mark Otto 31bb0fe027 Use @forward instead of @use for proper customization (#41762)
* Use `@forward` instead of `@use` for proper customization

* linty linterson

* woof
2025-11-14 12:07:23 -08:00
Mark Otto c80861e9c8 v6 Migration guide (#41683)
* wip

* More updates
2025-11-14 12:07:23 -08:00
Mark Otto 5a417dc9b7 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-11-14 12:07:23 -08:00
Mark Otto cc0ac410fa v6: justify-items and place-items utilities (#41757)
* Add utilities for place-items and justify-items

* bump
2025-11-14 12:07:23 -08:00
Mark Otto 8872e950f6 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-11-14 12:07:23 -08:00
Mark Otto 2487ccf8cd 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-11-14 12:07:23 -08:00
Mark Otto b6d459b095 New forms and buttons (#41708) 2025-11-14 12:07:23 -08:00
Mark Otto 160b3b4519 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-11-14 12:07:23 -08:00
Mark Otto 04b8fe71d6 Refactor accordion button to use mask, remove some Sass vars and dark mode styles (#41703) 2025-11-14 12:07:23 -08:00
Mark Otto b3ae589888 Restore both grids , enable CSS grid by default, and update mixins (#41702)
* Restore both grids and update mixins

* Bundlewatch
2025-11-14 12:07:22 -08:00
Mark Otto 5e83674516 First pass at CSS layers (#41701)
* First pass at CSS layers

* bundlewatch

* more bundlewatch
2025-11-14 12:07:22 -08:00
Mark Otto 9699052db1 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-11-14 12:07:22 -08:00
Mark Otto e214ce62e4 Co-locate heading and some type styles in Reboot to avoid some extends and extra dependencies once we migrate to Sass modules (#41697) 2025-11-14 12:07:14 -08:00
Mark Otto eedd6cfe36 Remove added badges from docs pages (#41694)
* Remove added badges from docs pages

* Remove AddedIn
2025-11-14 12:07:14 -08:00
Mark Otto d82c983ccf Clean up deprecated Sass (#41693)
* Remove all deprecated Sass variables and deprecation notices from docs components

* Fix linter error

* fix
2025-11-14 12:07:14 -08:00
Mark Otto e398044ab3 Drop -prefix (#41692) 2025-11-14 12:07:14 -08:00
Mark Otto 23bcf39ce2 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-11-14 12:07:14 -08:00
Mark Otto c726175119 Drop clearfix for display: flow-root (#41686)
* Drop clearfix for display: flow-root

* Fix links
2025-11-14 12:07:14 -08:00
Mark Otto 51f33728bf Add Markdownlint for our MDX (#41685) 2025-11-14 12:07:14 -08:00
Mark Otto 5bc6737fc2 Remove jQuery support in plugins (#41682) 2025-11-14 12:07:13 -08:00
Mark Otto 91f28cfc38 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-11-14 12:06:13 -08:00
Mark Otto a3f251e38a 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-11-14 12:06:13 -08:00
602 changed files with 68140 additions and 71812 deletions
+4 -6
View File
@@ -1,12 +1,10 @@
# https://github.com/browserslist/browserslist#readme
>= 0.5%
last 2 major versions
not dead
Chrome >= 60
Firefox >= 60
Firefox ESR
iOS >= 12
Safari >= 12
Chrome >= 120
Firefox >= 121
iOS >= 15.6
Safari >= 15.6
not Explorer <= 11
not kaios <= 2.5 # fix floating label issues in Firefox (see https://github.com/postcss/autoprefixer/issues/1533)
+14 -14
View File
@@ -2,59 +2,59 @@
"files": [
{
"path": "./dist/css/bootstrap-grid.css",
"maxSize": "6.5 kB"
"maxSize": "9.5 kB"
},
{
"path": "./dist/css/bootstrap-grid.min.css",
"maxSize": "6.0 kB"
"maxSize": "8.5 kB"
},
{
"path": "./dist/css/bootstrap-reboot.css",
"maxSize": "3.5 kB"
"maxSize": "5.25 kB"
},
{
"path": "./dist/css/bootstrap-reboot.min.css",
"maxSize": "3.25 kB"
"maxSize": "4.25 kB"
},
{
"path": "./dist/css/bootstrap-utilities.css",
"maxSize": "11.75 kB"
"maxSize": "14.25 kB"
},
{
"path": "./dist/css/bootstrap-utilities.min.css",
"maxSize": "10.75 kB"
"maxSize": "12.5 kB"
},
{
"path": "./dist/css/bootstrap.css",
"maxSize": "32.5 kB"
"maxSize": "37.75 kB"
},
{
"path": "./dist/css/bootstrap.min.css",
"maxSize": "30.25 kB"
"maxSize": "34.0 kB"
},
{
"path": "./dist/js/bootstrap.bundle.js",
"maxSize": "43.0 kB"
"maxSize": "67.75 kB"
},
{
"path": "./dist/js/bootstrap.bundle.min.js",
"maxSize": "23.5 kB"
"maxSize": "41.0 kB"
},
{
"path": "./dist/js/bootstrap.esm.js",
"maxSize": "28.0 kB"
"maxSize": "39.0 kB"
},
{
"path": "./dist/js/bootstrap.esm.min.js",
"maxSize": "18.25 kB"
"maxSize": "24.0 kB"
},
{
"path": "./dist/js/bootstrap.js",
"maxSize": "28.75 kB"
"maxSize": "39.75 kB"
},
{
"path": "./dist/js/bootstrap.min.js",
"maxSize": "16.25 kB"
"maxSize": "21.25 kB"
}
],
"ci": {
+3
View File
@@ -21,6 +21,7 @@
"callout",
"callouts",
"camelCase",
"checkgroup",
"clearfix",
"Codesniffer",
"combinator",
@@ -29,6 +30,7 @@
"Crossfade",
"crossfading",
"cssgrid",
"csstricks",
"Csvg",
"Datalists",
"Deque",
@@ -46,6 +48,7 @@
"favicons",
"fieldsets",
"flexbox",
"frontmatter",
"fullscreen",
"getbootstrap",
"Grayscale",
-162
View File
@@ -1,162 +0,0 @@
# Incident response plan
This document describes how the Bootstrap maintainers respond to and manage security or operational incidents affecting the project, its website, or its distributed releases. This plan is public to promote transparency and community trust. Operational details (e.g., private contacts, credentials, or internal coordination tools) are maintained separately in the maintainers private documentation.
---
## 1. Purpose & Scope
This plan defines how Bootstrap maintainers will:
- Identify, triage, and manage security or integrity incidents affecting project code, releases, or infrastructure.
- Communicate with the community and downstream consumers during and after an incident.
- Record lessons learned and update processes to reduce future risk.
It applies to:
- The Bootstrap source code, documentation, and build pipelines.
- Release artifacts (npm, CDN, GitHub releases).
- The main website ([https://getbootstrap.com](https://getbootstrap.com)).
- Any official Bootstrap GitHub organization infrastructure.
It does **not** cover unrelated third-party forks or integrations.
---
## 2. Definitions
- **Incident**: Any event that could compromise the confidentiality, integrity, or availability of Bootstrap code, releases, or users. Examples include:
- A discovered security vulnerability.
- A compromised GitHub account or CI/CD token.
- A malicious dependency or injected code in a release.
- Website defacement or unauthorized modification of documentation.
- Leaked secrets related to the project infrastructure.
- **Incident Commander (IC)**: The maintainer responsible for coordinating the overall response.
---
## 3. Roles & Responsibilities
| Role | Responsibilities |
|------|-------------------|
| **Incident Commander (IC)** | Coordinate the response, assign tasks, ensure timely communication. |
| **Security Maintainers** | Triage reported vulnerabilities, assess impact, create fixes, handle embargoes. |
| **Infrastructure Lead** | Manage CI/CD, website, and release infrastructure. |
| **Communications Lead** | Manage public announcements, blog posts, and social updates. |
| **Contributors & Community** | Promptly report suspected security issues and follow responsible disclosure guidelines. |
In practice, Bootstraps core team fulfills these roles collectively, assigning an IC on a per-incident basis.
---
## 4. Incident workflow
### 4.1 Detection & Reporting
- All security issues should be **privately reported** via the contact method in [`SECURITY.md`](../SECURITY.md) or through GitHubs Security Advisory mechanism.
- Maintainers also monitor:
- Automated dependency scanners (e.g., Dependabot, npm audit).
- GitHub notifications and vulnerability alerts.
- Community channels for suspicious activity.
### 4.2 Initial triage
Upon receiving a report:
1. A maintainer acknowledges receipt within 3 business days (or sooner, when possible).
Bootstrap is maintained by a small volunteer team; response times may vary slightly outside normal working hours.
2. The IC assesses severity and impact:
- **Critical:** immediate compromise of release infrastructure or code integrity.
- **High:** exploitable vulnerability in distributed assets.
- **Medium:** minor vulnerability or low-likelihood attack vector.
- **Low:** informational, no direct risk.
3. If confirmed as an incident, the IC opens a private coordination channel for maintainers and begins containment.
### 4.3 Containment & Eradication
- Revoke or rotate any affected credentials.
- Disable compromised infrastructure or build pipelines if necessary.
- Patch affected branches or dependencies.
- Verify integrity of artifacts and releases.
### 4.4 Communication
- Keep the reporting party informed (when applicable).
- For major incidents, the Communications Lead drafts a public advisory describing:
- What happened
- What was impacted
- How users can verify or mitigate
- What actions were taken
- Communications occur after containment to avoid amplifying risk.
Public disclosures are posted via:
- GitHub Security Advisory if appropriate
- [blog.getbootstrap.com/](https://blog.getbootstrap.com/)
- [Bootstrap GitHub discussions](https://github.com/orgs/twbs/discussions)
- [@getbootstrap](https://x.com/getbootstrap) on X (formerly Twitter) for critical security notices.
### 4.5 Recovery
- Validate all systems and releases are secure.
- Resume normal operations.
- Tag patched releases and notify affected users.
### 4.6 Post-incident review
Within two weeks after resolution:
- Conduct an internal debrief.
- Record:
- Root cause
- What worked / what didnt
- Remediation steps
- Documentation or automation updates needed
- Summarize lessons learned in the private maintainers wiki (with optional public summary if appropriate).
---
## 5. Severity levels & Response targets
| Severity | Example | Target response (volunteer team) |
|-----------|----------|----------------------------------|
| **Critical** | Compromised release, stolen signing keys | Acknowledge ≤ 24h (best effort), containment ≤ 48h, fix ideally ≤ 14d |
| **High** | Vulnerability enabling arbitrary code execution | Acknowledge ≤ 3 business days, fix ideally ≤ 1421d |
| **Medium** | XSS or content injection on docs site | Acknowledge ≤ 5 business days, fix in next release cycle |
| **Low** | Minor issue with limited risk | Acknowledge ≤ 7 business days, fix as scheduled |
**Note:** Timelines represent good-faith targets for a small volunteer core team, not hard SLAs. The maintainers will always prioritize public safety and transparency, even if timing varies.
---
## 6. Public disclosure principles
Bootstrap follows a responsible disclosure approach:
- Work privately with reporters and affected parties before publishing details.
- Never name reporters without consent.
- Coordinate embargo periods with downstream consumers when needed.
- Publish advisories only after patches or mitigations are available.
---
## 7. Communication Channels
| Purpose | Channel |
|----------|----------|
| Private reporting | Email address in [`SECURITY.md`](./SECURITY.md) or GitHub advisory form |
| General updates | [blog.getbootstrap.com/](https://blog.getbootstrap.com/) blog |
| Security advisories | GitHub Security Advisory dashboard |
| Social alerts | [@getbootstrap](https://x.com/getbootstrap) |
| GitHub discussion alerts | [github.com/orgs/twbs/discussions](https://github.com/orgs/twbs/discussions) |
---
## 8. Plan Maintenance
This plan is reviewed at least annually or after any major incident. Changes are approved by the Core Team and recorded in Git history.
---
_The Bootstrap maintainers are committed to transparency, user trust, and continuous improvement in our security and response practices._
+2 -2
View File
@@ -22,12 +22,12 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: "${{ env.NODE }}"
cache: npm
+3 -2
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -20,12 +21,12 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: "${{ env.NODE }}"
cache: npm
@@ -18,11 +18,12 @@ jobs:
name: calibreapp/image-actions
runs-on: ubuntu-latest
permissions:
# allow calibreapp/image-actions to update PRs
# allow calibreapp/image-actions to update PRs and commit compressed images
contents: write
pull-requests: write
steps:
- name: Clone repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
+6 -4
View File
@@ -5,11 +5,13 @@ on:
branches:
- main
- v4-dev
- v6-dev
- "!dependabot/**"
pull_request:
branches:
- main
- v4-dev
- v6-dev
- "!dependabot/**"
schedule:
- cron: "0 2 * * 4"
@@ -24,21 +26,21 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
config-file: ./.github/codeql/codeql-config.yml
languages: "javascript"
queries: +security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
uses: github/codeql-action/autobuild@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
category: "/language:javascript"
+3 -2
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -23,12 +24,12 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: Run cspell
uses: streetsidesoftware/cspell-action@e5a858a18b7e0b56e0342b1dcad796308b7341a2 # v8.1.1
uses: streetsidesoftware/cspell-action@3294df585d3d639e30f3bc019cb11940b9866e95 # v8.0.0
with:
config: ".cspell.json"
files: "**/*.{md,mdx}"
+3 -2
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -20,12 +21,12 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: "${{ env.NODE }}"
cache: npm
+4 -5
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -20,18 +21,16 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: "${{ env.NODE }}"
cache: npm
- run: java -version
- name: Install npm dependencies
run: npm ci
@@ -39,7 +38,7 @@ jobs:
run: npm run docs-build
- name: Validate HTML
run: npm run docs-vnu
run: npm run docs-html-validate
- name: Run linkinator
uses: JustinBeckwith/linkinator-action@af984b9f30f63e796ae2ea5be5e07cb587f1bbd9 # v2.3
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
if: github.repository == 'twbs/bootstrap'
steps:
- name: awaiting reply
uses: actions-cool/issues-helper@d1d51fccf39469b5458203b1369060db0ff0c0db # v3.7.4
uses: actions-cool/issues-helper@9861779a695cf1898bd984c727f685f351cfc372 # v3.7.2
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@d1d51fccf39469b5458203b1369060db0ff0c0db # v3.7.4
uses: actions-cool/issues-helper@9861779a695cf1898bd984c727f685f351cfc372 # v3.7.2
with:
actions: "create-comment"
token: ${{ secrets.GITHUB_TOKEN }}
+4 -3
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -25,12 +26,12 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: ${{ env.NODE }}
cache: npm
@@ -45,7 +46,7 @@ jobs:
run: npm run js-test
- name: Run Coveralls
uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2.3.7
uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # v2.3.6
if: ${{ !github.event.repository.fork }}
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
+3 -2
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -20,12 +21,12 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: "${{ env.NODE }}"
cache: npm
-49
View File
@@ -1,49 +0,0 @@
name: CSS (node-sass)
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
env:
FORCE_COLOR: 2
NODE: 22
permissions:
contents: read
jobs:
css:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: "${{ env.NODE }}"
- name: Build CSS with node-sass
run: |
npx --package node-sass@latest node-sass --version
npx --package node-sass@latest node-sass --output-style expanded --source-map true --source-map-contents true --precision 6 scss/ -o dist-sass/css/
ls -Al dist-sass/css
- name: Check built CSS files for Sass variables
shell: bash
run: |
SASS_VARS_FOUND=$(find "dist-sass/css/" -type f -name "*.css" -print0 | xargs -0 --no-run-if-empty grep -F "\$" || true)
if [[ -z "$SASS_VARS_FOUND" ]]; then
echo "All good, no Sass variables found!"
exit 0
else
echo "Found $(echo "$SASS_VARS_FOUND" | wc -l | bc) Sass variables:"
echo "$SASS_VARS_FOUND"
exit 1
fi
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
env:
GITHUB_REF_NAME: ${{ github.ref_name }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
+1
View File
@@ -4,6 +4,7 @@ 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" ]
branches: [ "main", "v6-dev" ]
# Declare default permissions as read only.
permissions: read-all
@@ -34,7 +34,7 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
@@ -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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
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@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
sarif_file: results.sarif
+1
View File
@@ -18,6 +18,7 @@
.cache
.DS_Store
.idea
.nvmrc
.project
.settings
.tmproj
+18
View File
@@ -0,0 +1,18 @@
{
"default": true,
"MD004": { "style": "dash" },
"MD011": false,
"MD013": false,
"MD024": false,
"MD025": false,
"MD026": false,
"MD031": false,
"MD033": false,
"MD034": false,
"MD037": false,
"MD038": false,
"MD041": false,
"MD046": false,
"line-length": false,
"no-inline-html": false
}
+15 -1
View File
@@ -2,8 +2,23 @@
"extends": [
"stylelint-config-twbs-bootstrap"
],
"plugins": [
"stylelint-order"
],
"reportInvalidScopeDisables": true,
"reportNeedlessDisables": true,
"rules": {
"order/order": [
[
{ "type": "at-rule", "name": "use" },
{ "type": "at-rule", "name": "forward" },
"dollar-variables",
"custom-properties",
"declarations",
"rules"
]
]
},
"overrides": [
{
"files": "**/*.scss",
@@ -13,7 +28,6 @@
"outline": "none"
},
"function-disallowed-list": [
"calc",
"lighten",
"darken"
],
+3 -19
View File
@@ -52,7 +52,7 @@ Several quick start options are available:
- Install with [Composer](https://getcomposer.org/): `composer require twbs/bootstrap:5.3.8`
- Install with [NuGet](https://www.nuget.org/): CSS: `Install-Package bootstrap` Sass: `Install-Package bootstrap.sass`
Read the [Getting started page](https://getbootstrap.com/docs/5.3/getting-started/introduction/) for information on the framework contents, templates, examples, and more.
Read the [Getting started page](https://getbootstrap.com/docs/5.3/getting-started/) for information on the framework contents, templates, examples, and more.
## Status
@@ -87,34 +87,18 @@ 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.rtl.css
│ ├── bootstrap.rtl.css.map
│ ├── bootstrap.rtl.min.css
│ └── bootstrap.rtl.min.css.map
── bootstrap.min.css.map
└── js/
├── bootstrap.bundle.js
├── bootstrap.bundle.js.map
@@ -131,7 +115,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/).
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.
## Bugs and feature requests
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env node
import { execSync } from 'node:child_process'
// Run bundlewatch and capture output
let stdout
let exitCode = 0
try {
stdout = execSync('npx bundlewatch --config .bundlewatch.config.json', {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe']
})
} catch (error) {
stdout = error.stdout || ''
exitCode = error.status || 1
}
// Parse lines that contain PASS or FAIL
const lines = stdout.split('\n').filter(l => l.startsWith('PASS') || l.startsWith('FAIL'))
if (lines.length === 0) {
console.log(stdout)
process.exit(exitCode)
}
// Parse size string to number (KB)
const parseSize = str => Number.parseFloat(str.replace('KB', ''))
// Calculate column widths and headroom
const rows = lines.map(line => {
const match = line.match(/(PASS|FAIL)\s+(.+?):\s+([\d.]+KB)\s+([<>])\s+([\d.]+KB)/)
if (match) {
const sizeNum = parseSize(match[3])
const maxNum = parseSize(match[5])
const headroomNum = maxNum - sizeNum
const headroom = `${headroomNum.toFixed(2)}KB`
return {
status: match[1],
file: match[2],
size: match[3],
max: match[5],
headroomNum,
headroom: match[1] === 'PASS' ? `+${headroom}` : `-${Math.abs(headroomNum).toFixed(2)}KB`
}
}
return null
}).filter(Boolean)
const maxFileLen = Math.max(...rows.map(r => r.file.length), 4)
const maxSizeLen = Math.max(...rows.map(r => r.size.length), 4)
const maxMaxLen = Math.max(...rows.map(r => r.max.length), 3)
const maxHeadroomLen = Math.max(...rows.map(r => r.headroom.length), 8)
// Build table
const hr = `+-${'-'.repeat(maxFileLen)}-+-${'-'.repeat(maxSizeLen)}-+-${'-'.repeat(maxMaxLen)}-+-${'-'.repeat(maxHeadroomLen)}-+`
console.log('')
console.log('bundlewatch results')
console.log(hr)
console.log(`| ${'File'.padEnd(maxFileLen)} | ${'Size'.padStart(maxSizeLen)} | ${'Max'.padStart(maxMaxLen)} | ${'Headroom'.padStart(maxHeadroomLen)} |`)
console.log(hr)
const green = '\u001B[32m'
const red = '\u001B[31m'
const reset = '\u001B[0m'
for (const row of rows) {
const sizeColor = row.status === 'PASS' ? green : red
const coloredSize = `${sizeColor}${row.size.padStart(maxSizeLen)}${reset}`
const headroomColor = row.headroomNum > 0.25 ? red : ''
const headroomReset = row.headroomNum > 0.25 ? reset : ''
const coloredHeadroom = `${headroomColor}${row.headroom.padStart(maxHeadroomLen)}${headroomReset}`
console.log(`| ${row.file.padEnd(maxFileLen)} | ${coloredSize} | ${row.max.padStart(maxMaxLen)} | ${coloredHeadroom} |`)
}
console.log(hr)
// Summary
const passed = rows.filter(r => r.status === 'PASS').length
const failed = rows.filter(r => r.status === 'FAIL').length
console.log('')
if (failed > 0) {
console.log(`\u001B[31mbundlewatch FAIL\u001B[0m - ${passed} passed, ${failed} failed`)
} else {
console.log(`\u001B[32mbundlewatch PASS\u001B[0m - ${passed}/${rows.length} files within limits`)
}
console.log('')
process.exit(exitCode)
+2 -6
View File
@@ -29,10 +29,6 @@ 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'
@@ -42,8 +38,8 @@ const files = [
configPropertyName: 'js_bundle_hash'
},
{
file: 'node_modules/@popperjs/core/dist/umd/popper.min.js',
configPropertyName: 'popper_hash'
file: 'node_modules/@floating-ui/dom/dist/floating-ui.dom.umd.min.js',
configPropertyName: 'floating_ui_hash'
}
]
+87
View File
@@ -0,0 +1,87 @@
#!/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
@@ -0,0 +1,124 @@
// 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";
}
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env node
/*!
* Script to run html-validate for HTML validation.
*
* This replaces the Java-based vnu-jar validator with a faster, Node.js-only solution.
* Benefits:
* - No Java dependency required
* - Faster execution (no JVM startup time)
* - Easy to configure with rule-based system
* - Better integration with Node.js build tools
*
* Copyright 2017-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
import { HtmlValidate } from 'html-validate'
import { globby } from 'globby'
const htmlValidate = new HtmlValidate({
rules: {
// Allow autocomplete on buttons (Bootstrap specific)
'attribute-allowed-values': 'off',
// Allow aria-disabled on links (Bootstrap specific)
'aria-label-misuse': 'off',
// Allow modern CSS syntax
'valid-id': 'off',
// Allow void elements with trailing slashes (Astro)
'void-style': 'off',
// Allow custom attributes
'no-unknown-elements': 'off',
'attribute-boolean-style': 'off',
'no-inline-style': 'off',
// KEEP duplicate ID checking enabled (this is important for HTML validity)
'no-dup-id': 'error'
},
elements: [
'html5',
{
// Allow custom attributes for Astro/framework compatibility
'*': {
attributes: {
'is:raw': { boolean: true },
switch: { boolean: true },
autocomplete: { enum: ['on', 'off', 'new-password', 'current-password'] }
}
}
}
]
})
async function validateHTML() {
try {
console.log('Running html-validate validation...')
// Find all HTML files
const files = await globby([
'_site/**/*.html',
'js/tests/**/*.html'
], {
ignore: ['**/node_modules/**']
})
console.log(`Validating ${files.length} HTML files...`)
let hasErrors = false
// Validate all files in parallel to avoid await-in-loop
const validationPromises = files.map(file =>
htmlValidate.validateFile(file).then(report => ({ file, report }))
)
const validationResults = await Promise.all(validationPromises)
// Process results and check for errors
for (const { file, report } of validationResults) {
if (!report.valid) {
hasErrors = true
console.error(`\nErrors in ${file}:`)
// Extract error messages with reduced nesting
const errorMessages = report.results.flatMap(result =>
result.messages.filter(message => message.severity === 2)
)
for (const message of errorMessages) {
console.error(` Line ${message.line}:${message.column} - ${message.message} (${message.ruleId})`)
}
}
}
if (hasErrors) {
console.error('\nHTML validation failed!')
process.exit(1)
} else {
console.log('✓ All HTML files are valid!')
}
} catch (error) {
console.error('HTML validation error:', error)
process.exit(1)
}
}
validateHTML()
+10 -6
View File
@@ -1,3 +1,6 @@
import postcssPrefixCustomProperties from 'postcss-prefix-custom-properties'
import autoprefixer from 'autoprefixer'
const mapConfig = {
inline: false,
annotation: true,
@@ -7,11 +10,12 @@ const mapConfig = {
export default context => {
return {
map: context.file.dirname.includes('examples') ? false : mapConfig,
plugins: {
autoprefixer: {
cascade: false
},
rtlcss: context.env === 'RTL'
}
plugins: [
postcssPrefixCustomProperties({
prefix: 'bs-',
ignore: [/^--bs-/, /^--bd-/]
}),
autoprefixer({ cascade: false })
]
}
}
+4 -4
View File
@@ -12,7 +12,7 @@ const BUNDLE = process.env.BUNDLE === 'true'
const ESM = process.env.ESM === 'true'
let destinationFile = `bootstrap${ESM ? '.esm' : ''}`
const external = ['@popperjs/core']
const external = ['@floating-ui/dom']
const plugins = [
babel({
// Only transpile our source code
@@ -22,14 +22,14 @@ const plugins = [
})
]
const globals = {
'@popperjs/core': 'Popper'
'@floating-ui/dom': 'FloatingUIDOM'
}
if (BUNDLE) {
destinationFile += '.bundle'
// Remove last entry in external array to bundle Popper
// Remove last entry in external array to bundle Floating UI
external.pop()
delete globals['@popperjs/core']
delete globals['@floating-ui/dom']
plugins.push(
replace({
'process.env.NODE_ENV': '"production"',
-66
View File
@@ -1,66 +0,0 @@
#!/usr/bin/env node
/*!
* Script to run vnu-jar if Java is available.
* Copyright 2017-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
import { execFile, spawn } from 'node:child_process'
import vnu from 'vnu-jar'
execFile('java', ['-version'], (error, stdout, stderr) => {
if (error) {
console.error('Skipping vnu-jar test; Java is probably missing.')
console.error(error)
return
}
console.log('Running vnu-jar validation...')
const is32bitJava = !/64-Bit/.test(stderr)
// vnu-jar accepts multiple ignores joined with a `|`.
// Also note that the ignores are string regular expressions.
const ignores = [
// "autocomplete" is included in <button> and checkboxes and radio <input>s due to
// Firefox's non-standard autocomplete behavior - see https://bugzilla.mozilla.org/show_bug.cgi?id=654072
'Attribute “autocomplete” is only allowed when the input type is.*',
'Attribute “autocomplete” not allowed on element “button” at this point.',
// Per https://www.w3.org/TR/html-aria/#docconformance having "aria-disabled" on a link is
// NOT RECOMMENDED, but it's still valid - we explain in the docs that it's not ideal,
// and offer more robust alternatives, but also need to show a less-than-ideal example
'An “aria-disabled” attribute whose value is “true” should not be specified on an “a” element that has an “href” attribute.',
// A `code` element with the `is:raw` attribute coming from remark-prismjs (Astro upstream possible bug)
'Attribute “is:raw” is not serializable as XML 1.0.',
'Attribute “is:raw” not allowed on element “code” at this point.',
// Astro's expecting trailing slashes on HTML tags such as <br />
'Trailing slash on void elements has no effect and interacts badly with unquoted attribute values.',
// Allow `switch` attribute.
'Attribute “switch” not allowed on element “input” at this point.'
].join('|')
const args = [
'-jar',
`"${vnu}"`,
'--asciiquotes',
'--skip-non-html',
'--Werror',
`--filterpattern "${ignores}"`,
'_site/',
'js/tests/'
]
// For the 32-bit Java we need to pass `-Xss512k`
if (is32bitJava) {
args.splice(0, 0, '-Xss512k')
}
console.log(`command used: java ${args.join(' ')}`)
return spawn('java', args, {
shell: true,
stdio: 'inherit'
})
.on('exit', process.exit)
})
+1 -3
View File
@@ -26,9 +26,7 @@ const docsDir = `${rootDocsDir}/docs/${versionShort}/`
// these are the files we need in the examples
const cssFiles = [
'bootstrap.min.css',
'bootstrap.min.css.map',
'bootstrap.rtl.min.css',
'bootstrap.rtl.min.css.map'
'bootstrap.min.css.map'
]
const jsFiles = [
'bootstrap.bundle.min.js',
+6 -8
View File
@@ -35,16 +35,14 @@ download:
cdn:
# See https://www.srihash.org for info on how to generate the hashes
css: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css"
css_hash: "sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB"
css_rtl: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.rtl.min.css"
css_rtl_hash: "sha384-CfCrinSRH2IR6a4e6fy2q6ioOX7O6Mtm1L9vRvFZ1trBncWmMePhzvafv7oIcWiW"
css_hash: "sha384-TDmpFhAO5TwSQwPF95I/odgwpTUuv0aaVm9/0fL7b+kKe7hFBp/+9cBCMkydgGOi"
js: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.min.js"
js_hash: "sha384-G/EV+4j2dNv+tEPo3++6LCgdCROaejBqfUeNjuKAiuXbjrxilcCdDz6ZAVfHWe1Y"
js_hash: "sha384-Php492snRLTR5p+hMyxpV6gYwp1avWXn4AaX31MgANrvsjr9Dpodl3Nw60L7Pewl"
js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
js_bundle_hash: "sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
popper: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"
popper_hash: "sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r"
popper_esm: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/esm/popper.min.js"
js_bundle_hash: "sha384-I2J4jlw924JZXHU9un9Mcuixq/rKhd5A8/B1NQ6ifPAiBFacZjwNcec8d6L38jQv"
floating_ui: "https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.7.0/dist/floating-ui.dom.umd.min.js"
floating_ui_hash: "sha384-R7p1RqabZNhI+RdPNIzTouzd/LBVorZ0Tn3ApcogSOk+HF3o+P0HIenrUw/n0MOj"
floating_ui_esm: "https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.7.0/dist/floating-ui.dom.esm.min.js"
anchors:
min: 2
+4401 -3852
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -4
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-4084
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+803 -561
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -4
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-598
View File
@@ -1,598 +0,0 @@
/*!
* Bootstrap Reboot v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
line-height: inherit;
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type=search]::-webkit-search-cancel-button {
cursor: pointer;
filter: grayscale(1);
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+5057 -4848
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
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
+11669 -11251
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -4
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
-12021
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4070 -2880
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2572 -1162
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
+2578 -1184
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
+5 -25
View File
@@ -1,13 +1,13 @@
/*!
* Bootstrap alert.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./util/component-functions.js'), require('./util/index.js')) :
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './util/component-functions', './util/index'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Alert = factory(global.BaseComponent, global.EventHandler, global.ComponentFunctions, global.Index));
})(this, (function (BaseComponent, EventHandler, componentFunctions_js, index_js) { 'use strict';
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./util/component-functions.js')) :
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './util/component-functions'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Alert = factory(global.BaseComponent, global.EventHandler, global.ComponentFunctions));
})(this, (function (BaseComponent, EventHandler, componentFunctions_js) { 'use strict';
/**
* --------------------------------------------------------------------------
@@ -56,20 +56,6 @@
EventHandler.trigger(this._element, EVENT_CLOSED);
this.dispose();
}
// Static
static jQueryInterface(config) {
return this.each(function () {
const data = Alert.getOrCreateInstance(this);
if (typeof config !== 'string') {
return;
}
if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {
throw new TypeError(`No method named "${config}"`);
}
data[config](this);
});
}
}
/**
@@ -78,12 +64,6 @@
componentFunctions_js.enableDismissTrigger(Alert, 'close');
/**
* jQuery
*/
index_js.defineJQueryPlugin(Alert);
return Alert;
}));
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"alert.js","sources":["../src/alert.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'alert'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE)\n\n if (closeEvent.defaultPrevented) {\n return\n }\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE)\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated)\n }\n\n // Private\n _destroyElement() {\n this._element.remove()\n EventHandler.trigger(this._element, EVENT_CLOSED)\n this.dispose()\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this)\n\n if (typeof config !== 'string') {\n return\n }\n\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](this)\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close')\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert)\n\nexport default Alert\n"],"names":["NAME","DATA_KEY","EVENT_KEY","EVENT_CLOSE","EVENT_CLOSED","CLASS_NAME_FADE","CLASS_NAME_SHOW","Alert","BaseComponent","close","closeEvent","EventHandler","trigger","_element","defaultPrevented","classList","remove","isAnimated","contains","_queueCallback","_destroyElement","dispose","jQueryInterface","config","each","data","getOrCreateInstance","undefined","startsWith","TypeError","enableDismissTrigger","defineJQueryPlugin"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;;EAOA;EACA;EACA;;EAEA,MAAMA,IAAI,GAAG,OAAO;EACpB,MAAMC,QAAQ,GAAG,UAAU;EAC3B,MAAMC,SAAS,GAAG,CAAA,CAAA,EAAID,QAAQ,CAAA,CAAE;EAEhC,MAAME,WAAW,GAAG,CAAA,KAAA,EAAQD,SAAS,CAAA,CAAE;EACvC,MAAME,YAAY,GAAG,CAAA,MAAA,EAASF,SAAS,CAAA,CAAE;EACzC,MAAMG,eAAe,GAAG,MAAM;EAC9B,MAAMC,eAAe,GAAG,MAAM;;EAE9B;EACA;EACA;;EAEA,MAAMC,KAAK,SAASC,aAAa,CAAC;EAChC;IACA,WAAWR,IAAIA,GAAG;EAChB,IAAA,OAAOA,IAAI;EACb,EAAA;;EAEA;EACAS,EAAAA,KAAKA,GAAG;MACN,MAAMC,UAAU,GAAGC,YAAY,CAACC,OAAO,CAAC,IAAI,CAACC,QAAQ,EAAEV,WAAW,CAAC;MAEnE,IAAIO,UAAU,CAACI,gBAAgB,EAAE;EAC/B,MAAA;EACF,IAAA;MAEA,IAAI,CAACD,QAAQ,CAACE,SAAS,CAACC,MAAM,CAACV,eAAe,CAAC;MAE/C,MAAMW,UAAU,GAAG,IAAI,CAACJ,QAAQ,CAACE,SAAS,CAACG,QAAQ,CAACb,eAAe,CAAC;EACpE,IAAA,IAAI,CAACc,cAAc,CAAC,MAAM,IAAI,CAACC,eAAe,EAAE,EAAE,IAAI,CAACP,QAAQ,EAAEI,UAAU,CAAC;EAC9E,EAAA;;EAEA;EACAG,EAAAA,eAAeA,GAAG;EAChB,IAAA,IAAI,CAACP,QAAQ,CAACG,MAAM,EAAE;MACtBL,YAAY,CAACC,OAAO,CAAC,IAAI,CAACC,QAAQ,EAAET,YAAY,CAAC;MACjD,IAAI,CAACiB,OAAO,EAAE;EAChB,EAAA;;EAEA;IACA,OAAOC,eAAeA,CAACC,MAAM,EAAE;EAC7B,IAAA,OAAO,IAAI,CAACC,IAAI,CAAC,YAAY;EAC3B,MAAA,MAAMC,IAAI,GAAGlB,KAAK,CAACmB,mBAAmB,CAAC,IAAI,CAAC;EAE5C,MAAA,IAAI,OAAOH,MAAM,KAAK,QAAQ,EAAE;EAC9B,QAAA;EACF,MAAA;EAEA,MAAA,IAAIE,IAAI,CAACF,MAAM,CAAC,KAAKI,SAAS,IAAIJ,MAAM,CAACK,UAAU,CAAC,GAAG,CAAC,IAAIL,MAAM,KAAK,aAAa,EAAE;EACpF,QAAA,MAAM,IAAIM,SAAS,CAAC,CAAA,iBAAA,EAAoBN,MAAM,GAAG,CAAC;EACpD,MAAA;EAEAE,MAAAA,IAAI,CAACF,MAAM,CAAC,CAAC,IAAI,CAAC;EACpB,IAAA,CAAC,CAAC;EACJ,EAAA;EACF;;EAEA;EACA;EACA;;AAEAO,4CAAoB,CAACvB,KAAK,EAAE,OAAO,CAAC;;EAEpC;EACA;EACA;;AAEAwB,6BAAkB,CAACxB,KAAK,CAAC;;;;;;;;"}
{"version":3,"file":"alert.js","sources":["../src/alert.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { enableDismissTrigger } from './util/component-functions.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'alert'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE)\n\n if (closeEvent.defaultPrevented) {\n return\n }\n\n this._element.classList.remove(CLASS_NAME_SHOW)\n\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE)\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated)\n }\n\n // Private\n _destroyElement() {\n this._element.remove()\n EventHandler.trigger(this._element, EVENT_CLOSED)\n this.dispose()\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close')\n\nexport default Alert\n"],"names":["NAME","DATA_KEY","EVENT_KEY","EVENT_CLOSE","EVENT_CLOSED","CLASS_NAME_FADE","CLASS_NAME_SHOW","Alert","BaseComponent","close","closeEvent","EventHandler","trigger","_element","defaultPrevented","classList","remove","isAnimated","contains","_queueCallback","_destroyElement","dispose","enableDismissTrigger"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;;EAMA;EACA;EACA;;EAEA,MAAMA,IAAI,GAAG,OAAO;EACpB,MAAMC,QAAQ,GAAG,UAAU;EAC3B,MAAMC,SAAS,GAAG,CAAA,CAAA,EAAID,QAAQ,CAAA,CAAE;EAEhC,MAAME,WAAW,GAAG,CAAA,KAAA,EAAQD,SAAS,CAAA,CAAE;EACvC,MAAME,YAAY,GAAG,CAAA,MAAA,EAASF,SAAS,CAAA,CAAE;EACzC,MAAMG,eAAe,GAAG,MAAM;EAC9B,MAAMC,eAAe,GAAG,MAAM;;EAE9B;EACA;EACA;;EAEA,MAAMC,KAAK,SAASC,aAAa,CAAC;EAChC;IACA,WAAWR,IAAIA,GAAG;EAChB,IAAA,OAAOA,IAAI;EACb,EAAA;;EAEA;EACAS,EAAAA,KAAKA,GAAG;MACN,MAAMC,UAAU,GAAGC,YAAY,CAACC,OAAO,CAAC,IAAI,CAACC,QAAQ,EAAEV,WAAW,CAAC;MAEnE,IAAIO,UAAU,CAACI,gBAAgB,EAAE;EAC/B,MAAA;EACF,IAAA;MAEA,IAAI,CAACD,QAAQ,CAACE,SAAS,CAACC,MAAM,CAACV,eAAe,CAAC;MAE/C,MAAMW,UAAU,GAAG,IAAI,CAACJ,QAAQ,CAACE,SAAS,CAACG,QAAQ,CAACb,eAAe,CAAC;EACpE,IAAA,IAAI,CAACc,cAAc,CAAC,MAAM,IAAI,CAACC,eAAe,EAAE,EAAE,IAAI,CAACP,QAAQ,EAAEI,UAAU,CAAC;EAC9E,EAAA;;EAEA;EACAG,EAAAA,eAAeA,GAAG;EAChB,IAAA,IAAI,CAACP,QAAQ,CAACG,MAAM,EAAE;MACtBL,YAAY,CAACC,OAAO,CAAC,IAAI,CAACC,QAAQ,EAAET,YAAY,CAAC;MACjD,IAAI,CAACiB,OAAO,EAAE;EAChB,EAAA;EACF;;EAEA;EACA;EACA;;AAEAC,4CAAoB,CAACf,KAAK,EAAE,OAAO,CAAC;;;;;;;;"}
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap base-component.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+5 -21
View File
@@ -1,13 +1,13 @@
/*!
* Bootstrap button.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./util/index.js')) :
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './util/index'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Button = factory(global.BaseComponent, global.EventHandler, global.Index));
})(this, (function (BaseComponent, EventHandler, index_js) { 'use strict';
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js')) :
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Button = factory(global.BaseComponent, global.EventHandler));
})(this, (function (BaseComponent, EventHandler) { 'use strict';
/**
* --------------------------------------------------------------------------
@@ -44,16 +44,6 @@
// Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method
this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE));
}
// Static
static jQueryInterface(config) {
return this.each(function () {
const data = Button.getOrCreateInstance(this);
if (config === 'toggle') {
data[config]();
}
});
}
}
/**
@@ -67,12 +57,6 @@
data.toggle();
});
/**
* jQuery
*/
index_js.defineJQueryPlugin(Button);
return Button;
}));
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"button.js","sources":["../src/button.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { defineJQueryPlugin } from './util/index.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'button'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]'\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE))\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this)\n\n if (config === 'toggle') {\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n event.preventDefault()\n\n const button = event.target.closest(SELECTOR_DATA_TOGGLE)\n const data = Button.getOrCreateInstance(button)\n\n data.toggle()\n})\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button)\n\nexport default Button\n"],"names":["NAME","DATA_KEY","EVENT_KEY","DATA_API_KEY","CLASS_NAME_ACTIVE","SELECTOR_DATA_TOGGLE","EVENT_CLICK_DATA_API","Button","BaseComponent","toggle","_element","setAttribute","classList","jQueryInterface","config","each","data","getOrCreateInstance","EventHandler","on","document","event","preventDefault","button","target","closest","defineJQueryPlugin"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;;EAMA;EACA;EACA;;EAEA,MAAMA,IAAI,GAAG,QAAQ;EACrB,MAAMC,QAAQ,GAAG,WAAW;EAC5B,MAAMC,SAAS,GAAG,CAAA,CAAA,EAAID,QAAQ,CAAA,CAAE;EAChC,MAAME,YAAY,GAAG,WAAW;EAEhC,MAAMC,iBAAiB,GAAG,QAAQ;EAClC,MAAMC,oBAAoB,GAAG,2BAA2B;EACxD,MAAMC,oBAAoB,GAAG,CAAA,KAAA,EAAQJ,SAAS,CAAA,EAAGC,YAAY,CAAA,CAAE;;EAE/D;EACA;EACA;;EAEA,MAAMI,MAAM,SAASC,aAAa,CAAC;EACjC;IACA,WAAWR,IAAIA,GAAG;EAChB,IAAA,OAAOA,IAAI;EACb,EAAA;;EAEA;EACAS,EAAAA,MAAMA,GAAG;EACP;EACA,IAAA,IAAI,CAACC,QAAQ,CAACC,YAAY,CAAC,cAAc,EAAE,IAAI,CAACD,QAAQ,CAACE,SAAS,CAACH,MAAM,CAACL,iBAAiB,CAAC,CAAC;EAC/F,EAAA;;EAEA;IACA,OAAOS,eAAeA,CAACC,MAAM,EAAE;EAC7B,IAAA,OAAO,IAAI,CAACC,IAAI,CAAC,YAAY;EAC3B,MAAA,MAAMC,IAAI,GAAGT,MAAM,CAACU,mBAAmB,CAAC,IAAI,CAAC;QAE7C,IAAIH,MAAM,KAAK,QAAQ,EAAE;EACvBE,QAAAA,IAAI,CAACF,MAAM,CAAC,EAAE;EAChB,MAAA;EACF,IAAA,CAAC,CAAC;EACJ,EAAA;EACF;;EAEA;EACA;EACA;;EAEAI,YAAY,CAACC,EAAE,CAACC,QAAQ,EAAEd,oBAAoB,EAAED,oBAAoB,EAAEgB,KAAK,IAAI;IAC7EA,KAAK,CAACC,cAAc,EAAE;IAEtB,MAAMC,MAAM,GAAGF,KAAK,CAACG,MAAM,CAACC,OAAO,CAACpB,oBAAoB,CAAC;EACzD,EAAA,MAAMW,IAAI,GAAGT,MAAM,CAACU,mBAAmB,CAACM,MAAM,CAAC;IAE/CP,IAAI,CAACP,MAAM,EAAE;EACf,CAAC,CAAC;;EAEF;EACA;EACA;;AAEAiB,6BAAkB,CAACnB,MAAM,CAAC;;;;;;;;"}
{"version":3,"file":"button.js","sources":["../src/button.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'button'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\n\nconst CLASS_NAME_ACTIVE = 'active'\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"button\"]'\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE))\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => {\n event.preventDefault()\n\n const button = event.target.closest(SELECTOR_DATA_TOGGLE)\n const data = Button.getOrCreateInstance(button)\n\n data.toggle()\n})\n\nexport default Button\n"],"names":["NAME","DATA_KEY","EVENT_KEY","DATA_API_KEY","CLASS_NAME_ACTIVE","SELECTOR_DATA_TOGGLE","EVENT_CLICK_DATA_API","Button","BaseComponent","toggle","_element","setAttribute","classList","EventHandler","on","document","event","preventDefault","button","target","closest","data","getOrCreateInstance"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;;EAKA;EACA;EACA;;EAEA,MAAMA,IAAI,GAAG,QAAQ;EACrB,MAAMC,QAAQ,GAAG,WAAW;EAC5B,MAAMC,SAAS,GAAG,CAAA,CAAA,EAAID,QAAQ,CAAA,CAAE;EAChC,MAAME,YAAY,GAAG,WAAW;EAEhC,MAAMC,iBAAiB,GAAG,QAAQ;EAClC,MAAMC,oBAAoB,GAAG,2BAA2B;EACxD,MAAMC,oBAAoB,GAAG,CAAA,KAAA,EAAQJ,SAAS,CAAA,EAAGC,YAAY,CAAA,CAAE;;EAE/D;EACA;EACA;;EAEA,MAAMI,MAAM,SAASC,aAAa,CAAC;EACjC;IACA,WAAWR,IAAIA,GAAG;EAChB,IAAA,OAAOA,IAAI;EACb,EAAA;;EAEA;EACAS,EAAAA,MAAMA,GAAG;EACP;EACA,IAAA,IAAI,CAACC,QAAQ,CAACC,YAAY,CAAC,cAAc,EAAE,IAAI,CAACD,QAAQ,CAACE,SAAS,CAACH,MAAM,CAACL,iBAAiB,CAAC,CAAC;EAC/F,EAAA;EACF;;EAEA;EACA;EACA;;EAEAS,YAAY,CAACC,EAAE,CAACC,QAAQ,EAAET,oBAAoB,EAAED,oBAAoB,EAAEW,KAAK,IAAI;IAC7EA,KAAK,CAACC,cAAc,EAAE;IAEtB,MAAMC,MAAM,GAAGF,KAAK,CAACG,MAAM,CAACC,OAAO,CAACf,oBAAoB,CAAC;EACzD,EAAA,MAAMgB,IAAI,GAAGd,MAAM,CAACe,mBAAmB,CAACJ,MAAM,CAAC;IAE/CG,IAAI,CAACZ,MAAM,EAAE;EACf,CAAC,CAAC;;;;;;;;"}
+1 -24
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap carousel.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
@@ -325,23 +325,6 @@
}
return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT;
}
// Static
static jQueryInterface(config) {
return this.each(function () {
const data = Carousel.getOrCreateInstance(this, config);
if (typeof config === 'number') {
data.to(config);
return;
}
if (typeof config === 'string') {
if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {
throw new TypeError(`No method named "${config}"`);
}
data[config]();
}
});
}
}
/**
@@ -376,12 +359,6 @@
}
});
/**
* jQuery
*/
index_js.defineJQueryPlugin(Carousel);
return Carousel;
}));
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -24
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap collapse.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
@@ -202,23 +202,6 @@
element.setAttribute('aria-expanded', isOpen);
}
}
// Static
static jQueryInterface(config) {
const _config = {};
if (typeof config === 'string' && /show|hide/.test(config)) {
_config.toggle = false;
}
return this.each(function () {
const data = Collapse.getOrCreateInstance(this, _config);
if (typeof config === 'string') {
if (typeof data[config] === 'undefined') {
throw new TypeError(`No method named "${config}"`);
}
data[config]();
}
});
}
}
/**
@@ -237,12 +220,6 @@
}
});
/**
* jQuery
*/
index_js.defineJQueryPlugin(Collapse);
return Collapse;
}));
+1 -1
View File
File diff suppressed because one or more lines are too long
+443
View File
@@ -0,0 +1,443 @@
/*!
* Bootstrap datepicker.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('vanilla-calendar-pro'), require('./base-component.js'), require('./dom/event-handler.js'), require('./util/index.js')) :
typeof define === 'function' && define.amd ? define(['vanilla-calendar-pro', './base-component', './dom/event-handler', './util/index'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Datepicker = factory(global["vanilla-calendar-pro"], global.BaseComponent, global.EventHandler, global.Index));
})(this, (function (vanillaCalendarPro, BaseComponent, EventHandler, index_js) { 'use strict';
/**
* --------------------------------------------------------------------------
* Bootstrap datepicker.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
const NAME = 'datepicker';
const DATA_KEY = 'bs.datepicker';
const EVENT_KEY = `.${DATA_KEY}`;
const DATA_API_KEY = '.data-api';
const EVENT_CHANGE = `change${EVENT_KEY}`;
const EVENT_SHOW = `show${EVENT_KEY}`;
const EVENT_SHOWN = `shown${EVENT_KEY}`;
const EVENT_HIDE = `hide${EVENT_KEY}`;
const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
const EVENT_FOCUSIN_DATA_API = `focusin${EVENT_KEY}${DATA_API_KEY}`;
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="datepicker"]';
const HIDE_DELAY = 100; // ms delay before hiding after selection
const Default = {
datepickerTheme: null,
// 'light', 'dark', 'auto' - explicit theme for datepicker popover only
dateMin: null,
dateMax: null,
dateFormat: null,
// Intl.DateTimeFormat options, or function(date, locale) => string
displayElement: null,
// Element to show formatted date (defaults to element for buttons)
displayMonthsCount: 1,
// Number of months to display side-by-side
firstWeekday: 1,
// Monday
inline: false,
// Render calendar inline (no popup)
locale: 'default',
positionElement: null,
// Element to position calendar relative to (defaults to input)
selectedDates: [],
selectionMode: 'single',
// 'single', 'multiple', 'multiple-ranged'
placement: 'left',
// 'left', 'center', 'right', 'auto'
vcpOptions: {} // Pass-through for any VCP option
};
const DefaultType = {
datepickerTheme: '(null|string)',
dateMin: '(null|string|number|object)',
dateMax: '(null|string|number|object)',
dateFormat: '(null|object|function)',
displayElement: '(null|string|element|boolean)',
displayMonthsCount: 'number',
firstWeekday: 'number',
inline: 'boolean',
locale: 'string',
positionElement: '(null|string|element)',
selectedDates: 'array',
selectionMode: 'string',
placement: 'string',
vcpOptions: 'object'
};
/**
* Class definition
*/
class Datepicker extends BaseComponent {
constructor(element, config) {
super(element, config);
this._calendar = null;
this._isShown = false;
this._initCalendar();
}
// Getters
static get Default() {
return Default;
}
static get DefaultType() {
return DefaultType;
}
static get NAME() {
return NAME;
}
// Public
toggle() {
if (this._config.inline) {
return; // Inline calendars are always visible
}
return this._isShown ? this.hide() : this.show();
}
show() {
if (this._config.inline) {
return; // Inline calendars are always visible
}
if (!this._calendar || index_js.isDisabled(this._element) || this._isShown) {
return;
}
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW);
if (showEvent.defaultPrevented) {
return;
}
this._calendar.show();
this._isShown = true;
EventHandler.trigger(this._element, EVENT_SHOWN);
}
hide() {
if (this._config.inline) {
return; // Inline calendars are always visible
}
if (!this._calendar || !this._isShown) {
return;
}
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);
if (hideEvent.defaultPrevented) {
return;
}
this._calendar.hide();
this._isShown = false;
EventHandler.trigger(this._element, EVENT_HIDDEN);
}
dispose() {
if (this._themeObserver) {
this._themeObserver.disconnect();
this._themeObserver = null;
}
if (this._calendar) {
this._calendar.destroy();
}
this._calendar = null;
super.dispose();
}
getSelectedDates() {
const dates = this._calendar?.context?.selectedDates;
return dates ? [...dates] : [];
}
setSelectedDates(dates) {
if (this._calendar) {
this._calendar.set({
selectedDates: dates
});
}
}
// Private
_initCalendar() {
this._isInput = this._element.tagName === 'INPUT';
this._isInline = this._config.inline;
// For inline mode, look for a hidden input child to bind to
if (this._isInline && !this._isInput) {
this._boundInput = this._element.querySelector('input[type="hidden"], input[name]');
}
this._positionElement = this._resolvePositionElement();
this._displayElement = this._resolveDisplayElement();
const calendarOptions = this._buildCalendarOptions();
// Create calendar on the position element (for correct popup positioning)
// but value updates still go to this._element (the input)
this._calendar = new vanillaCalendarPro.Calendar(this._positionElement, calendarOptions);
this._calendar.init();
// Watch for theme changes on ancestor elements (for live theme switching)
this._setupThemeObserver();
// Set initial value if input has a value
if (this._isInput && this._element.value) {
this._parseInputValue();
}
// Populate input/display with preselected dates
this._updateDisplayWithSelectedDates();
}
_updateDisplayWithSelectedDates() {
const {
selectedDates
} = this._config;
if (!selectedDates || selectedDates.length === 0) {
return;
}
const formattedDate = this._formatDateForInput(selectedDates);
if (this._isInput) {
this._element.value = formattedDate;
}
if (this._boundInput) {
this._boundInput.value = selectedDates.join(',');
}
if (this._displayElement) {
this._displayElement.textContent = formattedDate;
}
}
_resolvePositionElement() {
let {
positionElement
} = this._config;
if (typeof positionElement === 'string') {
positionElement = document.querySelector(positionElement);
}
// Use input's parent if in form-adorn
if (!positionElement && this._isInput && !this._isInline) {
const parent = this._element.closest('.form-adorn');
if (parent) {
positionElement = parent;
}
}
return positionElement || this._element;
}
_resolveDisplayElement() {
const {
displayElement
} = this._config;
if (typeof displayElement === 'string') {
return document.querySelector(displayElement);
}
// For buttons/non-inputs (not inline), look for a [data-bs-datepicker-display] child
if (displayElement === true || displayElement === null && !this._isInput && !this._isInline) {
const displayChild = this._element.querySelector('[data-bs-datepicker-display]');
return displayChild || this._element;
}
return displayElement;
}
_getThemeAncestor() {
return this._element.closest('[data-bs-theme]');
}
_getEffectiveTheme() {
// Priority: explicit datepickerTheme config > inherited from ancestor > none
const {
datepickerTheme
} = this._config;
if (datepickerTheme) {
return datepickerTheme;
}
const ancestor = this._getThemeAncestor();
return ancestor?.getAttribute('data-bs-theme') || null;
}
_syncThemeAttribute(element) {
if (!element) {
return;
}
const theme = this._getEffectiveTheme();
if (theme) {
// Copy theme to popover (needed because VCP appends to body, breaking CSS inheritance)
element.setAttribute('data-bs-theme', theme);
} else {
// No theme - remove attribute to allow natural inheritance
element.removeAttribute('data-bs-theme');
}
}
_setupThemeObserver() {
// Watch for theme changes on ancestor elements
const ancestor = this._getThemeAncestor();
if (!ancestor || this._config.datepickerTheme) {
// No ancestor to watch, or explicit datepickerTheme overrides
return;
}
this._themeObserver = new MutationObserver(() => {
this._syncThemeAttribute(this._calendar?.context?.mainElement);
});
this._themeObserver.observe(ancestor, {
attributes: true,
attributeFilter: ['data-bs-theme']
});
}
_buildCalendarOptions() {
// Get theme for VCP - use 'system' for auto-detection if no explicit theme
const theme = this._getEffectiveTheme();
// VCP uses 'system' for auto, Bootstrap uses 'auto'
const vcpTheme = !theme || theme === 'auto' ? 'system' : theme;
const calendarOptions = {
...this._config.vcpOptions,
inputMode: !this._isInline,
positionToInput: this._config.placement,
firstWeekday: this._config.firstWeekday,
locale: this._config.locale,
selectionDatesMode: this._config.selectionMode,
selectedDates: this._config.selectedDates,
displayMonthsCount: this._config.displayMonthsCount,
type: this._config.displayMonthsCount > 1 ? 'multiple' : 'default',
selectedTheme: vcpTheme,
themeAttrDetect: '[data-bs-theme]',
onClickDate: (self, event) => this._handleDateClick(self, event),
onInit: self => {
this._syncThemeAttribute(self.context.mainElement);
},
onShow: () => {
this._isShown = true;
this._syncThemeAttribute(this._calendar.context.mainElement);
},
onHide: () => {
this._isShown = false;
}
};
// Navigate to the month of the first selected date
if (this._config.selectedDates.length > 0) {
const firstDate = this._parseDate(this._config.selectedDates[0]);
calendarOptions.selectedMonth = firstDate.getMonth();
calendarOptions.selectedYear = firstDate.getFullYear();
}
if (this._config.dateMin) {
calendarOptions.dateMin = this._config.dateMin;
}
if (this._config.dateMax) {
calendarOptions.dateMax = this._config.dateMax;
}
return calendarOptions;
}
_handleDateClick(self, event) {
const selectedDates = [...self.context.selectedDates];
if (selectedDates.length > 0) {
const formattedDate = this._formatDateForInput(selectedDates);
if (this._isInput) {
this._element.value = formattedDate;
}
if (this._boundInput) {
this._boundInput.value = selectedDates.join(',');
}
if (this._displayElement) {
this._displayElement.textContent = formattedDate;
}
}
EventHandler.trigger(this._element, EVENT_CHANGE, {
dates: selectedDates,
event
});
this._maybeHideAfterSelection(selectedDates);
}
_maybeHideAfterSelection(selectedDates) {
if (this._isInline) {
return;
}
const shouldHide = this._config.selectionMode === 'single' && selectedDates.length > 0 || this._config.selectionMode === 'multiple-ranged' && selectedDates.length >= 2;
if (shouldHide) {
setTimeout(() => this.hide(), HIDE_DELAY);
}
}
_parseDate(dateStr) {
const [year, month, day] = dateStr.split('-');
return new Date(year, month - 1, day);
}
_formatDate(dateStr) {
const date = this._parseDate(dateStr);
const locale = this._config.locale === 'default' ? undefined : this._config.locale;
const {
dateFormat
} = this._config;
// Custom function formatter
if (typeof dateFormat === 'function') {
return dateFormat(date, locale);
}
// Intl.DateTimeFormat options object
if (dateFormat && typeof dateFormat === 'object') {
return new Intl.DateTimeFormat(locale, dateFormat).format(date);
}
// Default: locale-aware formatting
return date.toLocaleDateString(locale);
}
_formatDateForInput(dates) {
if (dates.length === 0) {
return '';
}
if (dates.length === 1) {
return this._formatDate(dates[0]);
}
// For date ranges, use en-dash; for multiple dates, use comma
const separator = this._config.selectionMode === 'multiple-ranged' ? ' ' : ', ';
return dates.map(d => this._formatDate(d)).join(separator);
}
_parseInputValue() {
// Try to parse the input value as a date
const value = this._element.value.trim();
if (!value) {
return;
}
const date = new Date(value);
if (!Number.isNaN(date.getTime())) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const formatted = `${year}-${month}-${day}`;
this._calendar.set({
selectedDates: [formatted]
});
}
}
}
/**
* Data API implementation
*/
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
// Only handle if not an input (inputs use focus)
// Skip inline datepickers (they're always visible)
if (this.tagName === 'INPUT' || this.dataset.bsInline === 'true') {
return;
}
event.preventDefault();
Datepicker.getOrCreateInstance(this).toggle();
});
EventHandler.on(document, EVENT_FOCUSIN_DATA_API, SELECTOR_DATA_TOGGLE, function () {
// Handle focus for input elements
if (this.tagName !== 'INPUT') {
return;
}
Datepicker.getOrCreateInstance(this).show();
});
// Auto-initialize inline datepickers on DOMContentLoaded
EventHandler.on(document, `DOMContentLoaded${EVENT_KEY}${DATA_API_KEY}`, () => {
for (const element of document.querySelectorAll(`${SELECTOR_DATA_TOGGLE}[data-bs-inline="true"]`)) {
Datepicker.getOrCreateInstance(element);
}
});
return Datepicker;
}));
//# sourceMappingURL=datepicker.js.map
+1
View File
File diff suppressed because one or more lines are too long
+242
View File
@@ -0,0 +1,242 @@
/*!
* Bootstrap dialog.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./util/component-functions.js'), require('./util/index.js')) :
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/component-functions', './util/index'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Dialog = factory(global.BaseComponent, global.EventHandler, global.Manipulator, global.SelectorEngine, global.ComponentFunctions, global.Index));
})(this, (function (BaseComponent, EventHandler, Manipulator, SelectorEngine, componentFunctions_js, index_js) { 'use strict';
/**
* --------------------------------------------------------------------------
* Bootstrap dialog.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
const NAME = 'dialog';
const DATA_KEY = 'bs.dialog';
const EVENT_KEY = `.${DATA_KEY}`;
const DATA_API_KEY = '.data-api';
const EVENT_SHOW = `show${EVENT_KEY}`;
const EVENT_SHOWN = `shown${EVENT_KEY}`;
const EVENT_HIDE = `hide${EVENT_KEY}`;
const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`;
const EVENT_CANCEL = `cancel${EVENT_KEY}`;
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
const CLASS_NAME_STATIC = 'dialog-static';
const CLASS_NAME_OPEN = 'dialog-open';
const CLASS_NAME_NONMODAL = 'dialog-nonmodal';
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dialog"]';
const SELECTOR_OPEN_MODAL_DIALOG = 'dialog.dialog[open]:not(.dialog-nonmodal)';
const Default = {
backdrop: true,
// true (click dismisses) or 'static' (click does nothing) - only applies to modal dialogs
keyboard: true,
modal: true // true uses showModal(), false uses show() for non-modal dialogs
};
const DefaultType = {
backdrop: '(boolean|string)',
keyboard: 'boolean',
modal: 'boolean'
};
/**
* Class definition
*/
class Dialog extends BaseComponent {
constructor(element, config) {
super(element, config);
this._isTransitioning = false;
this._addEventListeners();
}
// Getters
static get Default() {
return Default;
}
static get DefaultType() {
return DefaultType;
}
static get NAME() {
return NAME;
}
// Public
toggle(relatedTarget) {
return this._element.open ? this.hide() : this.show(relatedTarget);
}
show(relatedTarget) {
if (this._element.open || this._isTransitioning) {
return;
}
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {
relatedTarget
});
if (showEvent.defaultPrevented) {
return;
}
this._isTransitioning = true;
if (this._config.modal) {
// Modal dialog: use showModal() for focus trapping, backdrop, and top layer
this._element.showModal();
// Prevent body scroll for modal dialogs
document.body.classList.add(CLASS_NAME_OPEN);
} else {
// Non-modal dialog: use show() - no backdrop, no focus trap, no top layer
this._element.classList.add(CLASS_NAME_NONMODAL);
this._element.show();
}
this._queueCallback(() => {
this._isTransitioning = false;
EventHandler.trigger(this._element, EVENT_SHOWN, {
relatedTarget
});
}, this._element, this._isAnimated());
}
hide() {
if (!this._element.open || this._isTransitioning) {
return;
}
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);
if (hideEvent.defaultPrevented) {
return;
}
this._isTransitioning = true;
this._queueCallback(() => this._hideDialog(), this._element, this._isAnimated());
}
dispose() {
EventHandler.off(this._element, EVENT_KEY);
super.dispose();
}
handleUpdate() {
// Provided for API consistency with Modal.
// Native dialogs handle their own positioning.
}
// Private
_hideDialog() {
this._element.close();
this._element.classList.remove(CLASS_NAME_NONMODAL);
this._isTransitioning = false;
// Only restore body scroll if no other modal dialogs are open
if (!document.querySelector(SELECTOR_OPEN_MODAL_DIALOG)) {
document.body.classList.remove(CLASS_NAME_OPEN);
}
EventHandler.trigger(this._element, EVENT_HIDDEN);
}
_isAnimated() {
return this._element.classList.contains('fade');
}
_triggerBackdropTransition() {
const hidePreventedEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);
if (hidePreventedEvent.defaultPrevented) {
return;
}
this._element.classList.add(CLASS_NAME_STATIC);
this._queueCallback(() => {
this._element.classList.remove(CLASS_NAME_STATIC);
}, this._element);
}
_addEventListeners() {
// Handle native cancel event (Escape key) - only fires for modal dialogs
EventHandler.on(this._element, 'cancel', event => {
// Prevent native close behavior - we'll handle it
event.preventDefault();
if (!this._config.keyboard) {
this._triggerBackdropTransition();
return;
}
EventHandler.trigger(this._element, EVENT_CANCEL);
this.hide();
});
// Handle Escape key for non-modal dialogs (native cancel doesn't fire for show())
EventHandler.on(this._element, 'keydown', event => {
if (event.key !== 'Escape' || this._config.modal) {
return;
}
event.preventDefault();
if (!this._config.keyboard) {
return;
}
EventHandler.trigger(this._element, EVENT_CANCEL);
this.hide();
});
// Handle backdrop clicks (only applies to modal dialogs)
// Native <dialog> fires click on the dialog element when backdrop is clicked
EventHandler.on(this._element, 'click', event => {
// Only handle clicks directly on the dialog (backdrop area)
// Non-modal dialogs don't have a backdrop
if (event.target !== this._element || !this._config.modal) {
return;
}
if (this._config.backdrop === 'static') {
this._triggerBackdropTransition();
return;
}
// Default: click backdrop to dismiss
this.hide();
});
}
}
/**
* Data API implementation
*/
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
const target = SelectorEngine.getElementFromSelector(this);
if (['A', 'AREA'].includes(this.tagName)) {
event.preventDefault();
}
EventHandler.one(target, EVENT_SHOW, showEvent => {
if (showEvent.defaultPrevented) {
return;
}
EventHandler.one(target, EVENT_HIDDEN, () => {
if (index_js.isVisible(this)) {
this.focus();
}
});
});
// Get config from trigger's data attributes
const config = Manipulator.getDataAttributes(this);
// Check if trigger is inside an open dialog
const currentDialog = this.closest('dialog[open]');
const shouldSwap = currentDialog && currentDialog !== target;
if (shouldSwap) {
// Open new dialog first (its backdrop appears over current)
const newDialog = Dialog.getOrCreateInstance(target, config);
newDialog.show(this);
// Close the current dialog (no backdrop flash since new one is already open)
const currentInstance = Dialog.getInstance(currentDialog);
if (currentInstance) {
currentInstance.hide();
}
return;
}
const data = Dialog.getOrCreateInstance(target, config);
data.toggle(this);
});
componentFunctions_js.enableDismissTrigger(Dialog);
return Dialog;
}));
//# sourceMappingURL=dialog.js.map
+1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap data.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+8 -31
View File
@@ -1,13 +1,13 @@
/*!
* Bootstrap event-handler.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('../util/index.js')) :
typeof define === 'function' && define.amd ? define(['../util/index'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.EventHandler = factory(global.Index));
})(this, (function (index_js) { 'use strict';
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.EventHandler = factory());
})(this, (function () { 'use strict';
/**
* --------------------------------------------------------------------------
@@ -16,7 +16,6 @@
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
@@ -185,33 +184,11 @@
if (typeof event !== 'string' || !element) {
return null;
}
const $ = index_js.getjQuery();
const typeEvent = getTypeEvent(event);
const inNamespace = event !== typeEvent;
let jQueryEvent = null;
let bubbles = true;
let nativeDispatch = true;
let defaultPrevented = false;
if (inNamespace && $) {
jQueryEvent = $.Event(event, args);
$(element).trigger(jQueryEvent);
bubbles = !jQueryEvent.isPropagationStopped();
nativeDispatch = !jQueryEvent.isImmediatePropagationStopped();
defaultPrevented = jQueryEvent.isDefaultPrevented();
}
const evt = hydrateObj(new Event(event, {
bubbles,
bubbles: true,
cancelable: true
}), args);
if (defaultPrevented) {
evt.preventDefault();
}
if (nativeDispatch) {
element.dispatchEvent(evt);
}
if (evt.defaultPrevented && jQueryEvent) {
jQueryEvent.preventDefault();
}
element.dispatchEvent(evt);
return evt;
}
};
@@ -219,7 +196,7 @@
for (const [key, value] of Object.entries(meta)) {
try {
obj[key] = value;
} catch (_unused) {
} catch {
Object.defineProperty(obj, key, {
configurable: true,
get() {
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap manipulator.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
@@ -34,7 +34,7 @@
}
try {
return JSON.parse(decodeURIComponent(value));
} catch (_unused) {
} catch {
return value;
}
}
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"manipulator.js","sources":["../../src/dom/manipulator.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n"],"names":["normalizeData","value","Number","toString","JSON","parse","decodeURIComponent","_unused","normalizeDataKey","key","replace","chr","toLowerCase","Manipulator","setDataAttribute","element","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","Object","keys","dataset","filter","startsWith","pureKey","charAt","slice","getDataAttribute","getAttribute"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;EAEA,SAASA,aAAaA,CAACC,KAAK,EAAE;IAC5B,IAAIA,KAAK,KAAK,MAAM,EAAE;EACpB,IAAA,OAAO,IAAI;EACb,EAAA;IAEA,IAAIA,KAAK,KAAK,OAAO,EAAE;EACrB,IAAA,OAAO,KAAK;EACd,EAAA;IAEA,IAAIA,KAAK,KAAKC,MAAM,CAACD,KAAK,CAAC,CAACE,QAAQ,EAAE,EAAE;MACtC,OAAOD,MAAM,CAACD,KAAK,CAAC;EACtB,EAAA;EAEA,EAAA,IAAIA,KAAK,KAAK,EAAE,IAAIA,KAAK,KAAK,MAAM,EAAE;EACpC,IAAA,OAAO,IAAI;EACb,EAAA;EAEA,EAAA,IAAI,OAAOA,KAAK,KAAK,QAAQ,EAAE;EAC7B,IAAA,OAAOA,KAAK;EACd,EAAA;IAEA,IAAI;MACF,OAAOG,IAAI,CAACC,KAAK,CAACC,kBAAkB,CAACL,KAAK,CAAC,CAAC;IAC9C,CAAC,CAAC,OAAAM,OAAA,EAAM;EACN,IAAA,OAAON,KAAK;EACd,EAAA;EACF;EAEA,SAASO,gBAAgBA,CAACC,GAAG,EAAE;EAC7B,EAAA,OAAOA,GAAG,CAACC,OAAO,CAAC,QAAQ,EAAEC,GAAG,IAAI,CAAA,CAAA,EAAIA,GAAG,CAACC,WAAW,EAAE,EAAE,CAAC;EAC9D;AAEA,QAAMC,WAAW,GAAG;EAClBC,EAAAA,gBAAgBA,CAACC,OAAO,EAAEN,GAAG,EAAER,KAAK,EAAE;MACpCc,OAAO,CAACC,YAAY,CAAC,CAAA,QAAA,EAAWR,gBAAgB,CAACC,GAAG,CAAC,CAAA,CAAE,EAAER,KAAK,CAAC;IACjE,CAAC;EAEDgB,EAAAA,mBAAmBA,CAACF,OAAO,EAAEN,GAAG,EAAE;MAChCM,OAAO,CAACG,eAAe,CAAC,CAAA,QAAA,EAAWV,gBAAgB,CAACC,GAAG,CAAC,CAAA,CAAE,CAAC;IAC7D,CAAC;IAEDU,iBAAiBA,CAACJ,OAAO,EAAE;MACzB,IAAI,CAACA,OAAO,EAAE;EACZ,MAAA,OAAO,EAAE;EACX,IAAA;MAEA,MAAMK,UAAU,GAAG,EAAE;EACrB,IAAA,MAAMC,MAAM,GAAGC,MAAM,CAACC,IAAI,CAACR,OAAO,CAACS,OAAO,CAAC,CAACC,MAAM,CAAChB,GAAG,IAAIA,GAAG,CAACiB,UAAU,CAAC,IAAI,CAAC,IAAI,CAACjB,GAAG,CAACiB,UAAU,CAAC,UAAU,CAAC,CAAC;EAE9G,IAAA,KAAK,MAAMjB,GAAG,IAAIY,MAAM,EAAE;QACxB,IAAIM,OAAO,GAAGlB,GAAG,CAACC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;EACpCiB,MAAAA,OAAO,GAAGA,OAAO,CAACC,MAAM,CAAC,CAAC,CAAC,CAAChB,WAAW,EAAE,GAAGe,OAAO,CAACE,KAAK,CAAC,CAAC,CAAC;EAC5DT,MAAAA,UAAU,CAACO,OAAO,CAAC,GAAG3B,aAAa,CAACe,OAAO,CAACS,OAAO,CAACf,GAAG,CAAC,CAAC;EAC3D,IAAA;EAEA,IAAA,OAAOW,UAAU;IACnB,CAAC;EAEDU,EAAAA,gBAAgBA,CAACf,OAAO,EAAEN,GAAG,EAAE;EAC7B,IAAA,OAAOT,aAAa,CAACe,OAAO,CAACgB,YAAY,CAAC,CAAA,QAAA,EAAWvB,gBAAgB,CAACC,GAAG,CAAC,CAAA,CAAE,CAAC,CAAC;EAChF,EAAA;EACF;;;;;;;;"}
{"version":3,"file":"manipulator.js","sources":["../../src/dom/manipulator.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n"],"names":["normalizeData","value","Number","toString","JSON","parse","decodeURIComponent","normalizeDataKey","key","replace","chr","toLowerCase","Manipulator","setDataAttribute","element","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","Object","keys","dataset","filter","startsWith","pureKey","charAt","slice","getDataAttribute","getAttribute"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;EAEA,SAASA,aAAaA,CAACC,KAAK,EAAE;IAC5B,IAAIA,KAAK,KAAK,MAAM,EAAE;EACpB,IAAA,OAAO,IAAI;EACb,EAAA;IAEA,IAAIA,KAAK,KAAK,OAAO,EAAE;EACrB,IAAA,OAAO,KAAK;EACd,EAAA;IAEA,IAAIA,KAAK,KAAKC,MAAM,CAACD,KAAK,CAAC,CAACE,QAAQ,EAAE,EAAE;MACtC,OAAOD,MAAM,CAACD,KAAK,CAAC;EACtB,EAAA;EAEA,EAAA,IAAIA,KAAK,KAAK,EAAE,IAAIA,KAAK,KAAK,MAAM,EAAE;EACpC,IAAA,OAAO,IAAI;EACb,EAAA;EAEA,EAAA,IAAI,OAAOA,KAAK,KAAK,QAAQ,EAAE;EAC7B,IAAA,OAAOA,KAAK;EACd,EAAA;IAEA,IAAI;MACF,OAAOG,IAAI,CAACC,KAAK,CAACC,kBAAkB,CAACL,KAAK,CAAC,CAAC;EAC9C,EAAA,CAAC,CAAC,MAAM;EACN,IAAA,OAAOA,KAAK;EACd,EAAA;EACF;EAEA,SAASM,gBAAgBA,CAACC,GAAG,EAAE;EAC7B,EAAA,OAAOA,GAAG,CAACC,OAAO,CAAC,QAAQ,EAAEC,GAAG,IAAI,CAAA,CAAA,EAAIA,GAAG,CAACC,WAAW,EAAE,EAAE,CAAC;EAC9D;AAEA,QAAMC,WAAW,GAAG;EAClBC,EAAAA,gBAAgBA,CAACC,OAAO,EAAEN,GAAG,EAAEP,KAAK,EAAE;MACpCa,OAAO,CAACC,YAAY,CAAC,CAAA,QAAA,EAAWR,gBAAgB,CAACC,GAAG,CAAC,CAAA,CAAE,EAAEP,KAAK,CAAC;IACjE,CAAC;EAEDe,EAAAA,mBAAmBA,CAACF,OAAO,EAAEN,GAAG,EAAE;MAChCM,OAAO,CAACG,eAAe,CAAC,CAAA,QAAA,EAAWV,gBAAgB,CAACC,GAAG,CAAC,CAAA,CAAE,CAAC;IAC7D,CAAC;IAEDU,iBAAiBA,CAACJ,OAAO,EAAE;MACzB,IAAI,CAACA,OAAO,EAAE;EACZ,MAAA,OAAO,EAAE;EACX,IAAA;MAEA,MAAMK,UAAU,GAAG,EAAE;EACrB,IAAA,MAAMC,MAAM,GAAGC,MAAM,CAACC,IAAI,CAACR,OAAO,CAACS,OAAO,CAAC,CAACC,MAAM,CAAChB,GAAG,IAAIA,GAAG,CAACiB,UAAU,CAAC,IAAI,CAAC,IAAI,CAACjB,GAAG,CAACiB,UAAU,CAAC,UAAU,CAAC,CAAC;EAE9G,IAAA,KAAK,MAAMjB,GAAG,IAAIY,MAAM,EAAE;QACxB,IAAIM,OAAO,GAAGlB,GAAG,CAACC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;EACpCiB,MAAAA,OAAO,GAAGA,OAAO,CAACC,MAAM,CAAC,CAAC,CAAC,CAAChB,WAAW,EAAE,GAAGe,OAAO,CAACE,KAAK,CAAC,CAAC,CAAC;EAC5DT,MAAAA,UAAU,CAACO,OAAO,CAAC,GAAG1B,aAAa,CAACc,OAAO,CAACS,OAAO,CAACf,GAAG,CAAC,CAAC;EAC3D,IAAA;EAEA,IAAA,OAAOW,UAAU;IACnB,CAAC;EAEDU,EAAAA,gBAAgBA,CAACf,OAAO,EAAEN,GAAG,EAAE;EAC7B,IAAA,OAAOR,aAAa,CAACc,OAAO,CAACgB,YAAY,CAAC,CAAA,QAAA,EAAWvB,gBAAgB,CAACC,GAAG,CAAC,CAAA,CAAE,CAAC,CAAC;EAChF,EAAA;EACF;;;;;;;;"}
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap selector-engine.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+603 -140
View File
@@ -1,32 +1,13 @@
/*!
* Bootstrap dropdown.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@popperjs/core'), require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./util/index.js')) :
typeof define === 'function' && define.amd ? define(['@popperjs/core', './base-component', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/index'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Dropdown = factory(global["@popperjs/core"], global.BaseComponent, global.EventHandler, global.Manipulator, global.SelectorEngine, global.Index));
})(this, (function (Popper, BaseComponent, EventHandler, Manipulator, SelectorEngine, index_js) { 'use strict';
function _interopNamespaceDefault(e) {
const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });
if (e) {
for (const k in e) {
if (k !== 'default') {
const d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: () => e[k]
});
}
}
}
n.default = e;
return Object.freeze(n);
}
const Popper__namespace = /*#__PURE__*/_interopNamespaceDefault(Popper);
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@floating-ui/dom'), require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./util/index.js'), require('./util/floating-ui.js')) :
typeof define === 'function' && define.amd ? define(['@floating-ui/dom', './base-component', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/index', './util/floating-ui'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Dropdown = factory(global["@floating-ui/dom"], global.BaseComponent, global.EventHandler, global.Manipulator, global.SelectorEngine, global.Index, global.FloatingUi));
})(this, (function (dom, BaseComponent, EventHandler, Manipulator, SelectorEngine, index_js, floatingUi_js) { 'use strict';
/**
* --------------------------------------------------------------------------
@@ -48,8 +29,16 @@
const TAB_KEY = 'Tab';
const ARROW_UP_KEY = 'ArrowUp';
const ARROW_DOWN_KEY = 'ArrowDown';
const RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button
const ARROW_LEFT_KEY = 'ArrowLeft';
const ARROW_RIGHT_KEY = 'ArrowRight';
const HOME_KEY = 'Home';
const END_KEY = 'End';
const ENTER_KEY = 'Enter';
const SPACE_KEY = ' ';
const RIGHT_MOUSE_BUTTON = 2;
// Hover intent delay (ms) - grace period before closing submenu
const SUBMENU_CLOSE_DELAY = 100;
const EVENT_HIDE = `hide${EVENT_KEY}`;
const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
const EVENT_SHOW = `show${EVENT_KEY}`;
@@ -58,40 +47,54 @@
const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`;
const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`;
const CLASS_NAME_SHOW = 'show';
const CLASS_NAME_DROPUP = 'dropup';
const CLASS_NAME_DROPEND = 'dropend';
const CLASS_NAME_DROPSTART = 'dropstart';
const CLASS_NAME_DROPUP_CENTER = 'dropup-center';
const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center';
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)';
const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`;
const SELECTOR_MENU = '.dropdown-menu';
const SELECTOR_NAVBAR = '.navbar';
const SELECTOR_SUBMENU = '.dropdown-submenu';
const SELECTOR_SUBMENU_TOGGLE = '.dropdown-submenu > .dropdown-item';
const SELECTOR_NAVBAR_NAV = '.navbar-nav';
const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)';
const PLACEMENT_TOP = index_js.isRTL() ? 'top-end' : 'top-start';
const PLACEMENT_TOPEND = index_js.isRTL() ? 'top-start' : 'top-end';
const PLACEMENT_BOTTOM = index_js.isRTL() ? 'bottom-end' : 'bottom-start';
const PLACEMENT_BOTTOMEND = index_js.isRTL() ? 'bottom-start' : 'bottom-end';
const PLACEMENT_RIGHT = index_js.isRTL() ? 'left-start' : 'right-start';
const PLACEMENT_LEFT = index_js.isRTL() ? 'right-start' : 'left-start';
const PLACEMENT_TOPCENTER = 'top';
const PLACEMENT_BOTTOMCENTER = 'bottom';
const SELECTOR_VISIBLE_ITEMS = '.dropdown-item:not(.disabled):not(:disabled)';
// Default logical placement (uses start/end which get resolved to left/right based on RTL)
const DEFAULT_PLACEMENT = 'bottom-start';
const SUBMENU_PLACEMENT = 'end-start';
// Resolve logical placement (start/end) to physical (left/right) based on RTL
const resolveLogicalPlacement = placement => {
if (index_js.isRTL()) {
// RTL: start → right, end → left
return placement.replace(/^start(?=-|$)/, 'right').replace(/^end(?=-|$)/, 'left');
}
// LTR: start → left, end → right
return placement.replace(/^start(?=-|$)/, 'left').replace(/^end(?=-|$)/, 'right');
};
// Helper for barycentric coordinate calculation (point in triangle check)
const triangleSign = (p1, p2, p3) => (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
const Default = {
autoClose: true,
boundary: 'clippingParents',
display: 'dynamic',
offset: [0, 2],
popperConfig: null,
reference: 'toggle'
floatingConfig: null,
placement: DEFAULT_PLACEMENT,
reference: 'toggle',
// Submenu options
submenuTrigger: 'both',
// 'click', 'hover', or 'both'
submenuDelay: SUBMENU_CLOSE_DELAY
};
const DefaultType = {
autoClose: '(boolean|string)',
boundary: '(string|element)',
display: 'string',
offset: '(array|string|function)',
popperConfig: '(null|object|function)',
reference: '(string|element|object)'
floatingConfig: '(null|object|function)',
placement: 'string',
reference: '(string|element|object)',
submenuTrigger: 'string',
submenuDelay: 'number'
};
/**
@@ -100,12 +103,27 @@
class Dropdown extends BaseComponent {
constructor(element, config) {
if (typeof dom.computePosition === 'undefined') {
throw new TypeError('Bootstrap\'s dropdowns require Floating UI (https://floating-ui.com)');
}
super(element, config);
this._popper = null;
this._floatingCleanup = null;
this._mediaQueryListeners = [];
this._responsivePlacements = null;
this._parent = this._element.parentNode; // dropdown wrapper
this._isSubmenu = this._parent.classList.contains('dropdown-submenu');
this._openSubmenus = new Map(); // Map of submenu element -> cleanup function
this._submenuCloseTimeouts = new Map(); // Map of submenu element -> timeout ID
this._hoverIntentData = null; // For safe triangle calculation
// TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/
this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent);
this._inNavbar = this._detectNavbar();
// Parse responsive placements on init
this._parseResponsivePlacements();
// Set up submenu event listeners
this._setupSubmenuListeners();
}
// Getters
@@ -134,7 +152,7 @@
if (showEvent.defaultPrevented) {
return;
}
this._createPopper();
this._createFloating();
// If this is a touch-enabled device we add extra
// empty mouseover listeners to the body's immediate children;
@@ -146,9 +164,10 @@
}
}
this._element.focus();
this._element.setAttribute('aria-expanded', true);
this._element.setAttribute('aria-expanded', 'true');
this._menu.classList.add(CLASS_NAME_SHOW);
this._element.classList.add(CLASS_NAME_SHOW);
this._parent.classList.add(CLASS_NAME_SHOW);
EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget);
}
hide() {
@@ -161,15 +180,15 @@
this._completeHide(relatedTarget);
}
dispose() {
if (this._popper) {
this._popper.destroy();
}
this._disposeFloating();
this._disposeMediaQueryListeners();
this._closeAllSubmenus();
this._clearAllSubmenuTimeouts();
super.dispose();
}
update() {
this._inNavbar = this._detectNavbar();
if (this._popper) {
this._popper.update();
if (this._floatingCleanup) {
this._updateFloatingPosition();
}
}
@@ -180,6 +199,9 @@
return;
}
// Close all open submenus first
this._closeAllSubmenus();
// If this is a touch-enabled device we remove the extra
// empty mouseover listeners we added for iOS support
if ('ontouchstart' in document.documentElement) {
@@ -187,26 +209,27 @@
EventHandler.off(element, 'mouseover', index_js.noop);
}
}
if (this._popper) {
this._popper.destroy();
}
this._disposeFloating();
this._menu.classList.remove(CLASS_NAME_SHOW);
this._element.classList.remove(CLASS_NAME_SHOW);
this._parent.classList.remove(CLASS_NAME_SHOW);
this._element.setAttribute('aria-expanded', 'false');
Manipulator.removeDataAttribute(this._menu, 'popper');
Manipulator.removeDataAttribute(this._menu, 'placement');
Manipulator.removeDataAttribute(this._menu, 'display');
EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget);
}
_getConfig(config) {
config = super._getConfig(config);
if (typeof config.reference === 'object' && !index_js.isElement(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') {
// Popper virtual elements require a getBoundingClientRect method
// Floating UI virtual elements require a getBoundingClientRect method
throw new TypeError(`${NAME.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);
}
return config;
}
_createPopper() {
if (typeof Popper__namespace === 'undefined') {
throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org/docs/v2/)');
_createFloating() {
if (this._config.display === 'static') {
Manipulator.setDataAttribute(this._menu, 'display', 'static');
return;
}
let referenceElement = this._element;
if (this._config.reference === 'parent') {
@@ -216,83 +239,430 @@
} else if (typeof this._config.reference === 'object') {
referenceElement = this._config.reference;
}
const popperConfig = this._getPopperConfig();
this._popper = Popper__namespace.createPopper(referenceElement, this._menu, popperConfig);
// Initial position update
this._updateFloatingPosition(referenceElement);
// Set up auto-update for scroll/resize
this._floatingCleanup = dom.autoUpdate(referenceElement, this._menu, () => this._updateFloatingPosition(referenceElement));
}
async _updateFloatingPosition(referenceElement = null) {
if (!this._menu) {
return;
}
if (!referenceElement) {
if (this._config.reference === 'parent') {
referenceElement = this._parent;
} else if (index_js.isElement(this._config.reference)) {
referenceElement = index_js.getElement(this._config.reference);
} else if (typeof this._config.reference === 'object') {
referenceElement = this._config.reference;
} else {
referenceElement = this._element;
}
}
const placement = this._getPlacement();
const middleware = this._getFloatingMiddleware();
const floatingConfig = this._getFloatingConfig(placement, middleware);
await this._applyFloatingPosition(referenceElement, this._menu, floatingConfig.placement, floatingConfig.middleware);
}
_isShown() {
return this._menu.classList.contains(CLASS_NAME_SHOW);
}
_getPlacement() {
const parentDropdown = this._parent;
if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {
return PLACEMENT_RIGHT;
}
if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {
return PLACEMENT_LEFT;
}
if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {
return PLACEMENT_TOPCENTER;
}
if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {
return PLACEMENT_BOTTOMCENTER;
}
// If we have responsive placements, find the appropriate one for current viewport
const placement = this._responsivePlacements ? floatingUi_js.getResponsivePlacement(this._responsivePlacements, DEFAULT_PLACEMENT) : this._config.placement;
// We need to trim the value because custom properties can also include spaces
const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end';
if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {
return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP;
}
return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM;
// Resolve logical placements (start/end) to physical (left/right) based on RTL
return resolveLogicalPlacement(placement);
}
_detectNavbar() {
return this._element.closest(SELECTOR_NAVBAR) !== null;
_parseResponsivePlacements() {
this._responsivePlacements = floatingUi_js.parseResponsivePlacement(this._config.placement, DEFAULT_PLACEMENT);
if (this._responsivePlacements) {
this._setupMediaQueryListeners();
}
}
_setupMediaQueryListeners() {
this._disposeMediaQueryListeners();
this._mediaQueryListeners = floatingUi_js.createBreakpointListeners(() => {
if (this._isShown()) {
this._updateFloatingPosition();
}
});
}
_disposeMediaQueryListeners() {
floatingUi_js.disposeBreakpointListeners(this._mediaQueryListeners);
this._mediaQueryListeners = [];
}
_getOffset() {
const {
offset
offset: offsetConfig
} = this._config;
if (typeof offset === 'string') {
return offset.split(',').map(value => Number.parseInt(value, 10));
if (typeof offsetConfig === 'string') {
return offsetConfig.split(',').map(value => Number.parseInt(value, 10));
}
if (typeof offset === 'function') {
return popperData => offset(popperData, this._element);
if (typeof offsetConfig === 'function') {
// Floating UI passes different args, adapt the interface for offset function callbacks
return ({
placement,
rects
}) => {
const result = offsetConfig({
placement,
reference: rects.reference,
floating: rects.floating
}, this._element);
return result;
};
}
return offset;
return offsetConfig;
}
_getPopperConfig() {
const defaultBsPopperConfig = {
placement: this._getPlacement(),
modifiers: [{
name: 'preventOverflow',
options: {
boundary: this._config.boundary
}
}, {
name: 'offset',
options: {
offset: this._getOffset()
}
}]
_getFloatingMiddleware() {
const offsetValue = this._getOffset();
const middleware = [
// Offset middleware - handles distance from reference
dom.offset(typeof offsetValue === 'function' ? offsetValue : {
mainAxis: offsetValue[1] || 0,
crossAxis: offsetValue[0] || 0
}),
// Flip middleware - handles fallback placements
dom.flip({
fallbackPlacements: this._getFallbackPlacements()
}),
// Shift middleware - prevents overflow
dom.shift({
boundary: this._config.boundary === 'clippingParents' ? 'clippingAncestors' : this._config.boundary
})];
return middleware;
}
_getFallbackPlacements() {
// Get appropriate fallback placements based on current placement
// Fallbacks should preserve alignment (start/end) when possible
const placement = this._getPlacement();
// Handle all possible Floating UI placements
const fallbackMap = {
bottom: ['top', 'bottom-start', 'bottom-end', 'top-start', 'top-end'],
'bottom-start': ['top-start', 'bottom-end', 'top-end'],
'bottom-end': ['top-end', 'bottom-start', 'top-start'],
top: ['bottom', 'top-start', 'top-end', 'bottom-start', 'bottom-end'],
'top-start': ['bottom-start', 'top-end', 'bottom-end'],
'top-end': ['bottom-end', 'top-start', 'bottom-start'],
right: ['left', 'right-start', 'right-end', 'left-start', 'left-end'],
'right-start': ['left-start', 'right-end', 'left-end', 'top-start', 'bottom-start'],
'right-end': ['left-end', 'right-start', 'left-start', 'top-end', 'bottom-end'],
left: ['right', 'left-start', 'left-end', 'right-start', 'right-end'],
'left-start': ['right-start', 'left-end', 'right-end', 'top-start', 'bottom-start'],
'left-end': ['right-end', 'left-start', 'right-start', 'top-end', 'bottom-end']
};
return fallbackMap[placement] || ['top', 'bottom', 'right', 'left'];
}
_getFloatingConfig(placement, middleware) {
const defaultConfig = {
placement,
middleware
};
return {
...defaultConfig,
...index_js.execute(this._config.floatingConfig, [undefined, defaultConfig])
};
}
_disposeFloating() {
if (this._floatingCleanup) {
this._floatingCleanup();
this._floatingCleanup = null;
}
}
// Shared helper for positioning any floating element
async _applyFloatingPosition(reference, floating, placement, middleware) {
if (!floating.isConnected) {
return null;
}
const {
x,
y,
placement: finalPlacement
} = await dom.computePosition(reference, floating, {
placement,
middleware
});
if (!floating.isConnected) {
return null;
}
Object.assign(floating.style, {
position: 'absolute',
left: `${x}px`,
top: `${y}px`,
margin: '0'
});
Manipulator.setDataAttribute(floating, 'placement', finalPlacement);
return finalPlacement;
}
// -------------------------------------------------------------------------
// Submenu handling
// -------------------------------------------------------------------------
_setupSubmenuListeners() {
// Set up hover listeners for submenu triggers
if (this._config.submenuTrigger === 'hover' || this._config.submenuTrigger === 'both') {
EventHandler.on(this._menu, 'mouseenter', SELECTOR_SUBMENU_TOGGLE, event => {
this._onSubmenuTriggerEnter(event);
});
EventHandler.on(this._menu, 'mouseleave', SELECTOR_SUBMENU, event => {
this._onSubmenuLeave(event);
});
// Track mouse movement for safe triangle calculation
EventHandler.on(this._menu, 'mousemove', event => {
this._trackMousePosition(event);
});
}
// Set up click listener for submenu triggers
if (this._config.submenuTrigger === 'click' || this._config.submenuTrigger === 'both') {
EventHandler.on(this._menu, 'click', SELECTOR_SUBMENU_TOGGLE, event => {
this._onSubmenuTriggerClick(event);
});
}
}
_onSubmenuTriggerEnter(event) {
const trigger = event.target.closest(SELECTOR_SUBMENU_TOGGLE);
if (!trigger) {
return;
}
const submenuWrapper = trigger.closest(SELECTOR_SUBMENU);
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
if (!submenu) {
return;
}
// Cancel any pending close timeout for this submenu
this._cancelSubmenuCloseTimeout(submenu);
// Close other open submenus at the same level
this._closeSiblingSubmenus(submenuWrapper);
// Open this submenu
this._openSubmenu(trigger, submenu, submenuWrapper);
}
_onSubmenuLeave(event) {
const submenuWrapper = event.target.closest(SELECTOR_SUBMENU);
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
if (!submenu || !this._openSubmenus.has(submenu)) {
return;
}
// Check if we're moving toward the submenu (safe triangle)
if (this._isMovingTowardSubmenu(event, submenu)) {
return;
}
// Schedule submenu close with delay
this._scheduleSubmenuClose(submenu, submenuWrapper);
}
_onSubmenuTriggerClick(event) {
const trigger = event.target.closest(SELECTOR_SUBMENU_TOGGLE);
if (!trigger) {
return;
}
event.preventDefault();
event.stopPropagation();
const submenuWrapper = trigger.closest(SELECTOR_SUBMENU);
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
if (!submenu) {
return;
}
// Toggle submenu
if (this._openSubmenus.has(submenu)) {
this._closeSubmenu(submenu, submenuWrapper);
} else {
this._closeSiblingSubmenus(submenuWrapper);
this._openSubmenu(trigger, submenu, submenuWrapper);
}
}
_openSubmenu(trigger, submenu, submenuWrapper) {
if (this._openSubmenus.has(submenu)) {
return;
}
// Set ARIA attributes
trigger.setAttribute('aria-expanded', 'true');
trigger.setAttribute('aria-haspopup', 'true');
// Position and show submenu
submenu.classList.add(CLASS_NAME_SHOW);
submenuWrapper.classList.add(CLASS_NAME_SHOW);
// Set up Floating UI positioning for submenu
const cleanup = this._createSubmenuFloating(trigger, submenu, submenuWrapper);
this._openSubmenus.set(submenu, cleanup);
// Set up mouseenter on submenu to cancel close timeout
EventHandler.on(submenu, 'mouseenter', () => {
this._cancelSubmenuCloseTimeout(submenu);
});
}
_closeSubmenu(submenu, submenuWrapper) {
if (!this._openSubmenus.has(submenu)) {
return;
}
// Close any nested submenus first
const nestedSubmenus = SelectorEngine.find(`${SELECTOR_SUBMENU} ${SELECTOR_MENU}.${CLASS_NAME_SHOW}`, submenu);
for (const nested of nestedSubmenus) {
const nestedWrapper = nested.closest(SELECTOR_SUBMENU);
this._closeSubmenu(nested, nestedWrapper);
}
// Get the trigger
const trigger = SelectorEngine.findOne(SELECTOR_SUBMENU_TOGGLE, submenuWrapper);
// Clean up Floating UI
const cleanup = this._openSubmenus.get(submenu);
if (cleanup) {
cleanup();
}
this._openSubmenus.delete(submenu);
// Remove event listeners
EventHandler.off(submenu, 'mouseenter');
// Update ARIA and visibility
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
submenu.classList.remove(CLASS_NAME_SHOW);
submenuWrapper.classList.remove(CLASS_NAME_SHOW);
// Clear inline styles
submenu.style.position = '';
submenu.style.left = '';
submenu.style.top = '';
submenu.style.margin = '';
}
_closeAllSubmenus() {
for (const [submenu] of this._openSubmenus) {
const submenuWrapper = submenu.closest(SELECTOR_SUBMENU);
this._closeSubmenu(submenu, submenuWrapper);
}
}
_closeSiblingSubmenus(currentSubmenuWrapper) {
// Find all sibling submenu wrappers and close their menus
const parent = currentSubmenuWrapper.parentNode;
const siblingSubmenus = SelectorEngine.find(`${SELECTOR_SUBMENU} > ${SELECTOR_MENU}.${CLASS_NAME_SHOW}`, parent);
for (const siblingMenu of siblingSubmenus) {
const siblingWrapper = siblingMenu.closest(SELECTOR_SUBMENU);
if (siblingWrapper !== currentSubmenuWrapper) {
this._closeSubmenu(siblingMenu, siblingWrapper);
}
}
}
_createSubmenuFloating(trigger, submenu, submenuWrapper) {
const referenceElement = submenuWrapper;
const placement = resolveLogicalPlacement(SUBMENU_PLACEMENT);
const middleware = [dom.offset({
mainAxis: 0,
crossAxis: -4
}), dom.flip({
fallbackPlacements: [resolveLogicalPlacement('start-start'), resolveLogicalPlacement('end-end'), resolveLogicalPlacement('start-end')]
}), dom.shift({
padding: 8
})];
const updatePosition = () => this._applyFloatingPosition(referenceElement, submenu, placement, middleware);
updatePosition();
return dom.autoUpdate(referenceElement, submenu, updatePosition);
}
_scheduleSubmenuClose(submenu, submenuWrapper) {
this._cancelSubmenuCloseTimeout(submenu);
const timeoutId = setTimeout(() => {
this._closeSubmenu(submenu, submenuWrapper);
this._submenuCloseTimeouts.delete(submenu);
}, this._config.submenuDelay);
this._submenuCloseTimeouts.set(submenu, timeoutId);
}
_cancelSubmenuCloseTimeout(submenu) {
const timeoutId = this._submenuCloseTimeouts.get(submenu);
if (timeoutId) {
clearTimeout(timeoutId);
this._submenuCloseTimeouts.delete(submenu);
}
}
_clearAllSubmenuTimeouts() {
for (const timeoutId of this._submenuCloseTimeouts.values()) {
clearTimeout(timeoutId);
}
this._submenuCloseTimeouts.clear();
}
// -------------------------------------------------------------------------
// Hover intent / Safe triangle
// -------------------------------------------------------------------------
_trackMousePosition(event) {
this._hoverIntentData = {
x: event.clientX,
y: event.clientY,
timestamp: Date.now()
};
}
_isMovingTowardSubmenu(event, submenu) {
if (!this._hoverIntentData) {
return false;
}
const submenuRect = submenu.getBoundingClientRect();
const currentPos = {
x: event.clientX,
y: event.clientY
};
const lastPos = {
x: this._hoverIntentData.x,
y: this._hoverIntentData.y
};
// Disable Popper if we have a static display or Dropdown is in Navbar
if (this._inNavbar || this._config.display === 'static') {
Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // TODO: v6 remove
defaultBsPopperConfig.modifiers = [{
name: 'applyStyles',
enabled: false
}];
}
return {
...defaultBsPopperConfig,
...index_js.execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])
// Create a triangle from current position to submenu edges
// The triangle represents the "safe zone" for diagonal movement
const isRtl = index_js.isRTL();
// Determine which edge of the submenu to target based on direction
const targetX = isRtl ? submenuRect.right : submenuRect.left;
const topCorner = {
x: targetX,
y: submenuRect.top
};
const bottomCorner = {
x: targetX,
y: submenuRect.bottom
};
// Check if cursor is moving toward the submenu
// by checking if the current position is within the safe triangle
return this._pointInTriangle(currentPos, lastPos, topCorner, bottomCorner);
}
_pointInTriangle(point, v1, v2, v3) {
// Barycentric coordinate method to check if point is inside triangle
const d1 = triangleSign(point, v1, v2);
const d2 = triangleSign(point, v2, v3);
const d3 = triangleSign(point, v3, v1);
const hasNeg = d1 < 0 || d2 < 0 || d3 < 0;
const hasPos = d1 > 0 || d2 > 0 || d3 > 0;
return !(hasNeg && hasPos);
}
// -------------------------------------------------------------------------
// Keyboard navigation
// -------------------------------------------------------------------------
_selectMenuItem({
key,
target
}) {
const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => index_js.isVisible(element));
// Get items only from the current menu level (not nested submenus)
// If target is inside a menu, use that menu; otherwise use the main menu
const currentMenu = target.closest(SELECTOR_MENU) || this._menu;
const items = SelectorEngine.find(`:scope > li > ${SELECTOR_VISIBLE_ITEMS}, :scope > ${SELECTOR_VISIBLE_ITEMS}`, currentMenu).filter(element => index_js.isVisible(element));
if (!items.length) {
return;
}
@@ -301,19 +671,88 @@
// 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();
// Static
static jQueryInterface(config) {
return this.each(function () {
const data = Dropdown.getOrCreateInstance(this, config);
if (typeof config !== 'string') {
return;
// 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();
}
});
}
if (typeof data[config] === 'undefined') {
throw new TypeError(`No method named "${config}"`);
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();
}
});
}
data[config]();
});
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) {
@@ -345,32 +784,62 @@
}
}
static dataApiKeydownHandler(event) {
// If not an UP | DOWN | ESCAPE key => not a dropdown command
// If input/textarea && if key is other than ESCAPE => not a dropdown command
// If not a relevant key => not a dropdown command
const isInput = /input|textarea/i.test(event.target.tagName);
const isEscapeEvent = event.key === ESCAPE_KEY;
const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key);
if (!isUpOrDownEvent && !isEscapeEvent) {
const isLeftOrRightEvent = [ARROW_LEFT_KEY, ARROW_RIGHT_KEY].includes(event.key);
const isHomeOrEndEvent = [HOME_KEY, END_KEY].includes(event.key);
const isEnterOrSpaceEvent = [ENTER_KEY, SPACE_KEY].includes(event.key);
// Allow Enter/Space only on submenu triggers
const isSubmenuTrigger = event.target.matches(SELECTOR_SUBMENU_TOGGLE);
if (!isUpOrDownEvent && !isEscapeEvent && !isLeftOrRightEvent && !isHomeOrEndEvent && !(isEnterOrSpaceEvent && isSubmenuTrigger)) {
return;
}
if (isInput && !isEscapeEvent) {
return;
}
event.preventDefault();
// TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/
const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode);
if (!getToggleButton) {
return;
}
const instance = Dropdown.getOrCreateInstance(getToggleButton);
// Handle submenu navigation first
if ((isLeftOrRightEvent || isHomeOrEndEvent || isEnterOrSpaceEvent && isSubmenuTrigger) && instance._handleSubmenuKeydown(event)) {
return;
}
// Handle Up/Down navigation
if (isUpOrDownEvent) {
event.preventDefault();
event.stopPropagation();
instance.show();
instance._selectMenuItem(event);
return;
}
if (instance._isShown()) {
// else is escape and we check if it is shown
// Handle Escape
if (isEscapeEvent && instance._isShown()) {
event.preventDefault();
event.stopPropagation();
// If in a submenu, close just that submenu
const currentMenu = event.target.closest(SELECTOR_MENU);
const parentSubmenuWrapper = currentMenu?.closest(SELECTOR_SUBMENU);
if (parentSubmenuWrapper && instance._openSubmenus.size > 0) {
const parentTrigger = SelectorEngine.findOne(SELECTOR_SUBMENU_TOGGLE, parentSubmenuWrapper);
instance._closeSubmenu(currentMenu, parentSubmenuWrapper);
if (parentTrigger) {
parentTrigger.focus();
}
return;
}
// Otherwise close the whole dropdown
instance.hide();
getToggleButton.focus();
}
@@ -390,12 +859,6 @@
Dropdown.getOrCreateInstance(this).toggle();
});
/**
* jQuery
*/
index_js.defineJQueryPlugin(Dropdown);
return Dropdown;
}));
+1 -1
View File
File diff suppressed because one or more lines are too long
-320
View File
@@ -1,320 +0,0 @@
/*!
* Bootstrap modal.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/selector-engine.js'), require('./util/backdrop.js'), require('./util/component-functions.js'), require('./util/focustrap.js'), require('./util/index.js'), require('./util/scrollbar.js')) :
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine', './util/backdrop', './util/component-functions', './util/focustrap', './util/index', './util/scrollbar'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Modal = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine, global.Backdrop, global.ComponentFunctions, global.Focustrap, global.Index, global.Scrollbar));
})(this, (function (BaseComponent, EventHandler, SelectorEngine, Backdrop, componentFunctions_js, FocusTrap, index_js, ScrollBarHelper) { 'use strict';
/**
* --------------------------------------------------------------------------
* Bootstrap modal.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
const NAME = 'modal';
const DATA_KEY = 'bs.modal';
const EVENT_KEY = `.${DATA_KEY}`;
const DATA_API_KEY = '.data-api';
const ESCAPE_KEY = 'Escape';
const EVENT_HIDE = `hide${EVENT_KEY}`;
const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`;
const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
const EVENT_SHOW = `show${EVENT_KEY}`;
const EVENT_SHOWN = `shown${EVENT_KEY}`;
const EVENT_RESIZE = `resize${EVENT_KEY}`;
const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`;
const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`;
const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`;
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
const CLASS_NAME_OPEN = 'modal-open';
const CLASS_NAME_FADE = 'fade';
const CLASS_NAME_SHOW = 'show';
const CLASS_NAME_STATIC = 'modal-static';
const OPEN_SELECTOR = '.modal.show';
const SELECTOR_DIALOG = '.modal-dialog';
const SELECTOR_MODAL_BODY = '.modal-body';
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="modal"]';
const Default = {
backdrop: true,
focus: true,
keyboard: true
};
const DefaultType = {
backdrop: '(boolean|string)',
focus: 'boolean',
keyboard: 'boolean'
};
/**
* Class definition
*/
class Modal extends BaseComponent {
constructor(element, config) {
super(element, config);
this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element);
this._backdrop = this._initializeBackDrop();
this._focustrap = this._initializeFocusTrap();
this._isShown = false;
this._isTransitioning = false;
this._scrollBar = new ScrollBarHelper();
this._addEventListeners();
}
// Getters
static get Default() {
return Default;
}
static get DefaultType() {
return DefaultType;
}
static get NAME() {
return NAME;
}
// Public
toggle(relatedTarget) {
return this._isShown ? this.hide() : this.show(relatedTarget);
}
show(relatedTarget) {
if (this._isShown || this._isTransitioning) {
return;
}
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {
relatedTarget
});
if (showEvent.defaultPrevented) {
return;
}
this._isShown = true;
this._isTransitioning = true;
this._scrollBar.hide();
document.body.classList.add(CLASS_NAME_OPEN);
this._adjustDialog();
this._backdrop.show(() => this._showElement(relatedTarget));
}
hide() {
if (!this._isShown || this._isTransitioning) {
return;
}
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);
if (hideEvent.defaultPrevented) {
return;
}
this._isShown = false;
this._isTransitioning = true;
this._focustrap.deactivate();
this._element.classList.remove(CLASS_NAME_SHOW);
this._queueCallback(() => this._hideModal(), this._element, this._isAnimated());
}
dispose() {
EventHandler.off(window, EVENT_KEY);
EventHandler.off(this._dialog, EVENT_KEY);
this._backdrop.dispose();
this._focustrap.deactivate();
super.dispose();
}
handleUpdate() {
this._adjustDialog();
}
// Private
_initializeBackDrop() {
return new Backdrop({
isVisible: Boolean(this._config.backdrop),
// 'static' option will be translated to true, and booleans will keep their value,
isAnimated: this._isAnimated()
});
}
_initializeFocusTrap() {
return new FocusTrap({
trapElement: this._element
});
}
_showElement(relatedTarget) {
// try to append dynamic modal
if (!document.body.contains(this._element)) {
document.body.append(this._element);
}
this._element.style.display = 'block';
this._element.removeAttribute('aria-hidden');
this._element.setAttribute('aria-modal', true);
this._element.setAttribute('role', 'dialog');
this._element.scrollTop = 0;
const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog);
if (modalBody) {
modalBody.scrollTop = 0;
}
index_js.reflow(this._element);
this._element.classList.add(CLASS_NAME_SHOW);
const transitionComplete = () => {
if (this._config.focus) {
this._focustrap.activate();
}
this._isTransitioning = false;
EventHandler.trigger(this._element, EVENT_SHOWN, {
relatedTarget
});
};
this._queueCallback(transitionComplete, this._dialog, this._isAnimated());
}
_addEventListeners() {
EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {
if (event.key !== ESCAPE_KEY) {
return;
}
if (this._config.keyboard) {
this.hide();
return;
}
this._triggerBackdropTransition();
});
EventHandler.on(window, EVENT_RESIZE, () => {
if (this._isShown && !this._isTransitioning) {
this._adjustDialog();
}
});
EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {
// a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks
EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {
if (this._element !== event.target || this._element !== event2.target) {
return;
}
if (this._config.backdrop === 'static') {
this._triggerBackdropTransition();
return;
}
if (this._config.backdrop) {
this.hide();
}
});
});
}
_hideModal() {
this._element.style.display = 'none';
this._element.setAttribute('aria-hidden', true);
this._element.removeAttribute('aria-modal');
this._element.removeAttribute('role');
this._isTransitioning = false;
this._backdrop.hide(() => {
document.body.classList.remove(CLASS_NAME_OPEN);
this._resetAdjustments();
this._scrollBar.reset();
EventHandler.trigger(this._element, EVENT_HIDDEN);
});
}
_isAnimated() {
return this._element.classList.contains(CLASS_NAME_FADE);
}
_triggerBackdropTransition() {
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);
if (hideEvent.defaultPrevented) {
return;
}
const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;
const initialOverflowY = this._element.style.overflowY;
// return if the following background transition hasn't yet completed
if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {
return;
}
if (!isModalOverflowing) {
this._element.style.overflowY = 'hidden';
}
this._element.classList.add(CLASS_NAME_STATIC);
this._queueCallback(() => {
this._element.classList.remove(CLASS_NAME_STATIC);
this._queueCallback(() => {
this._element.style.overflowY = initialOverflowY;
}, this._dialog);
}, this._dialog);
this._element.focus();
}
/**
* The following methods are used to handle overflowing modals
*/
_adjustDialog() {
const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;
const scrollbarWidth = this._scrollBar.getWidth();
const isBodyOverflowing = scrollbarWidth > 0;
if (isBodyOverflowing && !isModalOverflowing) {
const property = index_js.isRTL() ? 'paddingLeft' : 'paddingRight';
this._element.style[property] = `${scrollbarWidth}px`;
}
if (!isBodyOverflowing && isModalOverflowing) {
const property = index_js.isRTL() ? 'paddingRight' : 'paddingLeft';
this._element.style[property] = `${scrollbarWidth}px`;
}
}
_resetAdjustments() {
this._element.style.paddingLeft = '';
this._element.style.paddingRight = '';
}
// Static
static jQueryInterface(config, relatedTarget) {
return this.each(function () {
const data = Modal.getOrCreateInstance(this, config);
if (typeof config !== 'string') {
return;
}
if (typeof data[config] === 'undefined') {
throw new TypeError(`No method named "${config}"`);
}
data[config](relatedTarget);
});
}
}
/**
* Data API implementation
*/
EventHandler.on(document, EVENT_CLICK_DATA_API, 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);
/**
* jQuery
*/
index_js.defineJQueryPlugin(Modal);
return Modal;
}));
//# sourceMappingURL=modal.js.map

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