Compare commits

..

13 Commits

Author SHA1 Message Date
Bardi Harborow c82919e897 Remove link to translation associated with supply chain attack
This commit removes a link from our documentation to a Mandarin Chinese (Simplified) translation of our documentation which is hosted on a sub-domain of the `bootcss.com` domain previously involved in the `polyfill.io` supply chain attack.
2025-12-27 11:19:55 -08:00
dependabot[bot] 4449c7465e Build(deps): Bump the github-actions group across 1 directory with 4 updates (#41961)
Bumps the github-actions group with 4 updates in the / directory: [github/codeql-action](https://github.com/github/codeql-action), [streetsidesoftware/cspell-action](https://github.com/streetsidesoftware/cspell-action), [actions-cool/issues-helper](https://github.com/actions-cool/issues-helper) and [actions/upload-artifact](https://github.com/actions/upload-artifact).


Updates `github/codeql-action` from 4.31.7 to 4.31.9
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/cf1bb45a277cb3c205638b2cd5c984db1c46a412...5d4e8d1aca955e8d8589aabd499c5cae939e33c7)

Updates `streetsidesoftware/cspell-action` from 8.0.0 to 8.1.1
- [Release notes](https://github.com/streetsidesoftware/cspell-action/releases)
- [Changelog](https://github.com/streetsidesoftware/cspell-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/streetsidesoftware/cspell-action/compare/3294df585d3d639e30f3bc019cb11940b9866e95...e5a858a18b7e0b56e0342b1dcad796308b7341a2)

Updates `actions-cool/issues-helper` from 3.7.3 to 3.7.4
- [Release notes](https://github.com/actions-cool/issues-helper/releases)
- [Changelog](https://github.com/actions-cool/issues-helper/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions-cool/issues-helper/compare/3809910bc12872edc9b8132f122069ac16cd16ee...d1d51fccf39469b5458203b1369060db0ff0c0db)

Updates `actions/upload-artifact` from 5.0.0 to 6.0.0
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/330a01c490aca151604b8cf639adc76d48f6c5d4...b7c566a772e6b6bfb58ed0dc250532a479d7789f)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.31.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: streetsidesoftware/cspell-action
  dependency-version: 8.1.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions-cool/issues-helper
  dependency-version: 3.7.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: actions/upload-artifact
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-20 15:02:08 +01:00
Julien Déramond 93920fe842 Remove volar-service-emmet version override
This error came from @astrojs/check version 0.9.5. More context at withastro/astro#14544.
2025-12-20 14:56:39 +01:00
Julien Déramond 02d8ed85e4 Bump dependencies
- @astrojs/check                ^0.9.5  →    ^0.9.6
- @astrojs/markdown-remark      ^6.3.9  →   ^6.3.10
- @astrojs/mdx                 ^4.3.12  →   ^4.3.13
- astro                        ^5.16.0  →   ^5.16.6
- autoprefixer                ^10.4.22  →  ^10.4.23
- find-unused-sass-variables    ^6.1.0  →    ^6.1.1
- jasmine                      ^5.12.0  →   ^5.13.0
- prettier                      ^3.6.2  →    ^3.7.4
- rollup                       ^4.53.3  →   ^4.54.0
- sass-true                    ^10.0.0  →   ^10.1.0
- stylelint                   ^16.26.0  →  ^16.26.1
- zod                          ^4.1.13  →    ^4.2.1
2025-12-20 14:50:51 +01:00
dependabot[bot] e170268b3c Build(deps): Bump the github-actions group across 1 directory with 4 updates (#41912)
Bumps the github-actions group with 4 updates in the / directory: [actions/checkout](https://github.com/actions/checkout), [actions/setup-node](https://github.com/actions/setup-node), [github/codeql-action](https://github.com/github/codeql-action) and [actions-cool/issues-helper](https://github.com/actions-cool/issues-helper).


Updates `actions/checkout` from 6.0.0 to 6.0.1
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/1af3b93b6815bc44a9784bd300feb67ff0d1eeb3...8e8c483db84b4bee98b60c0593521ed34d9990e8)

Updates `actions/setup-node` from 6.0.0 to 6.1.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/2028fbc5c25fe9cf00d9f06a71cc4710d4507903...395ad3262231945c25e8478fd5baf05154b1d79f)

Updates `github/codeql-action` from 4.31.5 to 4.31.7
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/fdbfb4d2750291e159f0156def62b853c2798ca2...cf1bb45a277cb3c205638b2cd5c984db1c46a412)

Updates `actions-cool/issues-helper` from 3.7.2 to 3.7.3
- [Release notes](https://github.com/actions-cool/issues-helper/releases)
- [Changelog](https://github.com/actions-cool/issues-helper/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions-cool/issues-helper/compare/9861779a695cf1898bd984c727f685f351cfc372...3809910bc12872edc9b8132f122069ac16cd16ee)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: actions/setup-node
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action
  dependency-version: 4.31.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: actions-cool/issues-helper
  dependency-version: 3.7.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 20:51:17 +01:00
Mark Otto e9cd061528 Add incident response plan documentation (#41905)
This document outlines the incident response plan for Bootstrap maintainers, detailing procedures for managing security and operational incidents, roles, responsibilities, and communication protocols.
2025-12-03 09:18:05 -08:00
Julien Déramond f29a71b1cf Build(deps-dev): Bump dependencies
- vnu-jar  25.11.20  →  25.11.25
- zod       ^4.1.12  →   ^4.1.13
2025-11-25 18:30:11 +01:00
dependabot[bot] 481bf7ece2 Build(deps): Bump the github-actions group with 2 updates (#41886)
Bumps the github-actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `actions/checkout` from 5.0.1 to 6.0.0
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/93cb6efe18208431cddfb8368fd83d5badbf9bfd...1af3b93b6815bc44a9784bd300feb67ff0d1eeb3)

Updates `github/codeql-action` from 4.31.3 to 4.31.5
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/014f16e7ab1402f30e7c3329d33797e7948572db...fdbfb4d2750291e159f0156def62b853c2798ca2)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: github/codeql-action
  dependency-version: 4.31.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-25 18:28:06 +01:00
Julien Déramond c577685708 Build(deps): Bump dependencies
- @astrojs/mdx   ^4.3.11  →   ^4.3.12
- astro          ^5.15.9  →   ^5.16.0
- rollup         ^4.53.2  →   ^4.53.3
- sass-true       ^9.0.0  →   ^10.0.0
- stylelint     ^16.25.0  →  ^16.26.0
- vnu-jar       25.11.17  →  25.11.20
2025-11-22 08:42:05 +01:00
Julien Déramond 61b0bab5c2 Build(deps-dev): Bump dependencies
- @astrojs/markdown-remark   ^6.3.8  →   ^6.3.9
- @astrojs/mdx              ^4.3.10  →  ^4.3.11
- astro                     ^5.15.8  →  ^5.15.9
2025-11-18 18:41:50 +01:00
dependabot[bot] 05fc0c7f37 Build(deps): Bump the github-actions group with 3 updates (#41878)
Bumps the github-actions group with 3 updates: [actions/checkout](https://github.com/actions/checkout), [github/codeql-action](https://github.com/github/codeql-action) and [coverallsapp/github-action](https://github.com/coverallsapp/github-action).


Updates `actions/checkout` from 5.0.0 to 5.0.1
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/08c6903cd8c0fde910a37f88322edcfb5dd907a8...93cb6efe18208431cddfb8368fd83d5badbf9bfd)

Updates `github/codeql-action` from 4.31.2 to 4.31.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/0499de31b99561a6d14a36a5f662c2a54f91beee...014f16e7ab1402f30e7c3329d33797e7948572db)

Updates `coverallsapp/github-action` from 2.3.6 to 2.3.7
- [Release notes](https://github.com/coverallsapp/github-action/releases)
- [Commits](https://github.com/coverallsapp/github-action/compare/648a8eb78e6d50909eff900e4ec85cab4524a45b...5cbfd81b66ca5d10c19b062c04de0199c215fb6e)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 5.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action
  dependency-version: 4.31.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: coverallsapp/github-action
  dependency-version: 2.3.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-18 18:39:20 +01:00
Julien Déramond c9e83a12b5 Build(deps-dev): Bump vnu-jar from 25.11.8 to 25.11.17 2025-11-17 18:27:33 +01:00
Julien Déramond 6f4d51f6ea Build(deps-dev): Bump dependencies
- astro   ^5.15.6  →  ^5.15.8
- globby  ^15.0.0  →  ^16.0.0
2025-11-15 18:02:18 +01:00
602 changed files with 72682 additions and 69010 deletions
+6 -4
View File
@@ -1,10 +1,12 @@
# https://github.com/browserslist/browserslist#readme
>= 0.5%
last 2 major versions
not dead
Chrome >= 120
Firefox >= 121
iOS >= 15.6
Safari >= 15.6
Chrome >= 60
Firefox >= 60
Firefox ESR
iOS >= 12
Safari >= 12
not Explorer <= 11
not kaios <= 2.5 # fix floating label issues in Firefox (see https://github.com/postcss/autoprefixer/issues/1533)
+14 -14
View File
@@ -2,59 +2,59 @@
"files": [
{
"path": "./dist/css/bootstrap-grid.css",
"maxSize": "9.5 kB"
"maxSize": "6.5 kB"
},
{
"path": "./dist/css/bootstrap-grid.min.css",
"maxSize": "8.5 kB"
"maxSize": "6.0 kB"
},
{
"path": "./dist/css/bootstrap-reboot.css",
"maxSize": "5.25 kB"
"maxSize": "3.5 kB"
},
{
"path": "./dist/css/bootstrap-reboot.min.css",
"maxSize": "4.25 kB"
"maxSize": "3.25 kB"
},
{
"path": "./dist/css/bootstrap-utilities.css",
"maxSize": "14.25 kB"
"maxSize": "11.75 kB"
},
{
"path": "./dist/css/bootstrap-utilities.min.css",
"maxSize": "12.5 kB"
"maxSize": "10.75 kB"
},
{
"path": "./dist/css/bootstrap.css",
"maxSize": "37.75 kB"
"maxSize": "32.5 kB"
},
{
"path": "./dist/css/bootstrap.min.css",
"maxSize": "34.0 kB"
"maxSize": "30.25 kB"
},
{
"path": "./dist/js/bootstrap.bundle.js",
"maxSize": "67.75 kB"
"maxSize": "43.0 kB"
},
{
"path": "./dist/js/bootstrap.bundle.min.js",
"maxSize": "41.0 kB"
"maxSize": "23.5 kB"
},
{
"path": "./dist/js/bootstrap.esm.js",
"maxSize": "39.0 kB"
"maxSize": "28.0 kB"
},
{
"path": "./dist/js/bootstrap.esm.min.js",
"maxSize": "24.0 kB"
"maxSize": "18.25 kB"
},
{
"path": "./dist/js/bootstrap.js",
"maxSize": "39.75 kB"
"maxSize": "28.75 kB"
},
{
"path": "./dist/js/bootstrap.min.js",
"maxSize": "21.25 kB"
"maxSize": "16.25 kB"
}
],
"ci": {
-3
View File
@@ -21,7 +21,6 @@
"callout",
"callouts",
"camelCase",
"checkgroup",
"clearfix",
"Codesniffer",
"combinator",
@@ -30,7 +29,6 @@
"Crossfade",
"crossfading",
"cssgrid",
"csstricks",
"Csvg",
"Datalists",
"Deque",
@@ -48,7 +46,6 @@
"favicons",
"fieldsets",
"flexbox",
"frontmatter",
"fullscreen",
"getbootstrap",
"Grayscale",
+162
View File
@@ -0,0 +1,162 @@
# 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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: "${{ env.NODE }}"
cache: npm
+2 -3
View File
@@ -4,7 +4,6 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -21,12 +20,12 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: "${{ env.NODE }}"
cache: npm
@@ -18,12 +18,11 @@ jobs:
name: calibreapp/image-actions
runs-on: ubuntu-latest
permissions:
# allow calibreapp/image-actions to update PRs and commit compressed images
contents: write
# allow calibreapp/image-actions to update PRs
pull-requests: write
steps:
- name: Clone repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
+4 -6
View File
@@ -5,13 +5,11 @@ on:
branches:
- main
- v4-dev
- v6-dev
- "!dependabot/**"
pull_request:
branches:
- main
- v4-dev
- v6-dev
- "!dependabot/**"
schedule:
- cron: "0 2 * * 4"
@@ -26,21 +24,21 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
config-file: ./.github/codeql/codeql-config.yml
languages: "javascript"
queries: +security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
uses: github/codeql-action/autobuild@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
category: "/language:javascript"
+2 -3
View File
@@ -4,7 +4,6 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -24,12 +23,12 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Run cspell
uses: streetsidesoftware/cspell-action@3294df585d3d639e30f3bc019cb11940b9866e95 # v8.0.0
uses: streetsidesoftware/cspell-action@e5a858a18b7e0b56e0342b1dcad796308b7341a2 # v8.1.1
with:
config: ".cspell.json"
files: "**/*.{md,mdx}"
+2 -3
View File
@@ -4,7 +4,6 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -21,12 +20,12 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: "${{ env.NODE }}"
cache: npm
+5 -4
View File
@@ -4,7 +4,6 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -21,16 +20,18 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: "${{ env.NODE }}"
cache: npm
- run: java -version
- name: Install npm dependencies
run: npm ci
@@ -38,7 +39,7 @@ jobs:
run: npm run docs-build
- name: Validate HTML
run: npm run docs-html-validate
run: npm run docs-vnu
- 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@9861779a695cf1898bd984c727f685f351cfc372 # v3.7.2
uses: actions-cool/issues-helper@d1d51fccf39469b5458203b1369060db0ff0c0db # v3.7.4
with:
actions: "close-issues"
labels: "awaiting-reply"
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
steps:
- name: awaiting reply
if: github.event.label.name == 'needs-example'
uses: actions-cool/issues-helper@9861779a695cf1898bd984c727f685f351cfc372 # v3.7.2
uses: actions-cool/issues-helper@d1d51fccf39469b5458203b1369060db0ff0c0db # v3.7.4
with:
actions: "create-comment"
token: ${{ secrets.GITHUB_TOKEN }}
+3 -4
View File
@@ -4,7 +4,6 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -26,12 +25,12 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: ${{ env.NODE }}
cache: npm
@@ -46,7 +45,7 @@ jobs:
run: npm run js-test
- name: Run Coveralls
uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # v2.3.6
uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2.3.7
if: ${{ !github.event.repository.fork }}
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
+2 -3
View File
@@ -4,7 +4,6 @@ on:
push:
branches:
- main
- v6-dev
pull_request:
workflow_dispatch:
@@ -21,12 +20,12 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: "${{ env.NODE }}"
cache: npm
+49
View File
@@ -0,0 +1,49 @@
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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
-1
View File
@@ -4,7 +4,6 @@ on:
push:
branches:
- main
- v6-dev
workflow_dispatch:
permissions:
+4 -4
View File
@@ -12,7 +12,7 @@ on:
schedule:
- cron: '27 12 * * 2'
push:
branches: [ "main", "v6-dev" ]
branches: [ "main" ]
# Declare default permissions as read only.
permissions: read-all
@@ -34,7 +34,7 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
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@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.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@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
sarif_file: results.sarif
-1
View File
@@ -18,7 +18,6 @@
.cache
.DS_Store
.idea
.nvmrc
.project
.settings
.tmproj
-18
View File
@@ -1,18 +0,0 @@
{
"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
}
+1 -15
View File
@@ -2,23 +2,8 @@
"extends": [
"stylelint-config-twbs-bootstrap"
],
"plugins": [
"stylelint-order"
],
"reportInvalidScopeDisables": true,
"reportNeedlessDisables": true,
"rules": {
"order/order": [
[
{ "type": "at-rule", "name": "use" },
{ "type": "at-rule", "name": "forward" },
"dollar-variables",
"custom-properties",
"declarations",
"rules"
]
]
},
"overrides": [
{
"files": "**/*.scss",
@@ -28,6 +13,7 @@
"outline": "none"
},
"function-disallowed-list": [
"calc",
"lighten",
"darken"
],
+19 -3
View File
@@ -52,7 +52,7 @@ Several quick start options are available:
- Install with [Composer](https://getcomposer.org/): `composer require twbs/bootstrap:5.3.8`
- Install with [NuGet](https://www.nuget.org/): CSS: `Install-Package bootstrap` Sass: `Install-Package bootstrap.sass`
Read the [Getting started page](https://getbootstrap.com/docs/5.3/getting-started/) for information on the framework contents, templates, examples, and more.
Read the [Getting started page](https://getbootstrap.com/docs/5.3/getting-started/introduction/) for information on the framework contents, templates, examples, and more.
## Status
@@ -87,18 +87,34 @@ Within the download youll find the following directories and files, logically
│ ├── bootstrap-grid.css.map
│ ├── bootstrap-grid.min.css
│ ├── bootstrap-grid.min.css.map
│ ├── bootstrap-grid.rtl.css
│ ├── bootstrap-grid.rtl.css.map
│ ├── bootstrap-grid.rtl.min.css
│ ├── bootstrap-grid.rtl.min.css.map
│ ├── bootstrap-reboot.css
│ ├── bootstrap-reboot.css.map
│ ├── bootstrap-reboot.min.css
│ ├── bootstrap-reboot.min.css.map
│ ├── bootstrap-reboot.rtl.css
│ ├── bootstrap-reboot.rtl.css.map
│ ├── bootstrap-reboot.rtl.min.css
│ ├── bootstrap-reboot.rtl.min.css.map
│ ├── bootstrap-utilities.css
│ ├── bootstrap-utilities.css.map
│ ├── bootstrap-utilities.min.css
│ ├── bootstrap-utilities.min.css.map
│ ├── bootstrap-utilities.rtl.css
│ ├── bootstrap-utilities.rtl.css.map
│ ├── bootstrap-utilities.rtl.min.css
│ ├── bootstrap-utilities.rtl.min.css.map
│ ├── bootstrap.css
│ ├── bootstrap.css.map
│ ├── bootstrap.min.css
── bootstrap.min.css.map
── bootstrap.min.css.map
│ ├── bootstrap.rtl.css
│ ├── bootstrap.rtl.css.map
│ ├── bootstrap.rtl.min.css
│ └── bootstrap.rtl.min.css.map
└── js/
├── bootstrap.bundle.js
├── bootstrap.bundle.js.map
@@ -115,7 +131,7 @@ Within the download youll find the following directories and files, logically
```
</details>
We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [Source maps](https://web.dev/articles/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/docs/v2/). All CSS files work for both LTR and RTL layouts thanks to logical properties—simply set `dir="rtl"` on your HTML element.
We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [Source maps](https://web.dev/articles/source-maps) (`bootstrap.*.map`) are available for use with certain browsers developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/docs/v2/).
## Bugs and feature requests
-93
View File
@@ -1,93 +0,0 @@
#!/usr/bin/env node
import { execSync } from 'node:child_process'
// Run bundlewatch and capture output
let stdout
let exitCode = 0
try {
stdout = execSync('npx bundlewatch --config .bundlewatch.config.json', {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe']
})
} catch (error) {
stdout = error.stdout || ''
exitCode = error.status || 1
}
// Parse lines that contain PASS or FAIL
const lines = stdout.split('\n').filter(l => l.startsWith('PASS') || l.startsWith('FAIL'))
if (lines.length === 0) {
console.log(stdout)
process.exit(exitCode)
}
// Parse size string to number (KB)
const parseSize = str => Number.parseFloat(str.replace('KB', ''))
// Calculate column widths and headroom
const rows = lines.map(line => {
const match = line.match(/(PASS|FAIL)\s+(.+?):\s+([\d.]+KB)\s+([<>])\s+([\d.]+KB)/)
if (match) {
const sizeNum = parseSize(match[3])
const maxNum = parseSize(match[5])
const headroomNum = maxNum - sizeNum
const headroom = `${headroomNum.toFixed(2)}KB`
return {
status: match[1],
file: match[2],
size: match[3],
max: match[5],
headroomNum,
headroom: match[1] === 'PASS' ? `+${headroom}` : `-${Math.abs(headroomNum).toFixed(2)}KB`
}
}
return null
}).filter(Boolean)
const maxFileLen = Math.max(...rows.map(r => r.file.length), 4)
const maxSizeLen = Math.max(...rows.map(r => r.size.length), 4)
const maxMaxLen = Math.max(...rows.map(r => r.max.length), 3)
const maxHeadroomLen = Math.max(...rows.map(r => r.headroom.length), 8)
// Build table
const hr = `+-${'-'.repeat(maxFileLen)}-+-${'-'.repeat(maxSizeLen)}-+-${'-'.repeat(maxMaxLen)}-+-${'-'.repeat(maxHeadroomLen)}-+`
console.log('')
console.log('bundlewatch results')
console.log(hr)
console.log(`| ${'File'.padEnd(maxFileLen)} | ${'Size'.padStart(maxSizeLen)} | ${'Max'.padStart(maxMaxLen)} | ${'Headroom'.padStart(maxHeadroomLen)} |`)
console.log(hr)
const green = '\u001B[32m'
const red = '\u001B[31m'
const reset = '\u001B[0m'
for (const row of rows) {
const sizeColor = row.status === 'PASS' ? green : red
const coloredSize = `${sizeColor}${row.size.padStart(maxSizeLen)}${reset}`
const headroomColor = row.headroomNum > 0.25 ? red : ''
const headroomReset = row.headroomNum > 0.25 ? reset : ''
const coloredHeadroom = `${headroomColor}${row.headroom.padStart(maxHeadroomLen)}${headroomReset}`
console.log(`| ${row.file.padEnd(maxFileLen)} | ${coloredSize} | ${row.max.padStart(maxMaxLen)} | ${coloredHeadroom} |`)
}
console.log(hr)
// Summary
const passed = rows.filter(r => r.status === 'PASS').length
const failed = rows.filter(r => r.status === 'FAIL').length
console.log('')
if (failed > 0) {
console.log(`\u001B[31mbundlewatch FAIL\u001B[0m - ${passed} passed, ${failed} failed`)
} else {
console.log(`\u001B[32mbundlewatch PASS\u001B[0m - ${passed}/${rows.length} files within limits`)
}
console.log('')
process.exit(exitCode)
+6 -2
View File
@@ -29,6 +29,10 @@ const files = [
file: 'dist/css/bootstrap.min.css',
configPropertyName: 'css_hash'
},
{
file: 'dist/css/bootstrap.rtl.min.css',
configPropertyName: 'css_rtl_hash'
},
{
file: 'dist/js/bootstrap.min.js',
configPropertyName: 'js_hash'
@@ -38,8 +42,8 @@ const files = [
configPropertyName: 'js_bundle_hash'
},
{
file: 'node_modules/@floating-ui/dom/dist/floating-ui.dom.umd.min.js',
configPropertyName: 'floating_ui_hash'
file: 'node_modules/@popperjs/core/dist/umd/popper.min.js',
configPropertyName: 'popper_hash'
}
]
-87
View File
@@ -1,87 +0,0 @@
#!/usr/bin/env node
/**
* Generate utilities metadata JSON from Sass
* This script compiles a special Sass file that outputs utility information as CSS comments,
* then extracts and saves it as JSON for documentation use.
*/
import { readFileSync, writeFileSync, unlinkSync } from 'node:fs'
import { execSync } from 'node:child_process'
import { fileURLToPath } from 'node:url'
import path from 'node:path'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const rootDir = path.join(__dirname, '..')
// Compile the metadata generator SCSS file
console.log('Compiling utilities metadata...')
try {
execSync(
'sass --style expanded --no-source-map build/generate-utilities-metadata.scss:dist/css/utilities-metadata.tmp.css',
{ cwd: rootDir, stdio: 'inherit' }
)
} catch {
console.error('Failed to compile metadata SCSS')
process.exit(1)
}
// Read the compiled CSS
const cssPath = path.join(rootDir, 'dist/css/utilities-metadata.tmp.css')
const cssContent = readFileSync(cssPath, 'utf8')
// Extract JSON from the CSS comment
const startMarker = 'BOOTSTRAP-UTILITIES-METADATA-START'
const endMarker = 'BOOTSTRAP-UTILITIES-METADATA-END'
const startIndex = cssContent.indexOf(startMarker)
const endIndex = cssContent.indexOf(endMarker)
if (startIndex === -1 || endIndex === -1) {
console.error('Could not find metadata markers in compiled CSS')
process.exit(1)
}
// Extract JSON content between markers
const jsonContent = cssContent
.slice(startIndex + startMarker.length, endIndex)
.trim()
// Validate JSON
try {
const parsed = JSON.parse(jsonContent)
console.log(`✓ Extracted metadata for ${Object.keys(parsed.utilities).length} utilities`)
// Write to JSON file
const outputPath = path.join(rootDir, 'dist/css/bootstrap-utilities.metadata.json')
writeFileSync(outputPath, JSON.stringify(parsed, null, 2))
console.log(`✓ Wrote metadata to ${outputPath}`)
// Clean up temporary CSS files
try {
unlinkSync(cssPath)
} catch {
// File may not exist
}
// Also clean up any other temporary variants that may have been created
const tempFiles = [
'dist/css/utilities-metadata.tmp.min.css',
'dist/css/utilities-metadata.tmp.min.css.map'
]
for (const file of tempFiles) {
try {
unlinkSync(path.join(rootDir, file))
} catch {
// File may not exist, ignore
}
}
console.log('✓ Cleaned up temporary files')
} catch (error) {
console.error('Failed to parse extracted JSON:', error.message)
console.error('Extracted content:', jsonContent.slice(0, 500))
process.exit(1)
}
-124
View File
@@ -1,124 +0,0 @@
// Generate utilities metadata JSON for documentation
// This file is compiled to extract utility information without generating CSS
@use "sass:map";
@use "sass:list";
@use "sass:string";
@use "sass:meta";
@use "../scss/config" as *;
@use "../scss/colors" as *;
@use "../scss/variables" as *;
@use "../scss/functions" as *;
@use "../scss/theme" as *;
@use "../scss/utilities" as *;
// Access the utilities map
$utilities-map: $utilities !default;
// Start JSON output
$json: '{"utilities":{' !default;
$utility-count: 0 !default;
$total-utilities: list.length(map.keys($utilities-map)) !default;
@each $key, $utility in $utilities-map {
$utility-count: $utility-count + 1;
// Skip if utility is null or false (disabled)
@if $utility {
// Extract class prefix
$class: $key;
@if map.has-key($utility, "class") {
$class: map.get($utility, "class");
}
// Extract property
$property: null;
@if map.has-key($utility, "property") {
$property: map.get($utility, "property");
}
// Extract values
$values: null;
@if map.has-key($utility, "values") {
$values: map.get($utility, "values");
}
// Generate class list
$classes: "";
@if $values {
@if meta.type-of($values) == "map" {
$value-keys: map.keys($values);
$first: true;
@each $value-key in $value-keys {
@if not $first {
$classes: $classes + ", ";
}
$class-name: "#{$class}-#{$value-key}";
@if $value-key == "null" or $value-key == null {
$class-name: $class;
}
$classes: $classes + '"' + $class-name + '"';
$first: false;
}
} @else if meta.type-of($values) == "list" {
$first: true;
@each $value in $values {
@if not $first {
$classes: $classes + ", ";
}
$class-name: "#{$class}-#{$value}";
$classes: $classes + '"' + $class-name + '"';
$first: false;
}
}
}
// Build JSON entry
$json: $json + '"' + $key + '":{"class":"' + $class + '"';
@if $property {
@if meta.type-of($property) == "string" {
$json: $json + ',"property":"' + $property + '"';
} @else if meta.type-of($property) == "list" {
$property-str: "";
$first: true;
@each $prop in $property {
@if not $first {
$property-str: $property-str + " ";
}
$property-str: $property-str + $prop;
$first: false;
}
$json: $json + ',"property":"' + $property-str + '"';
}
// Skip map properties as they're complex and don't translate to JSON well
}
@if $classes != "" {
$json: $json + ',"classes":[' + $classes + "]";
} @else {
$json: $json + ',"classes":[]';
}
$json: $json + "}";
@if $utility-count < $total-utilities {
$json: $json + ",";
}
}
}
// stylelint-disable-next-line scss/dollar-variable-default
$json: $json + "}}";
// Output as CSS comment so it appears in compiled file
/*! BOOTSTRAP-UTILITIES-METADATA-START
#{$json}
BOOTSTRAP-UTILITIES-METADATA-END */
// Prevent any actual CSS output
.bootstrap-utilities-metadata-generator {
content: "This file should not generate CSS, only metadata comments";
}
-104
View File
@@ -1,104 +0,0 @@
#!/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()
+6 -10
View File
@@ -1,6 +1,3 @@
import postcssPrefixCustomProperties from 'postcss-prefix-custom-properties'
import autoprefixer from 'autoprefixer'
const mapConfig = {
inline: false,
annotation: true,
@@ -10,12 +7,11 @@ const mapConfig = {
export default context => {
return {
map: context.file.dirname.includes('examples') ? false : mapConfig,
plugins: [
postcssPrefixCustomProperties({
prefix: 'bs-',
ignore: [/^--bs-/, /^--bd-/]
}),
autoprefixer({ cascade: false })
]
plugins: {
autoprefixer: {
cascade: false
},
rtlcss: context.env === 'RTL'
}
}
}
+4 -4
View File
@@ -12,7 +12,7 @@ const BUNDLE = process.env.BUNDLE === 'true'
const ESM = process.env.ESM === 'true'
let destinationFile = `bootstrap${ESM ? '.esm' : ''}`
const external = ['@floating-ui/dom']
const external = ['@popperjs/core']
const plugins = [
babel({
// Only transpile our source code
@@ -22,14 +22,14 @@ const plugins = [
})
]
const globals = {
'@floating-ui/dom': 'FloatingUIDOM'
'@popperjs/core': 'Popper'
}
if (BUNDLE) {
destinationFile += '.bundle'
// Remove last entry in external array to bundle Floating UI
// Remove last entry in external array to bundle Popper
external.pop()
delete globals['@floating-ui/dom']
delete globals['@popperjs/core']
plugins.push(
replace({
'process.env.NODE_ENV': '"production"',
+66
View File
@@ -0,0 +1,66 @@
#!/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)
})
+3 -1
View File
@@ -26,7 +26,9 @@ const docsDir = `${rootDocsDir}/docs/${versionShort}/`
// these are the files we need in the examples
const cssFiles = [
'bootstrap.min.css',
'bootstrap.min.css.map'
'bootstrap.min.css.map',
'bootstrap.rtl.min.css',
'bootstrap.rtl.min.css.map'
]
const jsFiles = [
'bootstrap.bundle.min.js',
+8 -6
View File
@@ -35,14 +35,16 @@ download:
cdn:
# See https://www.srihash.org for info on how to generate the hashes
css: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css"
css_hash: "sha384-TDmpFhAO5TwSQwPF95I/odgwpTUuv0aaVm9/0fL7b+kKe7hFBp/+9cBCMkydgGOi"
css_hash: "sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB"
css_rtl: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.rtl.min.css"
css_rtl_hash: "sha384-CfCrinSRH2IR6a4e6fy2q6ioOX7O6Mtm1L9vRvFZ1trBncWmMePhzvafv7oIcWiW"
js: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.min.js"
js_hash: "sha384-Php492snRLTR5p+hMyxpV6gYwp1avWXn4AaX31MgANrvsjr9Dpodl3Nw60L7Pewl"
js_hash: "sha384-G/EV+4j2dNv+tEPo3++6LCgdCROaejBqfUeNjuKAiuXbjrxilcCdDz6ZAVfHWe1Y"
js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
js_bundle_hash: "sha384-I2J4jlw924JZXHU9un9Mcuixq/rKhd5A8/B1NQ6ifPAiBFacZjwNcec8d6L38jQv"
floating_ui: "https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.7.0/dist/floating-ui.dom.umd.min.js"
floating_ui_hash: "sha384-R7p1RqabZNhI+RdPNIzTouzd/LBVorZ0Tn3ApcogSOk+HF3o+P0HIenrUw/n0MOj"
floating_ui_esm: "https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.7.0/dist/floating-ui.dom.esm.min.js"
js_bundle_hash: "sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
popper: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"
popper_hash: "sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r"
popper_esm: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/esm/popper.min.js"
anchors:
min: 2
+3803 -4352
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -4
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4084
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+564 -806
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -4
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+598
View File
@@ -0,0 +1,598 @@
/*!
* Bootstrap Reboot v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
line-height: inherit;
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type=search]::-webkit-search-cancel-button {
cursor: pointer;
filter: grayscale(1);
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+5159 -5368
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+11674 -12092
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -4
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+12021
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2887 -4077
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -4
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1163 -2573
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
+1185 -2579
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
+25 -5
View File
@@ -1,13 +1,13 @@
/*!
* Bootstrap alert.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
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';
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';
/**
* --------------------------------------------------------------------------
@@ -56,6 +56,20 @@
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);
});
}
}
/**
@@ -64,6 +78,12 @@
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'\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;;;;;;;;"}
{"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;;;;;;;;"}
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap base-component.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+21 -5
View File
@@ -1,13 +1,13 @@
/*!
* Bootstrap button.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
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';
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';
/**
* --------------------------------------------------------------------------
@@ -44,6 +44,16 @@
// 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]();
}
});
}
}
/**
@@ -57,6 +67,12 @@
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'\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;;;;;;;;"}
{"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;;;;;;;;"}
+24 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap carousel.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
@@ -325,6 +325,23 @@
}
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]();
}
});
}
}
/**
@@ -359,6 +376,12 @@
}
});
/**
* jQuery
*/
index_js.defineJQueryPlugin(Carousel);
return Carousel;
}));
+1 -1
View File
File diff suppressed because one or more lines are too long
+24 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap collapse.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
@@ -202,6 +202,23 @@
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]();
}
});
}
}
/**
@@ -220,6 +237,12 @@
}
});
/**
* 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
@@ -1,443 +0,0 @@
/*!
* Bootstrap datepicker.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('vanilla-calendar-pro'), require('./base-component.js'), require('./dom/event-handler.js'), require('./util/index.js')) :
typeof define === 'function' && define.amd ? define(['vanilla-calendar-pro', './base-component', './dom/event-handler', './util/index'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Datepicker = factory(global["vanilla-calendar-pro"], global.BaseComponent, global.EventHandler, global.Index));
})(this, (function (vanillaCalendarPro, BaseComponent, EventHandler, index_js) { 'use strict';
/**
* --------------------------------------------------------------------------
* Bootstrap datepicker.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
const NAME = 'datepicker';
const DATA_KEY = 'bs.datepicker';
const EVENT_KEY = `.${DATA_KEY}`;
const DATA_API_KEY = '.data-api';
const EVENT_CHANGE = `change${EVENT_KEY}`;
const EVENT_SHOW = `show${EVENT_KEY}`;
const EVENT_SHOWN = `shown${EVENT_KEY}`;
const EVENT_HIDE = `hide${EVENT_KEY}`;
const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
const EVENT_FOCUSIN_DATA_API = `focusin${EVENT_KEY}${DATA_API_KEY}`;
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="datepicker"]';
const HIDE_DELAY = 100; // ms delay before hiding after selection
const Default = {
datepickerTheme: null,
// 'light', 'dark', 'auto' - explicit theme for datepicker popover only
dateMin: null,
dateMax: null,
dateFormat: null,
// Intl.DateTimeFormat options, or function(date, locale) => string
displayElement: null,
// Element to show formatted date (defaults to element for buttons)
displayMonthsCount: 1,
// Number of months to display side-by-side
firstWeekday: 1,
// Monday
inline: false,
// Render calendar inline (no popup)
locale: 'default',
positionElement: null,
// Element to position calendar relative to (defaults to input)
selectedDates: [],
selectionMode: 'single',
// 'single', 'multiple', 'multiple-ranged'
placement: 'left',
// 'left', 'center', 'right', 'auto'
vcpOptions: {} // Pass-through for any VCP option
};
const DefaultType = {
datepickerTheme: '(null|string)',
dateMin: '(null|string|number|object)',
dateMax: '(null|string|number|object)',
dateFormat: '(null|object|function)',
displayElement: '(null|string|element|boolean)',
displayMonthsCount: 'number',
firstWeekday: 'number',
inline: 'boolean',
locale: 'string',
positionElement: '(null|string|element)',
selectedDates: 'array',
selectionMode: 'string',
placement: 'string',
vcpOptions: 'object'
};
/**
* Class definition
*/
class Datepicker extends BaseComponent {
constructor(element, config) {
super(element, config);
this._calendar = null;
this._isShown = false;
this._initCalendar();
}
// Getters
static get Default() {
return Default;
}
static get DefaultType() {
return DefaultType;
}
static get NAME() {
return NAME;
}
// Public
toggle() {
if (this._config.inline) {
return; // Inline calendars are always visible
}
return this._isShown ? this.hide() : this.show();
}
show() {
if (this._config.inline) {
return; // Inline calendars are always visible
}
if (!this._calendar || index_js.isDisabled(this._element) || this._isShown) {
return;
}
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW);
if (showEvent.defaultPrevented) {
return;
}
this._calendar.show();
this._isShown = true;
EventHandler.trigger(this._element, EVENT_SHOWN);
}
hide() {
if (this._config.inline) {
return; // Inline calendars are always visible
}
if (!this._calendar || !this._isShown) {
return;
}
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);
if (hideEvent.defaultPrevented) {
return;
}
this._calendar.hide();
this._isShown = false;
EventHandler.trigger(this._element, EVENT_HIDDEN);
}
dispose() {
if (this._themeObserver) {
this._themeObserver.disconnect();
this._themeObserver = null;
}
if (this._calendar) {
this._calendar.destroy();
}
this._calendar = null;
super.dispose();
}
getSelectedDates() {
const dates = this._calendar?.context?.selectedDates;
return dates ? [...dates] : [];
}
setSelectedDates(dates) {
if (this._calendar) {
this._calendar.set({
selectedDates: dates
});
}
}
// Private
_initCalendar() {
this._isInput = this._element.tagName === 'INPUT';
this._isInline = this._config.inline;
// For inline mode, look for a hidden input child to bind to
if (this._isInline && !this._isInput) {
this._boundInput = this._element.querySelector('input[type="hidden"], input[name]');
}
this._positionElement = this._resolvePositionElement();
this._displayElement = this._resolveDisplayElement();
const calendarOptions = this._buildCalendarOptions();
// Create calendar on the position element (for correct popup positioning)
// but value updates still go to this._element (the input)
this._calendar = new vanillaCalendarPro.Calendar(this._positionElement, calendarOptions);
this._calendar.init();
// Watch for theme changes on ancestor elements (for live theme switching)
this._setupThemeObserver();
// Set initial value if input has a value
if (this._isInput && this._element.value) {
this._parseInputValue();
}
// Populate input/display with preselected dates
this._updateDisplayWithSelectedDates();
}
_updateDisplayWithSelectedDates() {
const {
selectedDates
} = this._config;
if (!selectedDates || selectedDates.length === 0) {
return;
}
const formattedDate = this._formatDateForInput(selectedDates);
if (this._isInput) {
this._element.value = formattedDate;
}
if (this._boundInput) {
this._boundInput.value = selectedDates.join(',');
}
if (this._displayElement) {
this._displayElement.textContent = formattedDate;
}
}
_resolvePositionElement() {
let {
positionElement
} = this._config;
if (typeof positionElement === 'string') {
positionElement = document.querySelector(positionElement);
}
// Use input's parent if in form-adorn
if (!positionElement && this._isInput && !this._isInline) {
const parent = this._element.closest('.form-adorn');
if (parent) {
positionElement = parent;
}
}
return positionElement || this._element;
}
_resolveDisplayElement() {
const {
displayElement
} = this._config;
if (typeof displayElement === 'string') {
return document.querySelector(displayElement);
}
// For buttons/non-inputs (not inline), look for a [data-bs-datepicker-display] child
if (displayElement === true || displayElement === null && !this._isInput && !this._isInline) {
const displayChild = this._element.querySelector('[data-bs-datepicker-display]');
return displayChild || this._element;
}
return displayElement;
}
_getThemeAncestor() {
return this._element.closest('[data-bs-theme]');
}
_getEffectiveTheme() {
// Priority: explicit datepickerTheme config > inherited from ancestor > none
const {
datepickerTheme
} = this._config;
if (datepickerTheme) {
return datepickerTheme;
}
const ancestor = this._getThemeAncestor();
return ancestor?.getAttribute('data-bs-theme') || null;
}
_syncThemeAttribute(element) {
if (!element) {
return;
}
const theme = this._getEffectiveTheme();
if (theme) {
// Copy theme to popover (needed because VCP appends to body, breaking CSS inheritance)
element.setAttribute('data-bs-theme', theme);
} else {
// No theme - remove attribute to allow natural inheritance
element.removeAttribute('data-bs-theme');
}
}
_setupThemeObserver() {
// Watch for theme changes on ancestor elements
const ancestor = this._getThemeAncestor();
if (!ancestor || this._config.datepickerTheme) {
// No ancestor to watch, or explicit datepickerTheme overrides
return;
}
this._themeObserver = new MutationObserver(() => {
this._syncThemeAttribute(this._calendar?.context?.mainElement);
});
this._themeObserver.observe(ancestor, {
attributes: true,
attributeFilter: ['data-bs-theme']
});
}
_buildCalendarOptions() {
// Get theme for VCP - use 'system' for auto-detection if no explicit theme
const theme = this._getEffectiveTheme();
// VCP uses 'system' for auto, Bootstrap uses 'auto'
const vcpTheme = !theme || theme === 'auto' ? 'system' : theme;
const calendarOptions = {
...this._config.vcpOptions,
inputMode: !this._isInline,
positionToInput: this._config.placement,
firstWeekday: this._config.firstWeekday,
locale: this._config.locale,
selectionDatesMode: this._config.selectionMode,
selectedDates: this._config.selectedDates,
displayMonthsCount: this._config.displayMonthsCount,
type: this._config.displayMonthsCount > 1 ? 'multiple' : 'default',
selectedTheme: vcpTheme,
themeAttrDetect: '[data-bs-theme]',
onClickDate: (self, event) => this._handleDateClick(self, event),
onInit: self => {
this._syncThemeAttribute(self.context.mainElement);
},
onShow: () => {
this._isShown = true;
this._syncThemeAttribute(this._calendar.context.mainElement);
},
onHide: () => {
this._isShown = false;
}
};
// Navigate to the month of the first selected date
if (this._config.selectedDates.length > 0) {
const firstDate = this._parseDate(this._config.selectedDates[0]);
calendarOptions.selectedMonth = firstDate.getMonth();
calendarOptions.selectedYear = firstDate.getFullYear();
}
if (this._config.dateMin) {
calendarOptions.dateMin = this._config.dateMin;
}
if (this._config.dateMax) {
calendarOptions.dateMax = this._config.dateMax;
}
return calendarOptions;
}
_handleDateClick(self, event) {
const selectedDates = [...self.context.selectedDates];
if (selectedDates.length > 0) {
const formattedDate = this._formatDateForInput(selectedDates);
if (this._isInput) {
this._element.value = formattedDate;
}
if (this._boundInput) {
this._boundInput.value = selectedDates.join(',');
}
if (this._displayElement) {
this._displayElement.textContent = formattedDate;
}
}
EventHandler.trigger(this._element, EVENT_CHANGE, {
dates: selectedDates,
event
});
this._maybeHideAfterSelection(selectedDates);
}
_maybeHideAfterSelection(selectedDates) {
if (this._isInline) {
return;
}
const shouldHide = this._config.selectionMode === 'single' && selectedDates.length > 0 || this._config.selectionMode === 'multiple-ranged' && selectedDates.length >= 2;
if (shouldHide) {
setTimeout(() => this.hide(), HIDE_DELAY);
}
}
_parseDate(dateStr) {
const [year, month, day] = dateStr.split('-');
return new Date(year, month - 1, day);
}
_formatDate(dateStr) {
const date = this._parseDate(dateStr);
const locale = this._config.locale === 'default' ? undefined : this._config.locale;
const {
dateFormat
} = this._config;
// Custom function formatter
if (typeof dateFormat === 'function') {
return dateFormat(date, locale);
}
// Intl.DateTimeFormat options object
if (dateFormat && typeof dateFormat === 'object') {
return new Intl.DateTimeFormat(locale, dateFormat).format(date);
}
// Default: locale-aware formatting
return date.toLocaleDateString(locale);
}
_formatDateForInput(dates) {
if (dates.length === 0) {
return '';
}
if (dates.length === 1) {
return this._formatDate(dates[0]);
}
// For date ranges, use en-dash; for multiple dates, use comma
const separator = this._config.selectionMode === 'multiple-ranged' ? ' ' : ', ';
return dates.map(d => this._formatDate(d)).join(separator);
}
_parseInputValue() {
// Try to parse the input value as a date
const value = this._element.value.trim();
if (!value) {
return;
}
const date = new Date(value);
if (!Number.isNaN(date.getTime())) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const formatted = `${year}-${month}-${day}`;
this._calendar.set({
selectedDates: [formatted]
});
}
}
}
/**
* Data API implementation
*/
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
// Only handle if not an input (inputs use focus)
// Skip inline datepickers (they're always visible)
if (this.tagName === 'INPUT' || this.dataset.bsInline === 'true') {
return;
}
event.preventDefault();
Datepicker.getOrCreateInstance(this).toggle();
});
EventHandler.on(document, EVENT_FOCUSIN_DATA_API, SELECTOR_DATA_TOGGLE, function () {
// Handle focus for input elements
if (this.tagName !== 'INPUT') {
return;
}
Datepicker.getOrCreateInstance(this).show();
});
// Auto-initialize inline datepickers on DOMContentLoaded
EventHandler.on(document, `DOMContentLoaded${EVENT_KEY}${DATA_API_KEY}`, () => {
for (const element of document.querySelectorAll(`${SELECTOR_DATA_TOGGLE}[data-bs-inline="true"]`)) {
Datepicker.getOrCreateInstance(element);
}
});
return Datepicker;
}));
//# sourceMappingURL=datepicker.js.map
-1
View File
File diff suppressed because one or more lines are too long
-242
View File
@@ -1,242 +0,0 @@
/*!
* Bootstrap dialog.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./util/component-functions.js'), require('./util/index.js')) :
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/component-functions', './util/index'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Dialog = factory(global.BaseComponent, global.EventHandler, global.Manipulator, global.SelectorEngine, global.ComponentFunctions, global.Index));
})(this, (function (BaseComponent, EventHandler, Manipulator, SelectorEngine, componentFunctions_js, index_js) { 'use strict';
/**
* --------------------------------------------------------------------------
* Bootstrap dialog.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
const NAME = 'dialog';
const DATA_KEY = 'bs.dialog';
const EVENT_KEY = `.${DATA_KEY}`;
const DATA_API_KEY = '.data-api';
const EVENT_SHOW = `show${EVENT_KEY}`;
const EVENT_SHOWN = `shown${EVENT_KEY}`;
const EVENT_HIDE = `hide${EVENT_KEY}`;
const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`;
const EVENT_CANCEL = `cancel${EVENT_KEY}`;
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
const CLASS_NAME_STATIC = 'dialog-static';
const CLASS_NAME_OPEN = 'dialog-open';
const CLASS_NAME_NONMODAL = 'dialog-nonmodal';
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dialog"]';
const SELECTOR_OPEN_MODAL_DIALOG = 'dialog.dialog[open]:not(.dialog-nonmodal)';
const Default = {
backdrop: true,
// true (click dismisses) or 'static' (click does nothing) - only applies to modal dialogs
keyboard: true,
modal: true // true uses showModal(), false uses show() for non-modal dialogs
};
const DefaultType = {
backdrop: '(boolean|string)',
keyboard: 'boolean',
modal: 'boolean'
};
/**
* Class definition
*/
class Dialog extends BaseComponent {
constructor(element, config) {
super(element, config);
this._isTransitioning = false;
this._addEventListeners();
}
// Getters
static get Default() {
return Default;
}
static get DefaultType() {
return DefaultType;
}
static get NAME() {
return NAME;
}
// Public
toggle(relatedTarget) {
return this._element.open ? this.hide() : this.show(relatedTarget);
}
show(relatedTarget) {
if (this._element.open || this._isTransitioning) {
return;
}
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {
relatedTarget
});
if (showEvent.defaultPrevented) {
return;
}
this._isTransitioning = true;
if (this._config.modal) {
// Modal dialog: use showModal() for focus trapping, backdrop, and top layer
this._element.showModal();
// Prevent body scroll for modal dialogs
document.body.classList.add(CLASS_NAME_OPEN);
} else {
// Non-modal dialog: use show() - no backdrop, no focus trap, no top layer
this._element.classList.add(CLASS_NAME_NONMODAL);
this._element.show();
}
this._queueCallback(() => {
this._isTransitioning = false;
EventHandler.trigger(this._element, EVENT_SHOWN, {
relatedTarget
});
}, this._element, this._isAnimated());
}
hide() {
if (!this._element.open || this._isTransitioning) {
return;
}
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);
if (hideEvent.defaultPrevented) {
return;
}
this._isTransitioning = true;
this._queueCallback(() => this._hideDialog(), this._element, this._isAnimated());
}
dispose() {
EventHandler.off(this._element, EVENT_KEY);
super.dispose();
}
handleUpdate() {
// Provided for API consistency with Modal.
// Native dialogs handle their own positioning.
}
// Private
_hideDialog() {
this._element.close();
this._element.classList.remove(CLASS_NAME_NONMODAL);
this._isTransitioning = false;
// Only restore body scroll if no other modal dialogs are open
if (!document.querySelector(SELECTOR_OPEN_MODAL_DIALOG)) {
document.body.classList.remove(CLASS_NAME_OPEN);
}
EventHandler.trigger(this._element, EVENT_HIDDEN);
}
_isAnimated() {
return this._element.classList.contains('fade');
}
_triggerBackdropTransition() {
const hidePreventedEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);
if (hidePreventedEvent.defaultPrevented) {
return;
}
this._element.classList.add(CLASS_NAME_STATIC);
this._queueCallback(() => {
this._element.classList.remove(CLASS_NAME_STATIC);
}, this._element);
}
_addEventListeners() {
// Handle native cancel event (Escape key) - only fires for modal dialogs
EventHandler.on(this._element, 'cancel', event => {
// Prevent native close behavior - we'll handle it
event.preventDefault();
if (!this._config.keyboard) {
this._triggerBackdropTransition();
return;
}
EventHandler.trigger(this._element, EVENT_CANCEL);
this.hide();
});
// Handle Escape key for non-modal dialogs (native cancel doesn't fire for show())
EventHandler.on(this._element, 'keydown', event => {
if (event.key !== 'Escape' || this._config.modal) {
return;
}
event.preventDefault();
if (!this._config.keyboard) {
return;
}
EventHandler.trigger(this._element, EVENT_CANCEL);
this.hide();
});
// Handle backdrop clicks (only applies to modal dialogs)
// Native <dialog> fires click on the dialog element when backdrop is clicked
EventHandler.on(this._element, 'click', event => {
// Only handle clicks directly on the dialog (backdrop area)
// Non-modal dialogs don't have a backdrop
if (event.target !== this._element || !this._config.modal) {
return;
}
if (this._config.backdrop === 'static') {
this._triggerBackdropTransition();
return;
}
// Default: click backdrop to dismiss
this.hide();
});
}
}
/**
* Data API implementation
*/
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
const target = SelectorEngine.getElementFromSelector(this);
if (['A', 'AREA'].includes(this.tagName)) {
event.preventDefault();
}
EventHandler.one(target, EVENT_SHOW, showEvent => {
if (showEvent.defaultPrevented) {
return;
}
EventHandler.one(target, EVENT_HIDDEN, () => {
if (index_js.isVisible(this)) {
this.focus();
}
});
});
// Get config from trigger's data attributes
const config = Manipulator.getDataAttributes(this);
// Check if trigger is inside an open dialog
const currentDialog = this.closest('dialog[open]');
const shouldSwap = currentDialog && currentDialog !== target;
if (shouldSwap) {
// Open new dialog first (its backdrop appears over current)
const newDialog = Dialog.getOrCreateInstance(target, config);
newDialog.show(this);
// Close the current dialog (no backdrop flash since new one is already open)
const currentInstance = Dialog.getInstance(currentDialog);
if (currentInstance) {
currentInstance.hide();
}
return;
}
const data = Dialog.getOrCreateInstance(target, config);
data.toggle(this);
});
componentFunctions_js.enableDismissTrigger(Dialog);
return Dialog;
}));
//# sourceMappingURL=dialog.js.map
-1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap data.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+31 -8
View File
@@ -1,13 +1,13 @@
/*!
* Bootstrap event-handler.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
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';
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';
/**
* --------------------------------------------------------------------------
@@ -16,6 +16,7 @@
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
@@ -184,11 +185,33 @@
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: true,
bubbles,
cancelable: true
}), args);
element.dispatchEvent(evt);
if (defaultPrevented) {
evt.preventDefault();
}
if (nativeDispatch) {
element.dispatchEvent(evt);
}
if (evt.defaultPrevented && jQueryEvent) {
jQueryEvent.preventDefault();
}
return evt;
}
};
@@ -196,7 +219,7 @@
for (const [key, value] of Object.entries(meta)) {
try {
obj[key] = value;
} catch {
} catch (_unused) {
Object.defineProperty(obj, key, {
configurable: true,
get() {
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap manipulator.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
@@ -34,7 +34,7 @@
}
try {
return JSON.parse(decodeURIComponent(value));
} catch {
} catch (_unused) {
return value;
}
}
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"manipulator.js","sources":["../../src/dom/manipulator.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n"],"names":["normalizeData","value","Number","toString","JSON","parse","decodeURIComponent","normalizeDataKey","key","replace","chr","toLowerCase","Manipulator","setDataAttribute","element","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","Object","keys","dataset","filter","startsWith","pureKey","charAt","slice","getDataAttribute","getAttribute"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;EAEA,SAASA,aAAaA,CAACC,KAAK,EAAE;IAC5B,IAAIA,KAAK,KAAK,MAAM,EAAE;EACpB,IAAA,OAAO,IAAI;EACb,EAAA;IAEA,IAAIA,KAAK,KAAK,OAAO,EAAE;EACrB,IAAA,OAAO,KAAK;EACd,EAAA;IAEA,IAAIA,KAAK,KAAKC,MAAM,CAACD,KAAK,CAAC,CAACE,QAAQ,EAAE,EAAE;MACtC,OAAOD,MAAM,CAACD,KAAK,CAAC;EACtB,EAAA;EAEA,EAAA,IAAIA,KAAK,KAAK,EAAE,IAAIA,KAAK,KAAK,MAAM,EAAE;EACpC,IAAA,OAAO,IAAI;EACb,EAAA;EAEA,EAAA,IAAI,OAAOA,KAAK,KAAK,QAAQ,EAAE;EAC7B,IAAA,OAAOA,KAAK;EACd,EAAA;IAEA,IAAI;MACF,OAAOG,IAAI,CAACC,KAAK,CAACC,kBAAkB,CAACL,KAAK,CAAC,CAAC;EAC9C,EAAA,CAAC,CAAC,MAAM;EACN,IAAA,OAAOA,KAAK;EACd,EAAA;EACF;EAEA,SAASM,gBAAgBA,CAACC,GAAG,EAAE;EAC7B,EAAA,OAAOA,GAAG,CAACC,OAAO,CAAC,QAAQ,EAAEC,GAAG,IAAI,CAAA,CAAA,EAAIA,GAAG,CAACC,WAAW,EAAE,EAAE,CAAC;EAC9D;AAEA,QAAMC,WAAW,GAAG;EAClBC,EAAAA,gBAAgBA,CAACC,OAAO,EAAEN,GAAG,EAAEP,KAAK,EAAE;MACpCa,OAAO,CAACC,YAAY,CAAC,CAAA,QAAA,EAAWR,gBAAgB,CAACC,GAAG,CAAC,CAAA,CAAE,EAAEP,KAAK,CAAC;IACjE,CAAC;EAEDe,EAAAA,mBAAmBA,CAACF,OAAO,EAAEN,GAAG,EAAE;MAChCM,OAAO,CAACG,eAAe,CAAC,CAAA,QAAA,EAAWV,gBAAgB,CAACC,GAAG,CAAC,CAAA,CAAE,CAAC;IAC7D,CAAC;IAEDU,iBAAiBA,CAACJ,OAAO,EAAE;MACzB,IAAI,CAACA,OAAO,EAAE;EACZ,MAAA,OAAO,EAAE;EACX,IAAA;MAEA,MAAMK,UAAU,GAAG,EAAE;EACrB,IAAA,MAAMC,MAAM,GAAGC,MAAM,CAACC,IAAI,CAACR,OAAO,CAACS,OAAO,CAAC,CAACC,MAAM,CAAChB,GAAG,IAAIA,GAAG,CAACiB,UAAU,CAAC,IAAI,CAAC,IAAI,CAACjB,GAAG,CAACiB,UAAU,CAAC,UAAU,CAAC,CAAC;EAE9G,IAAA,KAAK,MAAMjB,GAAG,IAAIY,MAAM,EAAE;QACxB,IAAIM,OAAO,GAAGlB,GAAG,CAACC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;EACpCiB,MAAAA,OAAO,GAAGA,OAAO,CAACC,MAAM,CAAC,CAAC,CAAC,CAAChB,WAAW,EAAE,GAAGe,OAAO,CAACE,KAAK,CAAC,CAAC,CAAC;EAC5DT,MAAAA,UAAU,CAACO,OAAO,CAAC,GAAG1B,aAAa,CAACc,OAAO,CAACS,OAAO,CAACf,GAAG,CAAC,CAAC;EAC3D,IAAA;EAEA,IAAA,OAAOW,UAAU;IACnB,CAAC;EAEDU,EAAAA,gBAAgBA,CAACf,OAAO,EAAEN,GAAG,EAAE;EAC7B,IAAA,OAAOR,aAAa,CAACc,OAAO,CAACgB,YAAY,CAAC,CAAA,QAAA,EAAWvB,gBAAgB,CAACC,GAAG,CAAC,CAAA,CAAE,CAAC,CAAC;EAChF,EAAA;EACF;;;;;;;;"}
{"version":3,"file":"manipulator.js","sources":["../../src/dom/manipulator.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true\n }\n\n if (value === 'false') {\n return false\n }\n\n if (value === Number(value).toString()) {\n return Number(value)\n }\n\n if (value === '' || value === 'null') {\n return null\n }\n\n if (typeof value !== 'string') {\n return value\n }\n\n try {\n return JSON.parse(decodeURIComponent(value))\n } catch {\n return value\n }\n}\n\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`)\n}\n\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)\n },\n\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`)\n },\n\n getDataAttributes(element) {\n if (!element) {\n return {}\n }\n\n const attributes = {}\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))\n\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '')\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1)\n attributes[pureKey] = normalizeData(element.dataset[key])\n }\n\n return attributes\n },\n\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`))\n }\n}\n\nexport default Manipulator\n"],"names":["normalizeData","value","Number","toString","JSON","parse","decodeURIComponent","_unused","normalizeDataKey","key","replace","chr","toLowerCase","Manipulator","setDataAttribute","element","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","Object","keys","dataset","filter","startsWith","pureKey","charAt","slice","getDataAttribute","getAttribute"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;EAEA,SAASA,aAAaA,CAACC,KAAK,EAAE;IAC5B,IAAIA,KAAK,KAAK,MAAM,EAAE;EACpB,IAAA,OAAO,IAAI;EACb,EAAA;IAEA,IAAIA,KAAK,KAAK,OAAO,EAAE;EACrB,IAAA,OAAO,KAAK;EACd,EAAA;IAEA,IAAIA,KAAK,KAAKC,MAAM,CAACD,KAAK,CAAC,CAACE,QAAQ,EAAE,EAAE;MACtC,OAAOD,MAAM,CAACD,KAAK,CAAC;EACtB,EAAA;EAEA,EAAA,IAAIA,KAAK,KAAK,EAAE,IAAIA,KAAK,KAAK,MAAM,EAAE;EACpC,IAAA,OAAO,IAAI;EACb,EAAA;EAEA,EAAA,IAAI,OAAOA,KAAK,KAAK,QAAQ,EAAE;EAC7B,IAAA,OAAOA,KAAK;EACd,EAAA;IAEA,IAAI;MACF,OAAOG,IAAI,CAACC,KAAK,CAACC,kBAAkB,CAACL,KAAK,CAAC,CAAC;IAC9C,CAAC,CAAC,OAAAM,OAAA,EAAM;EACN,IAAA,OAAON,KAAK;EACd,EAAA;EACF;EAEA,SAASO,gBAAgBA,CAACC,GAAG,EAAE;EAC7B,EAAA,OAAOA,GAAG,CAACC,OAAO,CAAC,QAAQ,EAAEC,GAAG,IAAI,CAAA,CAAA,EAAIA,GAAG,CAACC,WAAW,EAAE,EAAE,CAAC;EAC9D;AAEA,QAAMC,WAAW,GAAG;EAClBC,EAAAA,gBAAgBA,CAACC,OAAO,EAAEN,GAAG,EAAER,KAAK,EAAE;MACpCc,OAAO,CAACC,YAAY,CAAC,CAAA,QAAA,EAAWR,gBAAgB,CAACC,GAAG,CAAC,CAAA,CAAE,EAAER,KAAK,CAAC;IACjE,CAAC;EAEDgB,EAAAA,mBAAmBA,CAACF,OAAO,EAAEN,GAAG,EAAE;MAChCM,OAAO,CAACG,eAAe,CAAC,CAAA,QAAA,EAAWV,gBAAgB,CAACC,GAAG,CAAC,CAAA,CAAE,CAAC;IAC7D,CAAC;IAEDU,iBAAiBA,CAACJ,OAAO,EAAE;MACzB,IAAI,CAACA,OAAO,EAAE;EACZ,MAAA,OAAO,EAAE;EACX,IAAA;MAEA,MAAMK,UAAU,GAAG,EAAE;EACrB,IAAA,MAAMC,MAAM,GAAGC,MAAM,CAACC,IAAI,CAACR,OAAO,CAACS,OAAO,CAAC,CAACC,MAAM,CAAChB,GAAG,IAAIA,GAAG,CAACiB,UAAU,CAAC,IAAI,CAAC,IAAI,CAACjB,GAAG,CAACiB,UAAU,CAAC,UAAU,CAAC,CAAC;EAE9G,IAAA,KAAK,MAAMjB,GAAG,IAAIY,MAAM,EAAE;QACxB,IAAIM,OAAO,GAAGlB,GAAG,CAACC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;EACpCiB,MAAAA,OAAO,GAAGA,OAAO,CAACC,MAAM,CAAC,CAAC,CAAC,CAAChB,WAAW,EAAE,GAAGe,OAAO,CAACE,KAAK,CAAC,CAAC,CAAC;EAC5DT,MAAAA,UAAU,CAACO,OAAO,CAAC,GAAG3B,aAAa,CAACe,OAAO,CAACS,OAAO,CAACf,GAAG,CAAC,CAAC;EAC3D,IAAA;EAEA,IAAA,OAAOW,UAAU;IACnB,CAAC;EAEDU,EAAAA,gBAAgBA,CAACf,OAAO,EAAEN,GAAG,EAAE;EAC7B,IAAA,OAAOT,aAAa,CAACe,OAAO,CAACgB,YAAY,CAAC,CAAA,QAAA,EAAWvB,gBAAgB,CAACC,GAAG,CAAC,CAAA,CAAE,CAAC,CAAC;EAChF,EAAA;EACF;;;;;;;;"}
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap selector-engine.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+140 -603
View File
@@ -1,13 +1,32 @@
/*!
* Bootstrap dropdown.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@floating-ui/dom'), require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./util/index.js'), require('./util/floating-ui.js')) :
typeof define === 'function' && define.amd ? define(['@floating-ui/dom', './base-component', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/index', './util/floating-ui'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Dropdown = factory(global["@floating-ui/dom"], global.BaseComponent, global.EventHandler, global.Manipulator, global.SelectorEngine, global.Index, global.FloatingUi));
})(this, (function (dom, BaseComponent, EventHandler, Manipulator, SelectorEngine, index_js, floatingUi_js) { 'use strict';
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@popperjs/core'), require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./util/index.js')) :
typeof define === 'function' && define.amd ? define(['@popperjs/core', './base-component', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/index'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Dropdown = factory(global["@popperjs/core"], global.BaseComponent, global.EventHandler, global.Manipulator, global.SelectorEngine, global.Index));
})(this, (function (Popper, BaseComponent, EventHandler, Manipulator, SelectorEngine, index_js) { 'use strict';
function _interopNamespaceDefault(e) {
const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });
if (e) {
for (const k in e) {
if (k !== 'default') {
const d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: () => e[k]
});
}
}
}
n.default = e;
return Object.freeze(n);
}
const Popper__namespace = /*#__PURE__*/_interopNamespaceDefault(Popper);
/**
* --------------------------------------------------------------------------
@@ -29,16 +48,8 @@
const TAB_KEY = 'Tab';
const ARROW_UP_KEY = 'ArrowUp';
const ARROW_DOWN_KEY = 'ArrowDown';
const ARROW_LEFT_KEY = 'ArrowLeft';
const ARROW_RIGHT_KEY = 'ArrowRight';
const HOME_KEY = 'Home';
const END_KEY = 'End';
const ENTER_KEY = 'Enter';
const SPACE_KEY = ' ';
const RIGHT_MOUSE_BUTTON = 2;
const RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button
// Hover intent delay (ms) - grace period before closing submenu
const SUBMENU_CLOSE_DELAY = 100;
const EVENT_HIDE = `hide${EVENT_KEY}`;
const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
const EVENT_SHOW = `show${EVENT_KEY}`;
@@ -47,54 +58,40 @@
const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`;
const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`;
const CLASS_NAME_SHOW = 'show';
const CLASS_NAME_DROPUP = 'dropup';
const CLASS_NAME_DROPEND = 'dropend';
const CLASS_NAME_DROPSTART = 'dropstart';
const CLASS_NAME_DROPUP_CENTER = 'dropup-center';
const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center';
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)';
const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`;
const SELECTOR_MENU = '.dropdown-menu';
const SELECTOR_SUBMENU = '.dropdown-submenu';
const SELECTOR_SUBMENU_TOGGLE = '.dropdown-submenu > .dropdown-item';
const SELECTOR_NAVBAR = '.navbar';
const SELECTOR_NAVBAR_NAV = '.navbar-nav';
const SELECTOR_VISIBLE_ITEMS = '.dropdown-item:not(.disabled):not(:disabled)';
// Default logical placement (uses start/end which get resolved to left/right based on RTL)
const DEFAULT_PLACEMENT = 'bottom-start';
const SUBMENU_PLACEMENT = 'end-start';
// Resolve logical placement (start/end) to physical (left/right) based on RTL
const resolveLogicalPlacement = placement => {
if (index_js.isRTL()) {
// RTL: start → right, end → left
return placement.replace(/^start(?=-|$)/, 'right').replace(/^end(?=-|$)/, 'left');
}
// LTR: start → left, end → right
return placement.replace(/^start(?=-|$)/, 'left').replace(/^end(?=-|$)/, 'right');
};
// Helper for barycentric coordinate calculation (point in triangle check)
const triangleSign = (p1, p2, p3) => (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)';
const PLACEMENT_TOP = index_js.isRTL() ? 'top-end' : 'top-start';
const PLACEMENT_TOPEND = index_js.isRTL() ? 'top-start' : 'top-end';
const PLACEMENT_BOTTOM = index_js.isRTL() ? 'bottom-end' : 'bottom-start';
const PLACEMENT_BOTTOMEND = index_js.isRTL() ? 'bottom-start' : 'bottom-end';
const PLACEMENT_RIGHT = index_js.isRTL() ? 'left-start' : 'right-start';
const PLACEMENT_LEFT = index_js.isRTL() ? 'right-start' : 'left-start';
const PLACEMENT_TOPCENTER = 'top';
const PLACEMENT_BOTTOMCENTER = 'bottom';
const Default = {
autoClose: true,
boundary: 'clippingParents',
display: 'dynamic',
offset: [0, 2],
floatingConfig: null,
placement: DEFAULT_PLACEMENT,
reference: 'toggle',
// Submenu options
submenuTrigger: 'both',
// 'click', 'hover', or 'both'
submenuDelay: SUBMENU_CLOSE_DELAY
popperConfig: null,
reference: 'toggle'
};
const DefaultType = {
autoClose: '(boolean|string)',
boundary: '(string|element)',
display: 'string',
offset: '(array|string|function)',
floatingConfig: '(null|object|function)',
placement: 'string',
reference: '(string|element|object)',
submenuTrigger: 'string',
submenuDelay: 'number'
popperConfig: '(null|object|function)',
reference: '(string|element|object)'
};
/**
@@ -103,27 +100,12 @@
class Dropdown extends BaseComponent {
constructor(element, config) {
if (typeof dom.computePosition === 'undefined') {
throw new TypeError('Bootstrap\'s dropdowns require Floating UI (https://floating-ui.com)');
}
super(element, config);
this._floatingCleanup = null;
this._mediaQueryListeners = [];
this._responsivePlacements = null;
this._popper = null;
this._parent = this._element.parentNode; // dropdown wrapper
this._isSubmenu = this._parent.classList.contains('dropdown-submenu');
this._openSubmenus = new Map(); // Map of submenu element -> cleanup function
this._submenuCloseTimeouts = new Map(); // Map of submenu element -> timeout ID
this._hoverIntentData = null; // For safe triangle calculation
// TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/
this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent);
// Parse responsive placements on init
this._parseResponsivePlacements();
// Set up submenu event listeners
this._setupSubmenuListeners();
this._inNavbar = this._detectNavbar();
}
// Getters
@@ -152,7 +134,7 @@
if (showEvent.defaultPrevented) {
return;
}
this._createFloating();
this._createPopper();
// If this is a touch-enabled device we add extra
// empty mouseover listeners to the body's immediate children;
@@ -164,10 +146,9 @@
}
}
this._element.focus();
this._element.setAttribute('aria-expanded', 'true');
this._element.setAttribute('aria-expanded', true);
this._menu.classList.add(CLASS_NAME_SHOW);
this._element.classList.add(CLASS_NAME_SHOW);
this._parent.classList.add(CLASS_NAME_SHOW);
EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget);
}
hide() {
@@ -180,15 +161,15 @@
this._completeHide(relatedTarget);
}
dispose() {
this._disposeFloating();
this._disposeMediaQueryListeners();
this._closeAllSubmenus();
this._clearAllSubmenuTimeouts();
if (this._popper) {
this._popper.destroy();
}
super.dispose();
}
update() {
if (this._floatingCleanup) {
this._updateFloatingPosition();
this._inNavbar = this._detectNavbar();
if (this._popper) {
this._popper.update();
}
}
@@ -199,9 +180,6 @@
return;
}
// Close all open submenus first
this._closeAllSubmenus();
// If this is a touch-enabled device we remove the extra
// empty mouseover listeners we added for iOS support
if ('ontouchstart' in document.documentElement) {
@@ -209,27 +187,26 @@
EventHandler.off(element, 'mouseover', index_js.noop);
}
}
this._disposeFloating();
if (this._popper) {
this._popper.destroy();
}
this._menu.classList.remove(CLASS_NAME_SHOW);
this._element.classList.remove(CLASS_NAME_SHOW);
this._parent.classList.remove(CLASS_NAME_SHOW);
this._element.setAttribute('aria-expanded', 'false');
Manipulator.removeDataAttribute(this._menu, 'placement');
Manipulator.removeDataAttribute(this._menu, 'display');
Manipulator.removeDataAttribute(this._menu, 'popper');
EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget);
}
_getConfig(config) {
config = super._getConfig(config);
if (typeof config.reference === 'object' && !index_js.isElement(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') {
// Floating UI virtual elements require a getBoundingClientRect method
// Popper virtual elements require a getBoundingClientRect method
throw new TypeError(`${NAME.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);
}
return config;
}
_createFloating() {
if (this._config.display === 'static') {
Manipulator.setDataAttribute(this._menu, 'display', 'static');
return;
_createPopper() {
if (typeof Popper__namespace === 'undefined') {
throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org/docs/v2/)');
}
let referenceElement = this._element;
if (this._config.reference === 'parent') {
@@ -239,430 +216,83 @@
} else if (typeof this._config.reference === 'object') {
referenceElement = this._config.reference;
}
// Initial position update
this._updateFloatingPosition(referenceElement);
// Set up auto-update for scroll/resize
this._floatingCleanup = dom.autoUpdate(referenceElement, this._menu, () => this._updateFloatingPosition(referenceElement));
}
async _updateFloatingPosition(referenceElement = null) {
if (!this._menu) {
return;
}
if (!referenceElement) {
if (this._config.reference === 'parent') {
referenceElement = this._parent;
} else if (index_js.isElement(this._config.reference)) {
referenceElement = index_js.getElement(this._config.reference);
} else if (typeof this._config.reference === 'object') {
referenceElement = this._config.reference;
} else {
referenceElement = this._element;
}
}
const placement = this._getPlacement();
const middleware = this._getFloatingMiddleware();
const floatingConfig = this._getFloatingConfig(placement, middleware);
await this._applyFloatingPosition(referenceElement, this._menu, floatingConfig.placement, floatingConfig.middleware);
const popperConfig = this._getPopperConfig();
this._popper = Popper__namespace.createPopper(referenceElement, this._menu, popperConfig);
}
_isShown() {
return this._menu.classList.contains(CLASS_NAME_SHOW);
}
_getPlacement() {
// If we have responsive placements, find the appropriate one for current viewport
const placement = this._responsivePlacements ? floatingUi_js.getResponsivePlacement(this._responsivePlacements, DEFAULT_PLACEMENT) : this._config.placement;
// Resolve logical placements (start/end) to physical (left/right) based on RTL
return resolveLogicalPlacement(placement);
}
_parseResponsivePlacements() {
this._responsivePlacements = floatingUi_js.parseResponsivePlacement(this._config.placement, DEFAULT_PLACEMENT);
if (this._responsivePlacements) {
this._setupMediaQueryListeners();
const parentDropdown = this._parent;
if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {
return PLACEMENT_RIGHT;
}
if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {
return PLACEMENT_LEFT;
}
if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {
return PLACEMENT_TOPCENTER;
}
if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {
return PLACEMENT_BOTTOMCENTER;
}
// We need to trim the value because custom properties can also include spaces
const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end';
if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {
return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP;
}
return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM;
}
_setupMediaQueryListeners() {
this._disposeMediaQueryListeners();
this._mediaQueryListeners = floatingUi_js.createBreakpointListeners(() => {
if (this._isShown()) {
this._updateFloatingPosition();
}
});
}
_disposeMediaQueryListeners() {
floatingUi_js.disposeBreakpointListeners(this._mediaQueryListeners);
this._mediaQueryListeners = [];
_detectNavbar() {
return this._element.closest(SELECTOR_NAVBAR) !== null;
}
_getOffset() {
const {
offset: offsetConfig
offset
} = this._config;
if (typeof offsetConfig === 'string') {
return offsetConfig.split(',').map(value => Number.parseInt(value, 10));
if (typeof offset === 'string') {
return offset.split(',').map(value => Number.parseInt(value, 10));
}
if (typeof offsetConfig === 'function') {
// Floating UI passes different args, adapt the interface for offset function callbacks
return ({
placement,
rects
}) => {
const result = offsetConfig({
placement,
reference: rects.reference,
floating: rects.floating
}, this._element);
return result;
};
if (typeof offset === 'function') {
return popperData => offset(popperData, this._element);
}
return offsetConfig;
return offset;
}
_getFloatingMiddleware() {
const offsetValue = this._getOffset();
const middleware = [
// Offset middleware - handles distance from reference
dom.offset(typeof offsetValue === 'function' ? offsetValue : {
mainAxis: offsetValue[1] || 0,
crossAxis: offsetValue[0] || 0
}),
// Flip middleware - handles fallback placements
dom.flip({
fallbackPlacements: this._getFallbackPlacements()
}),
// Shift middleware - prevents overflow
dom.shift({
boundary: this._config.boundary === 'clippingParents' ? 'clippingAncestors' : this._config.boundary
})];
return middleware;
}
_getFallbackPlacements() {
// Get appropriate fallback placements based on current placement
// Fallbacks should preserve alignment (start/end) when possible
const placement = this._getPlacement();
_getPopperConfig() {
const defaultBsPopperConfig = {
placement: this._getPlacement(),
modifiers: [{
name: 'preventOverflow',
options: {
boundary: this._config.boundary
}
}, {
name: 'offset',
options: {
offset: this._getOffset()
}
}]
};
// Handle all possible Floating UI placements
const fallbackMap = {
bottom: ['top', 'bottom-start', 'bottom-end', 'top-start', 'top-end'],
'bottom-start': ['top-start', 'bottom-end', 'top-end'],
'bottom-end': ['top-end', 'bottom-start', 'top-start'],
top: ['bottom', 'top-start', 'top-end', 'bottom-start', 'bottom-end'],
'top-start': ['bottom-start', 'top-end', 'bottom-end'],
'top-end': ['bottom-end', 'top-start', 'bottom-start'],
right: ['left', 'right-start', 'right-end', 'left-start', 'left-end'],
'right-start': ['left-start', 'right-end', 'left-end', 'top-start', 'bottom-start'],
'right-end': ['left-end', 'right-start', 'left-start', 'top-end', 'bottom-end'],
left: ['right', 'left-start', 'left-end', 'right-start', 'right-end'],
'left-start': ['right-start', 'left-end', 'right-end', 'top-start', 'bottom-start'],
'left-end': ['right-end', 'left-start', 'right-start', 'top-end', 'bottom-end']
};
return fallbackMap[placement] || ['top', 'bottom', 'right', 'left'];
}
_getFloatingConfig(placement, middleware) {
const defaultConfig = {
placement,
middleware
};
// Disable Popper if we have a static display or Dropdown is in Navbar
if (this._inNavbar || this._config.display === 'static') {
Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // TODO: v6 remove
defaultBsPopperConfig.modifiers = [{
name: 'applyStyles',
enabled: false
}];
}
return {
...defaultConfig,
...index_js.execute(this._config.floatingConfig, [undefined, defaultConfig])
...defaultBsPopperConfig,
...index_js.execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])
};
}
_disposeFloating() {
if (this._floatingCleanup) {
this._floatingCleanup();
this._floatingCleanup = null;
}
}
// Shared helper for positioning any floating element
async _applyFloatingPosition(reference, floating, placement, middleware) {
if (!floating.isConnected) {
return null;
}
const {
x,
y,
placement: finalPlacement
} = await dom.computePosition(reference, floating, {
placement,
middleware
});
if (!floating.isConnected) {
return null;
}
Object.assign(floating.style, {
position: 'absolute',
left: `${x}px`,
top: `${y}px`,
margin: '0'
});
Manipulator.setDataAttribute(floating, 'placement', finalPlacement);
return finalPlacement;
}
// -------------------------------------------------------------------------
// Submenu handling
// -------------------------------------------------------------------------
_setupSubmenuListeners() {
// Set up hover listeners for submenu triggers
if (this._config.submenuTrigger === 'hover' || this._config.submenuTrigger === 'both') {
EventHandler.on(this._menu, 'mouseenter', SELECTOR_SUBMENU_TOGGLE, event => {
this._onSubmenuTriggerEnter(event);
});
EventHandler.on(this._menu, 'mouseleave', SELECTOR_SUBMENU, event => {
this._onSubmenuLeave(event);
});
// Track mouse movement for safe triangle calculation
EventHandler.on(this._menu, 'mousemove', event => {
this._trackMousePosition(event);
});
}
// Set up click listener for submenu triggers
if (this._config.submenuTrigger === 'click' || this._config.submenuTrigger === 'both') {
EventHandler.on(this._menu, 'click', SELECTOR_SUBMENU_TOGGLE, event => {
this._onSubmenuTriggerClick(event);
});
}
}
_onSubmenuTriggerEnter(event) {
const trigger = event.target.closest(SELECTOR_SUBMENU_TOGGLE);
if (!trigger) {
return;
}
const submenuWrapper = trigger.closest(SELECTOR_SUBMENU);
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
if (!submenu) {
return;
}
// Cancel any pending close timeout for this submenu
this._cancelSubmenuCloseTimeout(submenu);
// Close other open submenus at the same level
this._closeSiblingSubmenus(submenuWrapper);
// Open this submenu
this._openSubmenu(trigger, submenu, submenuWrapper);
}
_onSubmenuLeave(event) {
const submenuWrapper = event.target.closest(SELECTOR_SUBMENU);
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
if (!submenu || !this._openSubmenus.has(submenu)) {
return;
}
// Check if we're moving toward the submenu (safe triangle)
if (this._isMovingTowardSubmenu(event, submenu)) {
return;
}
// Schedule submenu close with delay
this._scheduleSubmenuClose(submenu, submenuWrapper);
}
_onSubmenuTriggerClick(event) {
const trigger = event.target.closest(SELECTOR_SUBMENU_TOGGLE);
if (!trigger) {
return;
}
event.preventDefault();
event.stopPropagation();
const submenuWrapper = trigger.closest(SELECTOR_SUBMENU);
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
if (!submenu) {
return;
}
// Toggle submenu
if (this._openSubmenus.has(submenu)) {
this._closeSubmenu(submenu, submenuWrapper);
} else {
this._closeSiblingSubmenus(submenuWrapper);
this._openSubmenu(trigger, submenu, submenuWrapper);
}
}
_openSubmenu(trigger, submenu, submenuWrapper) {
if (this._openSubmenus.has(submenu)) {
return;
}
// Set ARIA attributes
trigger.setAttribute('aria-expanded', 'true');
trigger.setAttribute('aria-haspopup', 'true');
// Position and show submenu
submenu.classList.add(CLASS_NAME_SHOW);
submenuWrapper.classList.add(CLASS_NAME_SHOW);
// Set up Floating UI positioning for submenu
const cleanup = this._createSubmenuFloating(trigger, submenu, submenuWrapper);
this._openSubmenus.set(submenu, cleanup);
// Set up mouseenter on submenu to cancel close timeout
EventHandler.on(submenu, 'mouseenter', () => {
this._cancelSubmenuCloseTimeout(submenu);
});
}
_closeSubmenu(submenu, submenuWrapper) {
if (!this._openSubmenus.has(submenu)) {
return;
}
// Close any nested submenus first
const nestedSubmenus = SelectorEngine.find(`${SELECTOR_SUBMENU} ${SELECTOR_MENU}.${CLASS_NAME_SHOW}`, submenu);
for (const nested of nestedSubmenus) {
const nestedWrapper = nested.closest(SELECTOR_SUBMENU);
this._closeSubmenu(nested, nestedWrapper);
}
// Get the trigger
const trigger = SelectorEngine.findOne(SELECTOR_SUBMENU_TOGGLE, submenuWrapper);
// Clean up Floating UI
const cleanup = this._openSubmenus.get(submenu);
if (cleanup) {
cleanup();
}
this._openSubmenus.delete(submenu);
// Remove event listeners
EventHandler.off(submenu, 'mouseenter');
// Update ARIA and visibility
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
submenu.classList.remove(CLASS_NAME_SHOW);
submenuWrapper.classList.remove(CLASS_NAME_SHOW);
// Clear inline styles
submenu.style.position = '';
submenu.style.left = '';
submenu.style.top = '';
submenu.style.margin = '';
}
_closeAllSubmenus() {
for (const [submenu] of this._openSubmenus) {
const submenuWrapper = submenu.closest(SELECTOR_SUBMENU);
this._closeSubmenu(submenu, submenuWrapper);
}
}
_closeSiblingSubmenus(currentSubmenuWrapper) {
// Find all sibling submenu wrappers and close their menus
const parent = currentSubmenuWrapper.parentNode;
const siblingSubmenus = SelectorEngine.find(`${SELECTOR_SUBMENU} > ${SELECTOR_MENU}.${CLASS_NAME_SHOW}`, parent);
for (const siblingMenu of siblingSubmenus) {
const siblingWrapper = siblingMenu.closest(SELECTOR_SUBMENU);
if (siblingWrapper !== currentSubmenuWrapper) {
this._closeSubmenu(siblingMenu, siblingWrapper);
}
}
}
_createSubmenuFloating(trigger, submenu, submenuWrapper) {
const referenceElement = submenuWrapper;
const placement = resolveLogicalPlacement(SUBMENU_PLACEMENT);
const middleware = [dom.offset({
mainAxis: 0,
crossAxis: -4
}), dom.flip({
fallbackPlacements: [resolveLogicalPlacement('start-start'), resolveLogicalPlacement('end-end'), resolveLogicalPlacement('start-end')]
}), dom.shift({
padding: 8
})];
const updatePosition = () => this._applyFloatingPosition(referenceElement, submenu, placement, middleware);
updatePosition();
return dom.autoUpdate(referenceElement, submenu, updatePosition);
}
_scheduleSubmenuClose(submenu, submenuWrapper) {
this._cancelSubmenuCloseTimeout(submenu);
const timeoutId = setTimeout(() => {
this._closeSubmenu(submenu, submenuWrapper);
this._submenuCloseTimeouts.delete(submenu);
}, this._config.submenuDelay);
this._submenuCloseTimeouts.set(submenu, timeoutId);
}
_cancelSubmenuCloseTimeout(submenu) {
const timeoutId = this._submenuCloseTimeouts.get(submenu);
if (timeoutId) {
clearTimeout(timeoutId);
this._submenuCloseTimeouts.delete(submenu);
}
}
_clearAllSubmenuTimeouts() {
for (const timeoutId of this._submenuCloseTimeouts.values()) {
clearTimeout(timeoutId);
}
this._submenuCloseTimeouts.clear();
}
// -------------------------------------------------------------------------
// Hover intent / Safe triangle
// -------------------------------------------------------------------------
_trackMousePosition(event) {
this._hoverIntentData = {
x: event.clientX,
y: event.clientY,
timestamp: Date.now()
};
}
_isMovingTowardSubmenu(event, submenu) {
if (!this._hoverIntentData) {
return false;
}
const submenuRect = submenu.getBoundingClientRect();
const currentPos = {
x: event.clientX,
y: event.clientY
};
const lastPos = {
x: this._hoverIntentData.x,
y: this._hoverIntentData.y
};
// Create a triangle from current position to submenu edges
// The triangle represents the "safe zone" for diagonal movement
const isRtl = index_js.isRTL();
// Determine which edge of the submenu to target based on direction
const targetX = isRtl ? submenuRect.right : submenuRect.left;
const topCorner = {
x: targetX,
y: submenuRect.top
};
const bottomCorner = {
x: targetX,
y: submenuRect.bottom
};
// Check if cursor is moving toward the submenu
// by checking if the current position is within the safe triangle
return this._pointInTriangle(currentPos, lastPos, topCorner, bottomCorner);
}
_pointInTriangle(point, v1, v2, v3) {
// Barycentric coordinate method to check if point is inside triangle
const d1 = triangleSign(point, v1, v2);
const d2 = triangleSign(point, v2, v3);
const d3 = triangleSign(point, v3, v1);
const hasNeg = d1 < 0 || d2 < 0 || d3 < 0;
const hasPos = d1 > 0 || d2 > 0 || d3 > 0;
return !(hasNeg && hasPos);
}
// -------------------------------------------------------------------------
// Keyboard navigation
// -------------------------------------------------------------------------
_selectMenuItem({
key,
target
}) {
// Get items only from the current menu level (not nested submenus)
// If target is inside a menu, use that menu; otherwise use the main menu
const currentMenu = target.closest(SELECTOR_MENU) || this._menu;
const items = SelectorEngine.find(`:scope > li > ${SELECTOR_VISIBLE_ITEMS}, :scope > ${SELECTOR_VISIBLE_ITEMS}`, currentMenu).filter(element => index_js.isVisible(element));
const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => index_js.isVisible(element));
if (!items.length) {
return;
}
@@ -671,88 +301,19 @@
// allow cycling to get the last item in case key equals ARROW_UP_KEY
index_js.getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus();
}
_handleSubmenuKeydown(event) {
const {
key,
target
} = event;
const isRtl = index_js.isRTL();
// Determine the "enter submenu" and "exit submenu" keys based on RTL
const enterKey = isRtl ? ARROW_LEFT_KEY : ARROW_RIGHT_KEY;
const exitKey = isRtl ? ARROW_RIGHT_KEY : ARROW_LEFT_KEY;
// Check if target is a submenu trigger
const submenuWrapper = target.closest(SELECTOR_SUBMENU);
const isSubmenuTrigger = submenuWrapper && target.matches(SELECTOR_SUBMENU_TOGGLE);
// Handle Enter/Space on submenu trigger
if ((key === ENTER_KEY || key === SPACE_KEY) && isSubmenuTrigger) {
event.preventDefault();
event.stopPropagation();
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
if (submenu) {
this._closeSiblingSubmenus(submenuWrapper);
this._openSubmenu(target, submenu, submenuWrapper);
// Focus first item in submenu
requestAnimationFrame(() => {
const firstItem = SelectorEngine.findOne(SELECTOR_VISIBLE_ITEMS, submenu);
if (firstItem) {
firstItem.focus();
}
});
// Static
static jQueryInterface(config) {
return this.each(function () {
const data = Dropdown.getOrCreateInstance(this, config);
if (typeof config !== 'string') {
return;
}
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();
}
});
if (typeof data[config] === 'undefined') {
throw new TypeError(`No method named "${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;
data[config]();
});
}
static clearMenus(event) {
if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY) {
@@ -784,62 +345,32 @@
}
}
static dataApiKeydownHandler(event) {
// If not a relevant key => not a dropdown command
// If not an UP | DOWN | ESCAPE key => not a dropdown command
// If input/textarea && if key is other than ESCAPE => not a dropdown command
const isInput = /input|textarea/i.test(event.target.tagName);
const isEscapeEvent = event.key === ESCAPE_KEY;
const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key);
const isLeftOrRightEvent = [ARROW_LEFT_KEY, ARROW_RIGHT_KEY].includes(event.key);
const isHomeOrEndEvent = [HOME_KEY, END_KEY].includes(event.key);
const isEnterOrSpaceEvent = [ENTER_KEY, SPACE_KEY].includes(event.key);
// Allow Enter/Space only on submenu triggers
const isSubmenuTrigger = event.target.matches(SELECTOR_SUBMENU_TOGGLE);
if (!isUpOrDownEvent && !isEscapeEvent && !isLeftOrRightEvent && !isHomeOrEndEvent && !(isEnterOrSpaceEvent && isSubmenuTrigger)) {
if (!isUpOrDownEvent && !isEscapeEvent) {
return;
}
if (isInput && !isEscapeEvent) {
return;
}
event.preventDefault();
// TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/
const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode);
if (!getToggleButton) {
return;
}
const instance = Dropdown.getOrCreateInstance(getToggleButton);
// Handle submenu navigation first
if ((isLeftOrRightEvent || isHomeOrEndEvent || isEnterOrSpaceEvent && isSubmenuTrigger) && instance._handleSubmenuKeydown(event)) {
return;
}
// Handle Up/Down navigation
if (isUpOrDownEvent) {
event.preventDefault();
event.stopPropagation();
instance.show();
instance._selectMenuItem(event);
return;
}
// Handle Escape
if (isEscapeEvent && instance._isShown()) {
event.preventDefault();
if (instance._isShown()) {
// else is escape and we check if it is shown
event.stopPropagation();
// If in a submenu, close just that submenu
const currentMenu = event.target.closest(SELECTOR_MENU);
const parentSubmenuWrapper = currentMenu?.closest(SELECTOR_SUBMENU);
if (parentSubmenuWrapper && instance._openSubmenus.size > 0) {
const parentTrigger = SelectorEngine.findOne(SELECTOR_SUBMENU_TOGGLE, parentSubmenuWrapper);
instance._closeSubmenu(currentMenu, parentSubmenuWrapper);
if (parentTrigger) {
parentTrigger.focus();
}
return;
}
// Otherwise close the whole dropdown
instance.hide();
getToggleButton.focus();
}
@@ -859,6 +390,12 @@
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
@@ -0,0 +1,320 @@
/*!
* 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