Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7edd5c283e | |||
| 31623677f6 | |||
| fd0630d9bc | |||
| b3f71d921e | |||
| d022106d09 | |||
| fa44eff36f | |||
| 2c21f91acb | |||
| ac67589d41 | |||
| f0c163b917 | |||
| 956de4bbaf | |||
| c8e8d28d29 | |||
| 482b0eb333 | |||
| e5a1ee3d3a | |||
| 21491d1f2f | |||
| 98d6c80cd9 | |||
| 5f910f4a09 | |||
| 484c4357ea | |||
| 334d68b75a | |||
| dbf36bbd01 | |||
| 062e7425b8 | |||
| ae01f064b0 | |||
| c0f88d638a | |||
| ca74fc29fd | |||
| 85adb49027 | |||
| f1edacb405 | |||
| 96491cf5c8 | |||
| ff14a64d2d | |||
| ea8523a4f4 | |||
| 1d3d4339ba | |||
| 37d69e7c38 |
+6
-4
@@ -1,10 +1,12 @@
|
||||
# https://github.com/browserslist/browserslist#readme
|
||||
|
||||
>= 0.5%
|
||||
last 2 major versions
|
||||
not dead
|
||||
Chrome >= 120
|
||||
Firefox >= 121
|
||||
iOS >= 15.6
|
||||
Safari >= 15.6
|
||||
Chrome >= 60
|
||||
Firefox >= 60
|
||||
Firefox ESR
|
||||
iOS >= 12
|
||||
Safari >= 12
|
||||
not Explorer <= 11
|
||||
not kaios <= 2.5 # fix floating label issues in Firefox (see https://github.com/postcss/autoprefixer/issues/1533)
|
||||
|
||||
+13
-13
@@ -2,27 +2,27 @@
|
||||
"files": [
|
||||
{
|
||||
"path": "./dist/css/bootstrap-grid.css",
|
||||
"maxSize": "9.5 kB"
|
||||
"maxSize": "7.75 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/bootstrap-grid.min.css",
|
||||
"maxSize": "8.5 kB"
|
||||
"maxSize": "6.75 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/bootstrap-reboot.css",
|
||||
"maxSize": "5.25 kB"
|
||||
"maxSize": "4.5 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/bootstrap-reboot.min.css",
|
||||
"maxSize": "4.25 kB"
|
||||
"maxSize": "4.5 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/bootstrap-utilities.css",
|
||||
"maxSize": "14.5 kB"
|
||||
"maxSize": "13.0 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/bootstrap-utilities.min.css",
|
||||
"maxSize": "12.75 kB"
|
||||
"maxSize": "12.0 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/bootstrap.css",
|
||||
@@ -30,31 +30,31 @@
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/bootstrap.min.css",
|
||||
"maxSize": "32.5 kB"
|
||||
"maxSize": "32.0 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/bootstrap.bundle.js",
|
||||
"maxSize": "49.75 kB"
|
||||
"maxSize": "43.0 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/bootstrap.bundle.min.js",
|
||||
"maxSize": "26.0 kB"
|
||||
"maxSize": "23.5 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/bootstrap.esm.js",
|
||||
"maxSize": "36.0 kB"
|
||||
"maxSize": "28.0 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/bootstrap.esm.min.js",
|
||||
"maxSize": "22.25 kB"
|
||||
"maxSize": "18.25 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/bootstrap.js",
|
||||
"maxSize": "36.5 kB"
|
||||
"maxSize": "28.75 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/bootstrap.min.js",
|
||||
"maxSize": "19.75 kB"
|
||||
"maxSize": "16.25 kB"
|
||||
}
|
||||
],
|
||||
"ci": {
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
"favicons",
|
||||
"fieldsets",
|
||||
"flexbox",
|
||||
"frontmatter",
|
||||
"fullscreen",
|
||||
"getbootstrap",
|
||||
"Grayscale",
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: "${{ env.NODE }}"
|
||||
cache: npm
|
||||
|
||||
@@ -4,7 +4,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v6-dev
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -26,7 +25,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: "${{ env.NODE }}"
|
||||
cache: npm
|
||||
|
||||
@@ -18,8 +18,7 @@ jobs:
|
||||
name: calibreapp/image-actions
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# allow calibreapp/image-actions to update PRs and commit compressed images
|
||||
contents: write
|
||||
# allow calibreapp/image-actions to update PRs
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Clone repository
|
||||
|
||||
@@ -5,13 +5,11 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- v4-dev
|
||||
- v6-dev
|
||||
- "!dependabot/**"
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- v4-dev
|
||||
- v6-dev
|
||||
- "!dependabot/**"
|
||||
schedule:
|
||||
- cron: "0 2 * * 4"
|
||||
@@ -31,16 +29,16 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
|
||||
uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3
|
||||
with:
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
languages: "javascript"
|
||||
queries: +security-and-quality
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
|
||||
uses: github/codeql-action/autobuild@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
|
||||
uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3
|
||||
with:
|
||||
category: "/language:javascript"
|
||||
|
||||
@@ -4,7 +4,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v6-dev
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -29,7 +28,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run cspell
|
||||
uses: streetsidesoftware/cspell-action@3294df585d3d639e30f3bc019cb11940b9866e95 # v8.0.0
|
||||
uses: streetsidesoftware/cspell-action@dcd03dc3e8a59ec2e360d0c62db517baa0b4bb6d # v7.2.0
|
||||
with:
|
||||
config: ".cspell.json"
|
||||
files: "**/*.{md,mdx}"
|
||||
|
||||
@@ -4,7 +4,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v6-dev
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -26,7 +25,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: "${{ env.NODE }}"
|
||||
cache: npm
|
||||
|
||||
@@ -4,7 +4,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v6-dev
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -26,11 +25,13 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: "${{ env.NODE }}"
|
||||
cache: npm
|
||||
|
||||
- run: java -version
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: npm ci
|
||||
|
||||
@@ -38,10 +39,10 @@ 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
|
||||
uses: JustinBeckwith/linkinator-action@3d5ba091319fa7b0ac14703761eebb7d100e6f6d # v1.11.0
|
||||
with:
|
||||
paths: _site
|
||||
recurse: true
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
if: github.repository == 'twbs/bootstrap'
|
||||
steps:
|
||||
- name: awaiting reply
|
||||
uses: actions-cool/issues-helper@9861779a695cf1898bd984c727f685f351cfc372 # v3.7.2
|
||||
uses: actions-cool/issues-helper@45d75b6cf72bf4f254be6230cb887ad002702491 # v3.6.3
|
||||
with:
|
||||
actions: "close-issues"
|
||||
labels: "awaiting-reply"
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
steps:
|
||||
- name: awaiting reply
|
||||
if: github.event.label.name == 'needs-example'
|
||||
uses: actions-cool/issues-helper@9861779a695cf1898bd984c727f685f351cfc372 # v3.7.2
|
||||
uses: actions-cool/issues-helper@45d75b6cf72bf4f254be6230cb887ad002702491 # v3.6.3
|
||||
with:
|
||||
actions: "create-comment"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -4,7 +4,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v6-dev
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -31,7 +30,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: ${{ env.NODE }}
|
||||
cache: npm
|
||||
|
||||
@@ -4,7 +4,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v6-dev
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -26,7 +25,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: "${{ env.NODE }}"
|
||||
cache: npm
|
||||
|
||||
@@ -4,7 +4,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v6-dev
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
||||
@@ -12,7 +12,7 @@ on:
|
||||
schedule:
|
||||
- cron: '27 12 * * 2'
|
||||
push:
|
||||
branches: [ "main", "v6-dev" ]
|
||||
branches: [ "main" ]
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
|
||||
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@@ -73,6 +73,6 @@ jobs:
|
||||
# Upload the results to GitHub's code scanning dashboard (optional).
|
||||
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
|
||||
uses: github/codeql-action/upload-sarif@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
.cache
|
||||
.DS_Store
|
||||
.idea
|
||||
.nvmrc
|
||||
.project
|
||||
.settings
|
||||
.tmproj
|
||||
|
||||
@@ -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 you’ll 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 you’ll 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
|
||||
|
||||
@@ -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)
|
||||
@@ -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'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"',
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
@@ -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
@@ -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
|
||||
|
||||
Vendored
+3803
-4352
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+4
-4
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+4084
File diff suppressed because it is too large
Load Diff
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+6
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Vendored
+561
-803
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+4
-4
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+598
@@ -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 */
|
||||
+1
File diff suppressed because one or more lines are too long
+6
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Vendored
+5156
-5365
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
-1224
File diff suppressed because it is too large
Load Diff
Vendored
+4
-4
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+5397
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
+6
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+11671
-11655
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+4
-4
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+12021
File diff suppressed because it is too large
Load Diff
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+6
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+2403
-3466
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+671
-1957
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+693
-1963
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
-242
@@ -1,242 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap dialog.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/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
|
||||
Vendored
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -196,7 +196,7 @@
|
||||
for (const [key, value] of Object.entries(meta)) {
|
||||
try {
|
||||
obj[key] = value;
|
||||
} catch {
|
||||
} catch (_unused) {
|
||||
Object.defineProperty(obj, key, {
|
||||
configurable: true,
|
||||
get() {
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -34,7 +34,7 @@
|
||||
}
|
||||
try {
|
||||
return JSON.parse(decodeURIComponent(value));
|
||||
} catch {
|
||||
} catch (_unused) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -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;;;;;;;;"}
|
||||
Vendored
+123
-606
@@ -4,10 +4,29 @@
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@floating-ui/dom'), require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./util/index.js'), require('./util/floating-ui.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['@floating-ui/dom', './base-component', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/index', './util/floating-ui'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Dropdown = factory(global["@floating-ui/dom"], global.BaseComponent, global.EventHandler, global.Manipulator, global.SelectorEngine, global.Index, global.FloatingUi));
|
||||
})(this, (function (dom, BaseComponent, EventHandler, Manipulator, SelectorEngine, index_js, floatingUi_js) { 'use strict';
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@popperjs/core'), require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./util/index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['@popperjs/core', './base-component', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Dropdown = factory(global["@popperjs/core"], global.BaseComponent, global.EventHandler, global.Manipulator, global.SelectorEngine, global.Index));
|
||||
})(this, (function (Popper, BaseComponent, EventHandler, Manipulator, SelectorEngine, index_js) { 'use strict';
|
||||
|
||||
function _interopNamespaceDefault(e) {
|
||||
const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });
|
||||
if (e) {
|
||||
for (const k in e) {
|
||||
if (k !== 'default') {
|
||||
const d = Object.getOwnPropertyDescriptor(e, k);
|
||||
Object.defineProperty(n, k, d.get ? d : {
|
||||
enumerable: true,
|
||||
get: () => e[k]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
n.default = e;
|
||||
return Object.freeze(n);
|
||||
}
|
||||
|
||||
const Popper__namespace = /*#__PURE__*/_interopNamespaceDefault(Popper);
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
@@ -29,16 +48,8 @@
|
||||
const TAB_KEY = 'Tab';
|
||||
const ARROW_UP_KEY = 'ArrowUp';
|
||||
const ARROW_DOWN_KEY = 'ArrowDown';
|
||||
const ARROW_LEFT_KEY = 'ArrowLeft';
|
||||
const ARROW_RIGHT_KEY = 'ArrowRight';
|
||||
const HOME_KEY = 'Home';
|
||||
const END_KEY = 'End';
|
||||
const ENTER_KEY = 'Enter';
|
||||
const SPACE_KEY = ' ';
|
||||
const RIGHT_MOUSE_BUTTON = 2;
|
||||
const RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button
|
||||
|
||||
// Hover intent delay (ms) - grace period before closing submenu
|
||||
const SUBMENU_CLOSE_DELAY = 100;
|
||||
const EVENT_HIDE = `hide${EVENT_KEY}`;
|
||||
const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
|
||||
const EVENT_SHOW = `show${EVENT_KEY}`;
|
||||
@@ -47,54 +58,40 @@
|
||||
const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`;
|
||||
const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`;
|
||||
const CLASS_NAME_SHOW = 'show';
|
||||
const CLASS_NAME_DROPUP = 'dropup';
|
||||
const CLASS_NAME_DROPEND = 'dropend';
|
||||
const CLASS_NAME_DROPSTART = 'dropstart';
|
||||
const CLASS_NAME_DROPUP_CENTER = 'dropup-center';
|
||||
const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center';
|
||||
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)';
|
||||
const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`;
|
||||
const SELECTOR_MENU = '.dropdown-menu';
|
||||
const SELECTOR_SUBMENU = '.dropdown-submenu';
|
||||
const SELECTOR_SUBMENU_TOGGLE = '.dropdown-submenu > .dropdown-item';
|
||||
const SELECTOR_NAVBAR = '.navbar';
|
||||
const SELECTOR_NAVBAR_NAV = '.navbar-nav';
|
||||
const SELECTOR_VISIBLE_ITEMS = '.dropdown-item:not(.disabled):not(:disabled)';
|
||||
|
||||
// Default logical placement (uses start/end which get resolved to left/right based on RTL)
|
||||
const DEFAULT_PLACEMENT = 'bottom-start';
|
||||
const SUBMENU_PLACEMENT = 'end-start';
|
||||
|
||||
// Resolve logical placement (start/end) to physical (left/right) based on RTL
|
||||
const resolveLogicalPlacement = placement => {
|
||||
if (index_js.isRTL()) {
|
||||
// RTL: start → right, end → left
|
||||
return placement.replace(/^start(?=-|$)/, 'right').replace(/^end(?=-|$)/, 'left');
|
||||
}
|
||||
|
||||
// LTR: start → left, end → right
|
||||
return placement.replace(/^start(?=-|$)/, 'left').replace(/^end(?=-|$)/, 'right');
|
||||
};
|
||||
|
||||
// Helper for barycentric coordinate calculation (point in triangle check)
|
||||
const triangleSign = (p1, p2, p3) => (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
|
||||
const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)';
|
||||
const PLACEMENT_TOP = index_js.isRTL() ? 'top-end' : 'top-start';
|
||||
const PLACEMENT_TOPEND = index_js.isRTL() ? 'top-start' : 'top-end';
|
||||
const PLACEMENT_BOTTOM = index_js.isRTL() ? 'bottom-end' : 'bottom-start';
|
||||
const PLACEMENT_BOTTOMEND = index_js.isRTL() ? 'bottom-start' : 'bottom-end';
|
||||
const PLACEMENT_RIGHT = index_js.isRTL() ? 'left-start' : 'right-start';
|
||||
const PLACEMENT_LEFT = index_js.isRTL() ? 'right-start' : 'left-start';
|
||||
const PLACEMENT_TOPCENTER = 'top';
|
||||
const PLACEMENT_BOTTOMCENTER = 'bottom';
|
||||
const Default = {
|
||||
autoClose: true,
|
||||
boundary: 'clippingParents',
|
||||
display: 'dynamic',
|
||||
offset: [0, 2],
|
||||
floatingConfig: null,
|
||||
placement: DEFAULT_PLACEMENT,
|
||||
reference: 'toggle',
|
||||
// Submenu options
|
||||
submenuTrigger: 'both',
|
||||
// 'click', 'hover', or 'both'
|
||||
submenuDelay: SUBMENU_CLOSE_DELAY
|
||||
popperConfig: null,
|
||||
reference: 'toggle'
|
||||
};
|
||||
const DefaultType = {
|
||||
autoClose: '(boolean|string)',
|
||||
boundary: '(string|element)',
|
||||
display: 'string',
|
||||
offset: '(array|string|function)',
|
||||
floatingConfig: '(null|object|function)',
|
||||
placement: 'string',
|
||||
reference: '(string|element|object)',
|
||||
submenuTrigger: 'string',
|
||||
submenuDelay: 'number'
|
||||
popperConfig: '(null|object|function)',
|
||||
reference: '(string|element|object)'
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -103,27 +100,12 @@
|
||||
|
||||
class Dropdown extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
if (typeof dom.computePosition === 'undefined') {
|
||||
throw new TypeError('Bootstrap\'s dropdowns require Floating UI (https://floating-ui.com)');
|
||||
}
|
||||
super(element, config);
|
||||
this._floatingCleanup = null;
|
||||
this._mediaQueryListeners = [];
|
||||
this._responsivePlacements = null;
|
||||
this._popper = null;
|
||||
this._parent = this._element.parentNode; // dropdown wrapper
|
||||
this._isSubmenu = this._parent.classList.contains('dropdown-submenu');
|
||||
this._openSubmenus = new Map(); // Map of submenu element -> cleanup function
|
||||
this._submenuCloseTimeouts = new Map(); // Map of submenu element -> timeout ID
|
||||
this._hoverIntentData = null; // For safe triangle calculation
|
||||
|
||||
// TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/
|
||||
this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent);
|
||||
|
||||
// Parse responsive placements on init
|
||||
this._parseResponsivePlacements();
|
||||
|
||||
// Set up submenu event listeners
|
||||
this._setupSubmenuListeners();
|
||||
this._inNavbar = this._detectNavbar();
|
||||
}
|
||||
|
||||
// Getters
|
||||
@@ -152,7 +134,7 @@
|
||||
if (showEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
this._createFloating();
|
||||
this._createPopper();
|
||||
|
||||
// If this is a touch-enabled device we add extra
|
||||
// empty mouseover listeners to the body's immediate children;
|
||||
@@ -164,10 +146,9 @@
|
||||
}
|
||||
}
|
||||
this._element.focus();
|
||||
this._element.setAttribute('aria-expanded', 'true');
|
||||
this._element.setAttribute('aria-expanded', true);
|
||||
this._menu.classList.add(CLASS_NAME_SHOW);
|
||||
this._element.classList.add(CLASS_NAME_SHOW);
|
||||
this._parent.classList.add(CLASS_NAME_SHOW);
|
||||
EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget);
|
||||
}
|
||||
hide() {
|
||||
@@ -180,15 +161,15 @@
|
||||
this._completeHide(relatedTarget);
|
||||
}
|
||||
dispose() {
|
||||
this._disposeFloating();
|
||||
this._disposeMediaQueryListeners();
|
||||
this._closeAllSubmenus();
|
||||
this._clearAllSubmenuTimeouts();
|
||||
if (this._popper) {
|
||||
this._popper.destroy();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
update() {
|
||||
if (this._floatingCleanup) {
|
||||
this._updateFloatingPosition();
|
||||
this._inNavbar = this._detectNavbar();
|
||||
if (this._popper) {
|
||||
this._popper.update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,9 +180,6 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Close all open submenus first
|
||||
this._closeAllSubmenus();
|
||||
|
||||
// If this is a touch-enabled device we remove the extra
|
||||
// empty mouseover listeners we added for iOS support
|
||||
if ('ontouchstart' in document.documentElement) {
|
||||
@@ -209,27 +187,26 @@
|
||||
EventHandler.off(element, 'mouseover', index_js.noop);
|
||||
}
|
||||
}
|
||||
this._disposeFloating();
|
||||
if (this._popper) {
|
||||
this._popper.destroy();
|
||||
}
|
||||
this._menu.classList.remove(CLASS_NAME_SHOW);
|
||||
this._element.classList.remove(CLASS_NAME_SHOW);
|
||||
this._parent.classList.remove(CLASS_NAME_SHOW);
|
||||
this._element.setAttribute('aria-expanded', 'false');
|
||||
Manipulator.removeDataAttribute(this._menu, 'placement');
|
||||
Manipulator.removeDataAttribute(this._menu, 'display');
|
||||
Manipulator.removeDataAttribute(this._menu, 'popper');
|
||||
EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget);
|
||||
}
|
||||
_getConfig(config) {
|
||||
config = super._getConfig(config);
|
||||
if (typeof config.reference === 'object' && !index_js.isElement(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') {
|
||||
// Floating UI virtual elements require a getBoundingClientRect method
|
||||
// Popper virtual elements require a getBoundingClientRect method
|
||||
throw new TypeError(`${NAME.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
_createFloating() {
|
||||
if (this._config.display === 'static') {
|
||||
Manipulator.setDataAttribute(this._menu, 'display', 'static');
|
||||
return;
|
||||
_createPopper() {
|
||||
if (typeof Popper__namespace === 'undefined') {
|
||||
throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org/docs/v2/)');
|
||||
}
|
||||
let referenceElement = this._element;
|
||||
if (this._config.reference === 'parent') {
|
||||
@@ -239,430 +216,83 @@
|
||||
} else if (typeof this._config.reference === 'object') {
|
||||
referenceElement = this._config.reference;
|
||||
}
|
||||
|
||||
// Initial position update
|
||||
this._updateFloatingPosition(referenceElement);
|
||||
|
||||
// Set up auto-update for scroll/resize
|
||||
this._floatingCleanup = dom.autoUpdate(referenceElement, this._menu, () => this._updateFloatingPosition(referenceElement));
|
||||
}
|
||||
async _updateFloatingPosition(referenceElement = null) {
|
||||
if (!this._menu) {
|
||||
return;
|
||||
}
|
||||
if (!referenceElement) {
|
||||
if (this._config.reference === 'parent') {
|
||||
referenceElement = this._parent;
|
||||
} else if (index_js.isElement(this._config.reference)) {
|
||||
referenceElement = index_js.getElement(this._config.reference);
|
||||
} else if (typeof this._config.reference === 'object') {
|
||||
referenceElement = this._config.reference;
|
||||
} else {
|
||||
referenceElement = this._element;
|
||||
}
|
||||
}
|
||||
const placement = this._getPlacement();
|
||||
const middleware = this._getFloatingMiddleware();
|
||||
const floatingConfig = this._getFloatingConfig(placement, middleware);
|
||||
await this._applyFloatingPosition(referenceElement, this._menu, floatingConfig.placement, floatingConfig.middleware);
|
||||
const popperConfig = this._getPopperConfig();
|
||||
this._popper = Popper__namespace.createPopper(referenceElement, this._menu, popperConfig);
|
||||
}
|
||||
_isShown() {
|
||||
return this._menu.classList.contains(CLASS_NAME_SHOW);
|
||||
}
|
||||
_getPlacement() {
|
||||
// If we have responsive placements, find the appropriate one for current viewport
|
||||
const placement = this._responsivePlacements ? floatingUi_js.getResponsivePlacement(this._responsivePlacements, DEFAULT_PLACEMENT) : this._config.placement;
|
||||
|
||||
// Resolve logical placements (start/end) to physical (left/right) based on RTL
|
||||
return resolveLogicalPlacement(placement);
|
||||
}
|
||||
_parseResponsivePlacements() {
|
||||
this._responsivePlacements = floatingUi_js.parseResponsivePlacement(this._config.placement, DEFAULT_PLACEMENT);
|
||||
if (this._responsivePlacements) {
|
||||
this._setupMediaQueryListeners();
|
||||
const parentDropdown = this._parent;
|
||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {
|
||||
return PLACEMENT_RIGHT;
|
||||
}
|
||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {
|
||||
return PLACEMENT_LEFT;
|
||||
}
|
||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {
|
||||
return PLACEMENT_TOPCENTER;
|
||||
}
|
||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {
|
||||
return PLACEMENT_BOTTOMCENTER;
|
||||
}
|
||||
|
||||
// We need to trim the value because custom properties can also include spaces
|
||||
const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end';
|
||||
if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {
|
||||
return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP;
|
||||
}
|
||||
return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM;
|
||||
}
|
||||
_setupMediaQueryListeners() {
|
||||
this._disposeMediaQueryListeners();
|
||||
this._mediaQueryListeners = floatingUi_js.createBreakpointListeners(() => {
|
||||
if (this._isShown()) {
|
||||
this._updateFloatingPosition();
|
||||
}
|
||||
});
|
||||
}
|
||||
_disposeMediaQueryListeners() {
|
||||
floatingUi_js.disposeBreakpointListeners(this._mediaQueryListeners);
|
||||
this._mediaQueryListeners = [];
|
||||
_detectNavbar() {
|
||||
return this._element.closest(SELECTOR_NAVBAR) !== null;
|
||||
}
|
||||
_getOffset() {
|
||||
const {
|
||||
offset: offsetConfig
|
||||
offset
|
||||
} = this._config;
|
||||
if (typeof offsetConfig === 'string') {
|
||||
return offsetConfig.split(',').map(value => Number.parseInt(value, 10));
|
||||
if (typeof offset === 'string') {
|
||||
return offset.split(',').map(value => Number.parseInt(value, 10));
|
||||
}
|
||||
if (typeof offsetConfig === 'function') {
|
||||
// Floating UI passes different args, adapt the interface for offset function callbacks
|
||||
return ({
|
||||
placement,
|
||||
rects
|
||||
}) => {
|
||||
const result = offsetConfig({
|
||||
placement,
|
||||
reference: rects.reference,
|
||||
floating: rects.floating
|
||||
}, this._element);
|
||||
return result;
|
||||
};
|
||||
if (typeof offset === 'function') {
|
||||
return popperData => offset(popperData, this._element);
|
||||
}
|
||||
return offsetConfig;
|
||||
return offset;
|
||||
}
|
||||
_getFloatingMiddleware() {
|
||||
const offsetValue = this._getOffset();
|
||||
const middleware = [
|
||||
// Offset middleware - handles distance from reference
|
||||
dom.offset(typeof offsetValue === 'function' ? offsetValue : {
|
||||
mainAxis: offsetValue[1] || 0,
|
||||
crossAxis: offsetValue[0] || 0
|
||||
}),
|
||||
// Flip middleware - handles fallback placements
|
||||
dom.flip({
|
||||
fallbackPlacements: this._getFallbackPlacements()
|
||||
}),
|
||||
// Shift middleware - prevents overflow
|
||||
dom.shift({
|
||||
boundary: this._config.boundary === 'clippingParents' ? 'clippingAncestors' : this._config.boundary
|
||||
})];
|
||||
return middleware;
|
||||
}
|
||||
_getFallbackPlacements() {
|
||||
// Get appropriate fallback placements based on current placement
|
||||
// Fallbacks should preserve alignment (start/end) when possible
|
||||
const placement = this._getPlacement();
|
||||
_getPopperConfig() {
|
||||
const defaultBsPopperConfig = {
|
||||
placement: this._getPlacement(),
|
||||
modifiers: [{
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
boundary: this._config.boundary
|
||||
}
|
||||
}, {
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: this._getOffset()
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
// Handle all possible Floating UI placements
|
||||
const fallbackMap = {
|
||||
bottom: ['top', 'bottom-start', 'bottom-end', 'top-start', 'top-end'],
|
||||
'bottom-start': ['top-start', 'bottom-end', 'top-end'],
|
||||
'bottom-end': ['top-end', 'bottom-start', 'top-start'],
|
||||
top: ['bottom', 'top-start', 'top-end', 'bottom-start', 'bottom-end'],
|
||||
'top-start': ['bottom-start', 'top-end', 'bottom-end'],
|
||||
'top-end': ['bottom-end', 'top-start', 'bottom-start'],
|
||||
right: ['left', 'right-start', 'right-end', 'left-start', 'left-end'],
|
||||
'right-start': ['left-start', 'right-end', 'left-end', 'top-start', 'bottom-start'],
|
||||
'right-end': ['left-end', 'right-start', 'left-start', 'top-end', 'bottom-end'],
|
||||
left: ['right', 'left-start', 'left-end', 'right-start', 'right-end'],
|
||||
'left-start': ['right-start', 'left-end', 'right-end', 'top-start', 'bottom-start'],
|
||||
'left-end': ['right-end', 'left-start', 'right-start', 'top-end', 'bottom-end']
|
||||
};
|
||||
return fallbackMap[placement] || ['top', 'bottom', 'right', 'left'];
|
||||
}
|
||||
_getFloatingConfig(placement, middleware) {
|
||||
const defaultConfig = {
|
||||
placement,
|
||||
middleware
|
||||
};
|
||||
// Disable Popper if we have a static display or Dropdown is in Navbar
|
||||
if (this._inNavbar || this._config.display === 'static') {
|
||||
Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // TODO: v6 remove
|
||||
defaultBsPopperConfig.modifiers = [{
|
||||
name: 'applyStyles',
|
||||
enabled: false
|
||||
}];
|
||||
}
|
||||
return {
|
||||
...defaultConfig,
|
||||
...index_js.execute(this._config.floatingConfig, [undefined, defaultConfig])
|
||||
...defaultBsPopperConfig,
|
||||
...index_js.execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])
|
||||
};
|
||||
}
|
||||
_disposeFloating() {
|
||||
if (this._floatingCleanup) {
|
||||
this._floatingCleanup();
|
||||
this._floatingCleanup = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Shared helper for positioning any floating element
|
||||
async _applyFloatingPosition(reference, floating, placement, middleware) {
|
||||
if (!floating.isConnected) {
|
||||
return null;
|
||||
}
|
||||
const {
|
||||
x,
|
||||
y,
|
||||
placement: finalPlacement
|
||||
} = await dom.computePosition(reference, floating, {
|
||||
placement,
|
||||
middleware
|
||||
});
|
||||
if (!floating.isConnected) {
|
||||
return null;
|
||||
}
|
||||
Object.assign(floating.style, {
|
||||
position: 'absolute',
|
||||
left: `${x}px`,
|
||||
top: `${y}px`,
|
||||
margin: '0'
|
||||
});
|
||||
Manipulator.setDataAttribute(floating, 'placement', finalPlacement);
|
||||
return finalPlacement;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Submenu handling
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
_setupSubmenuListeners() {
|
||||
// Set up hover listeners for submenu triggers
|
||||
if (this._config.submenuTrigger === 'hover' || this._config.submenuTrigger === 'both') {
|
||||
EventHandler.on(this._menu, 'mouseenter', SELECTOR_SUBMENU_TOGGLE, event => {
|
||||
this._onSubmenuTriggerEnter(event);
|
||||
});
|
||||
EventHandler.on(this._menu, 'mouseleave', SELECTOR_SUBMENU, event => {
|
||||
this._onSubmenuLeave(event);
|
||||
});
|
||||
|
||||
// Track mouse movement for safe triangle calculation
|
||||
EventHandler.on(this._menu, 'mousemove', event => {
|
||||
this._trackMousePosition(event);
|
||||
});
|
||||
}
|
||||
|
||||
// Set up click listener for submenu triggers
|
||||
if (this._config.submenuTrigger === 'click' || this._config.submenuTrigger === 'both') {
|
||||
EventHandler.on(this._menu, 'click', SELECTOR_SUBMENU_TOGGLE, event => {
|
||||
this._onSubmenuTriggerClick(event);
|
||||
});
|
||||
}
|
||||
}
|
||||
_onSubmenuTriggerEnter(event) {
|
||||
const trigger = event.target.closest(SELECTOR_SUBMENU_TOGGLE);
|
||||
if (!trigger) {
|
||||
return;
|
||||
}
|
||||
const submenuWrapper = trigger.closest(SELECTOR_SUBMENU);
|
||||
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
|
||||
if (!submenu) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel any pending close timeout for this submenu
|
||||
this._cancelSubmenuCloseTimeout(submenu);
|
||||
|
||||
// Close other open submenus at the same level
|
||||
this._closeSiblingSubmenus(submenuWrapper);
|
||||
|
||||
// Open this submenu
|
||||
this._openSubmenu(trigger, submenu, submenuWrapper);
|
||||
}
|
||||
_onSubmenuLeave(event) {
|
||||
const submenuWrapper = event.target.closest(SELECTOR_SUBMENU);
|
||||
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
|
||||
if (!submenu || !this._openSubmenus.has(submenu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we're moving toward the submenu (safe triangle)
|
||||
if (this._isMovingTowardSubmenu(event, submenu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedule submenu close with delay
|
||||
this._scheduleSubmenuClose(submenu, submenuWrapper);
|
||||
}
|
||||
_onSubmenuTriggerClick(event) {
|
||||
const trigger = event.target.closest(SELECTOR_SUBMENU_TOGGLE);
|
||||
if (!trigger) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const submenuWrapper = trigger.closest(SELECTOR_SUBMENU);
|
||||
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
|
||||
if (!submenu) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle submenu
|
||||
if (this._openSubmenus.has(submenu)) {
|
||||
this._closeSubmenu(submenu, submenuWrapper);
|
||||
} else {
|
||||
this._closeSiblingSubmenus(submenuWrapper);
|
||||
this._openSubmenu(trigger, submenu, submenuWrapper);
|
||||
}
|
||||
}
|
||||
_openSubmenu(trigger, submenu, submenuWrapper) {
|
||||
if (this._openSubmenus.has(submenu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set ARIA attributes
|
||||
trigger.setAttribute('aria-expanded', 'true');
|
||||
trigger.setAttribute('aria-haspopup', 'true');
|
||||
|
||||
// Position and show submenu
|
||||
submenu.classList.add(CLASS_NAME_SHOW);
|
||||
submenuWrapper.classList.add(CLASS_NAME_SHOW);
|
||||
|
||||
// Set up Floating UI positioning for submenu
|
||||
const cleanup = this._createSubmenuFloating(trigger, submenu, submenuWrapper);
|
||||
this._openSubmenus.set(submenu, cleanup);
|
||||
|
||||
// Set up mouseenter on submenu to cancel close timeout
|
||||
EventHandler.on(submenu, 'mouseenter', () => {
|
||||
this._cancelSubmenuCloseTimeout(submenu);
|
||||
});
|
||||
}
|
||||
_closeSubmenu(submenu, submenuWrapper) {
|
||||
if (!this._openSubmenus.has(submenu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Close any nested submenus first
|
||||
const nestedSubmenus = SelectorEngine.find(`${SELECTOR_SUBMENU} ${SELECTOR_MENU}.${CLASS_NAME_SHOW}`, submenu);
|
||||
for (const nested of nestedSubmenus) {
|
||||
const nestedWrapper = nested.closest(SELECTOR_SUBMENU);
|
||||
this._closeSubmenu(nested, nestedWrapper);
|
||||
}
|
||||
|
||||
// Get the trigger
|
||||
const trigger = SelectorEngine.findOne(SELECTOR_SUBMENU_TOGGLE, submenuWrapper);
|
||||
|
||||
// Clean up Floating UI
|
||||
const cleanup = this._openSubmenus.get(submenu);
|
||||
if (cleanup) {
|
||||
cleanup();
|
||||
}
|
||||
this._openSubmenus.delete(submenu);
|
||||
|
||||
// Remove event listeners
|
||||
EventHandler.off(submenu, 'mouseenter');
|
||||
|
||||
// Update ARIA and visibility
|
||||
if (trigger) {
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
submenu.classList.remove(CLASS_NAME_SHOW);
|
||||
submenuWrapper.classList.remove(CLASS_NAME_SHOW);
|
||||
|
||||
// Clear inline styles
|
||||
submenu.style.position = '';
|
||||
submenu.style.left = '';
|
||||
submenu.style.top = '';
|
||||
submenu.style.margin = '';
|
||||
}
|
||||
_closeAllSubmenus() {
|
||||
for (const [submenu] of this._openSubmenus) {
|
||||
const submenuWrapper = submenu.closest(SELECTOR_SUBMENU);
|
||||
this._closeSubmenu(submenu, submenuWrapper);
|
||||
}
|
||||
}
|
||||
_closeSiblingSubmenus(currentSubmenuWrapper) {
|
||||
// Find all sibling submenu wrappers and close their menus
|
||||
const parent = currentSubmenuWrapper.parentNode;
|
||||
const siblingSubmenus = SelectorEngine.find(`${SELECTOR_SUBMENU} > ${SELECTOR_MENU}.${CLASS_NAME_SHOW}`, parent);
|
||||
for (const siblingMenu of siblingSubmenus) {
|
||||
const siblingWrapper = siblingMenu.closest(SELECTOR_SUBMENU);
|
||||
if (siblingWrapper !== currentSubmenuWrapper) {
|
||||
this._closeSubmenu(siblingMenu, siblingWrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
_createSubmenuFloating(trigger, submenu, submenuWrapper) {
|
||||
const referenceElement = submenuWrapper;
|
||||
const placement = resolveLogicalPlacement(SUBMENU_PLACEMENT);
|
||||
const middleware = [dom.offset({
|
||||
mainAxis: 0,
|
||||
crossAxis: -4
|
||||
}), dom.flip({
|
||||
fallbackPlacements: [resolveLogicalPlacement('start-start'), resolveLogicalPlacement('end-end'), resolveLogicalPlacement('start-end')]
|
||||
}), dom.shift({
|
||||
padding: 8
|
||||
})];
|
||||
const updatePosition = () => this._applyFloatingPosition(referenceElement, submenu, placement, middleware);
|
||||
updatePosition();
|
||||
return dom.autoUpdate(referenceElement, submenu, updatePosition);
|
||||
}
|
||||
_scheduleSubmenuClose(submenu, submenuWrapper) {
|
||||
this._cancelSubmenuCloseTimeout(submenu);
|
||||
const timeoutId = setTimeout(() => {
|
||||
this._closeSubmenu(submenu, submenuWrapper);
|
||||
this._submenuCloseTimeouts.delete(submenu);
|
||||
}, this._config.submenuDelay);
|
||||
this._submenuCloseTimeouts.set(submenu, timeoutId);
|
||||
}
|
||||
_cancelSubmenuCloseTimeout(submenu) {
|
||||
const timeoutId = this._submenuCloseTimeouts.get(submenu);
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
this._submenuCloseTimeouts.delete(submenu);
|
||||
}
|
||||
}
|
||||
_clearAllSubmenuTimeouts() {
|
||||
for (const timeoutId of this._submenuCloseTimeouts.values()) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
this._submenuCloseTimeouts.clear();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Hover intent / Safe triangle
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
_trackMousePosition(event) {
|
||||
this._hoverIntentData = {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
_isMovingTowardSubmenu(event, submenu) {
|
||||
if (!this._hoverIntentData) {
|
||||
return false;
|
||||
}
|
||||
const submenuRect = submenu.getBoundingClientRect();
|
||||
const currentPos = {
|
||||
x: event.clientX,
|
||||
y: event.clientY
|
||||
};
|
||||
const lastPos = {
|
||||
x: this._hoverIntentData.x,
|
||||
y: this._hoverIntentData.y
|
||||
};
|
||||
|
||||
// Create a triangle from current position to submenu edges
|
||||
// The triangle represents the "safe zone" for diagonal movement
|
||||
const isRtl = index_js.isRTL();
|
||||
|
||||
// Determine which edge of the submenu to target based on direction
|
||||
const targetX = isRtl ? submenuRect.right : submenuRect.left;
|
||||
const topCorner = {
|
||||
x: targetX,
|
||||
y: submenuRect.top
|
||||
};
|
||||
const bottomCorner = {
|
||||
x: targetX,
|
||||
y: submenuRect.bottom
|
||||
};
|
||||
|
||||
// Check if cursor is moving toward the submenu
|
||||
// by checking if the current position is within the safe triangle
|
||||
return this._pointInTriangle(currentPos, lastPos, topCorner, bottomCorner);
|
||||
}
|
||||
_pointInTriangle(point, v1, v2, v3) {
|
||||
// Barycentric coordinate method to check if point is inside triangle
|
||||
const d1 = triangleSign(point, v1, v2);
|
||||
const d2 = triangleSign(point, v2, v3);
|
||||
const d3 = triangleSign(point, v3, v1);
|
||||
const hasNeg = d1 < 0 || d2 < 0 || d3 < 0;
|
||||
const hasPos = d1 > 0 || d2 > 0 || d3 > 0;
|
||||
return !(hasNeg && hasPos);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Keyboard navigation
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
_selectMenuItem({
|
||||
key,
|
||||
target
|
||||
}) {
|
||||
// Get items only from the current menu level (not nested submenus)
|
||||
// If target is inside a menu, use that menu; otherwise use the main menu
|
||||
const currentMenu = target.closest(SELECTOR_MENU) || this._menu;
|
||||
const items = SelectorEngine.find(`:scope > li > ${SELECTOR_VISIBLE_ITEMS}, :scope > ${SELECTOR_VISIBLE_ITEMS}`, currentMenu).filter(element => index_js.isVisible(element));
|
||||
const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => index_js.isVisible(element));
|
||||
if (!items.length) {
|
||||
return;
|
||||
}
|
||||
@@ -671,89 +301,6 @@
|
||||
// allow cycling to get the last item in case key equals ARROW_UP_KEY
|
||||
index_js.getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus();
|
||||
}
|
||||
_handleSubmenuKeydown(event) {
|
||||
const {
|
||||
key,
|
||||
target
|
||||
} = event;
|
||||
const isRtl = index_js.isRTL();
|
||||
|
||||
// Determine the "enter submenu" and "exit submenu" keys based on RTL
|
||||
const enterKey = isRtl ? ARROW_LEFT_KEY : ARROW_RIGHT_KEY;
|
||||
const exitKey = isRtl ? ARROW_RIGHT_KEY : ARROW_LEFT_KEY;
|
||||
|
||||
// Check if target is a submenu trigger
|
||||
const submenuWrapper = target.closest(SELECTOR_SUBMENU);
|
||||
const isSubmenuTrigger = submenuWrapper && target.matches(SELECTOR_SUBMENU_TOGGLE);
|
||||
|
||||
// Handle Enter/Space on submenu trigger
|
||||
if ((key === ENTER_KEY || key === SPACE_KEY) && isSubmenuTrigger) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
|
||||
if (submenu) {
|
||||
this._closeSiblingSubmenus(submenuWrapper);
|
||||
this._openSubmenu(target, submenu, submenuWrapper);
|
||||
// Focus first item in submenu
|
||||
requestAnimationFrame(() => {
|
||||
const firstItem = SelectorEngine.findOne(SELECTOR_VISIBLE_ITEMS, submenu);
|
||||
if (firstItem) {
|
||||
firstItem.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle Right arrow (or Left in RTL) - enter submenu
|
||||
if (key === enterKey && isSubmenuTrigger) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
|
||||
if (submenu) {
|
||||
this._closeSiblingSubmenus(submenuWrapper);
|
||||
this._openSubmenu(target, submenu, submenuWrapper);
|
||||
// Focus first item in submenu
|
||||
requestAnimationFrame(() => {
|
||||
const firstItem = SelectorEngine.findOne(SELECTOR_VISIBLE_ITEMS, submenu);
|
||||
if (firstItem) {
|
||||
firstItem.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle Left arrow (or Right in RTL) - exit submenu
|
||||
if (key === exitKey) {
|
||||
const currentMenu = target.closest(SELECTOR_MENU);
|
||||
const parentSubmenuWrapper = currentMenu?.closest(SELECTOR_SUBMENU);
|
||||
if (parentSubmenuWrapper) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const parentTrigger = SelectorEngine.findOne(SELECTOR_SUBMENU_TOGGLE, parentSubmenuWrapper);
|
||||
this._closeSubmenu(currentMenu, parentSubmenuWrapper);
|
||||
if (parentTrigger) {
|
||||
parentTrigger.focus();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Home/End keys
|
||||
if (key === HOME_KEY || key === END_KEY) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const currentMenu = target.closest(SELECTOR_MENU);
|
||||
const items = SelectorEngine.find(`:scope > li > ${SELECTOR_VISIBLE_ITEMS}, :scope > ${SELECTOR_VISIBLE_ITEMS}`, currentMenu).filter(element => index_js.isVisible(element));
|
||||
if (items.length) {
|
||||
const targetItem = key === HOME_KEY ? items[0] : items[items.length - 1];
|
||||
targetItem.focus();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static clearMenus(event) {
|
||||
if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY) {
|
||||
return;
|
||||
@@ -784,62 +331,32 @@
|
||||
}
|
||||
}
|
||||
static dataApiKeydownHandler(event) {
|
||||
// If not a relevant key => not a dropdown command
|
||||
// If not an UP | DOWN | ESCAPE key => not a dropdown command
|
||||
// If input/textarea && if key is other than ESCAPE => not a dropdown command
|
||||
|
||||
const isInput = /input|textarea/i.test(event.target.tagName);
|
||||
const isEscapeEvent = event.key === ESCAPE_KEY;
|
||||
const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key);
|
||||
const isLeftOrRightEvent = [ARROW_LEFT_KEY, ARROW_RIGHT_KEY].includes(event.key);
|
||||
const isHomeOrEndEvent = [HOME_KEY, END_KEY].includes(event.key);
|
||||
const isEnterOrSpaceEvent = [ENTER_KEY, SPACE_KEY].includes(event.key);
|
||||
|
||||
// Allow Enter/Space only on submenu triggers
|
||||
const isSubmenuTrigger = event.target.matches(SELECTOR_SUBMENU_TOGGLE);
|
||||
if (!isUpOrDownEvent && !isEscapeEvent && !isLeftOrRightEvent && !isHomeOrEndEvent && !(isEnterOrSpaceEvent && isSubmenuTrigger)) {
|
||||
if (!isUpOrDownEvent && !isEscapeEvent) {
|
||||
return;
|
||||
}
|
||||
if (isInput && !isEscapeEvent) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
|
||||
// TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/
|
||||
const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode);
|
||||
if (!getToggleButton) {
|
||||
return;
|
||||
}
|
||||
const instance = Dropdown.getOrCreateInstance(getToggleButton);
|
||||
|
||||
// Handle submenu navigation first
|
||||
if ((isLeftOrRightEvent || isHomeOrEndEvent || isEnterOrSpaceEvent && isSubmenuTrigger) && instance._handleSubmenuKeydown(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle Up/Down navigation
|
||||
if (isUpOrDownEvent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
instance.show();
|
||||
instance._selectMenuItem(event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle Escape
|
||||
if (isEscapeEvent && instance._isShown()) {
|
||||
event.preventDefault();
|
||||
if (instance._isShown()) {
|
||||
// else is escape and we check if it is shown
|
||||
event.stopPropagation();
|
||||
|
||||
// If in a submenu, close just that submenu
|
||||
const currentMenu = event.target.closest(SELECTOR_MENU);
|
||||
const parentSubmenuWrapper = currentMenu?.closest(SELECTOR_SUBMENU);
|
||||
if (parentSubmenuWrapper && instance._openSubmenus.size > 0) {
|
||||
const parentTrigger = SelectorEngine.findOne(SELECTOR_SUBMENU_TOGGLE, parentSubmenuWrapper);
|
||||
instance._closeSubmenu(currentMenu, parentSubmenuWrapper);
|
||||
if (parentTrigger) {
|
||||
parentTrigger.focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise close the whole dropdown
|
||||
instance.hide();
|
||||
getToggleButton.focus();
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+300
@@ -0,0 +1,300 @@
|
||||
/*!
|
||||
* Bootstrap modal.js v5.3.8 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/selector-engine.js'), require('./util/backdrop.js'), require('./util/component-functions.js'), require('./util/focustrap.js'), require('./util/index.js'), require('./util/scrollbar.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine', './util/backdrop', './util/component-functions', './util/focustrap', './util/index', './util/scrollbar'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Modal = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine, global.Backdrop, global.ComponentFunctions, global.Focustrap, global.Index, global.Scrollbar));
|
||||
})(this, (function (BaseComponent, EventHandler, SelectorEngine, Backdrop, componentFunctions_js, FocusTrap, index_js, ScrollBarHelper) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap modal.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'modal';
|
||||
const DATA_KEY = 'bs.modal';
|
||||
const EVENT_KEY = `.${DATA_KEY}`;
|
||||
const DATA_API_KEY = '.data-api';
|
||||
const ESCAPE_KEY = 'Escape';
|
||||
const EVENT_HIDE = `hide${EVENT_KEY}`;
|
||||
const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`;
|
||||
const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
|
||||
const EVENT_SHOW = `show${EVENT_KEY}`;
|
||||
const EVENT_SHOWN = `shown${EVENT_KEY}`;
|
||||
const EVENT_RESIZE = `resize${EVENT_KEY}`;
|
||||
const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`;
|
||||
const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`;
|
||||
const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`;
|
||||
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
|
||||
const CLASS_NAME_OPEN = 'modal-open';
|
||||
const CLASS_NAME_FADE = 'fade';
|
||||
const CLASS_NAME_SHOW = 'show';
|
||||
const CLASS_NAME_STATIC = 'modal-static';
|
||||
const OPEN_SELECTOR = '.modal.show';
|
||||
const SELECTOR_DIALOG = '.modal-dialog';
|
||||
const SELECTOR_MODAL_BODY = '.modal-body';
|
||||
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="modal"]';
|
||||
const Default = {
|
||||
backdrop: true,
|
||||
focus: true,
|
||||
keyboard: true
|
||||
};
|
||||
const DefaultType = {
|
||||
backdrop: '(boolean|string)',
|
||||
focus: 'boolean',
|
||||
keyboard: 'boolean'
|
||||
};
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Modal extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
super(element, config);
|
||||
this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element);
|
||||
this._backdrop = this._initializeBackDrop();
|
||||
this._focustrap = this._initializeFocusTrap();
|
||||
this._isShown = false;
|
||||
this._isTransitioning = false;
|
||||
this._scrollBar = new ScrollBarHelper();
|
||||
this._addEventListeners();
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default;
|
||||
}
|
||||
static get DefaultType() {
|
||||
return DefaultType;
|
||||
}
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Public
|
||||
toggle(relatedTarget) {
|
||||
return this._isShown ? this.hide() : this.show(relatedTarget);
|
||||
}
|
||||
show(relatedTarget) {
|
||||
if (this._isShown || this._isTransitioning) {
|
||||
return;
|
||||
}
|
||||
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {
|
||||
relatedTarget
|
||||
});
|
||||
if (showEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
this._isShown = true;
|
||||
this._isTransitioning = true;
|
||||
this._scrollBar.hide();
|
||||
document.body.classList.add(CLASS_NAME_OPEN);
|
||||
this._adjustDialog();
|
||||
this._backdrop.show(() => this._showElement(relatedTarget));
|
||||
}
|
||||
hide() {
|
||||
if (!this._isShown || this._isTransitioning) {
|
||||
return;
|
||||
}
|
||||
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);
|
||||
if (hideEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
this._isShown = false;
|
||||
this._isTransitioning = true;
|
||||
this._focustrap.deactivate();
|
||||
this._element.classList.remove(CLASS_NAME_SHOW);
|
||||
this._queueCallback(() => this._hideModal(), this._element, this._isAnimated());
|
||||
}
|
||||
dispose() {
|
||||
EventHandler.off(window, EVENT_KEY);
|
||||
EventHandler.off(this._dialog, EVENT_KEY);
|
||||
this._backdrop.dispose();
|
||||
this._focustrap.deactivate();
|
||||
super.dispose();
|
||||
}
|
||||
handleUpdate() {
|
||||
this._adjustDialog();
|
||||
}
|
||||
|
||||
// Private
|
||||
_initializeBackDrop() {
|
||||
return new Backdrop({
|
||||
isVisible: Boolean(this._config.backdrop),
|
||||
// 'static' option will be translated to true, and booleans will keep their value,
|
||||
isAnimated: this._isAnimated()
|
||||
});
|
||||
}
|
||||
_initializeFocusTrap() {
|
||||
return new FocusTrap({
|
||||
trapElement: this._element
|
||||
});
|
||||
}
|
||||
_showElement(relatedTarget) {
|
||||
// try to append dynamic modal
|
||||
if (!document.body.contains(this._element)) {
|
||||
document.body.append(this._element);
|
||||
}
|
||||
this._element.style.display = 'block';
|
||||
this._element.removeAttribute('aria-hidden');
|
||||
this._element.setAttribute('aria-modal', true);
|
||||
this._element.setAttribute('role', 'dialog');
|
||||
this._element.scrollTop = 0;
|
||||
const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog);
|
||||
if (modalBody) {
|
||||
modalBody.scrollTop = 0;
|
||||
}
|
||||
index_js.reflow(this._element);
|
||||
this._element.classList.add(CLASS_NAME_SHOW);
|
||||
const transitionComplete = () => {
|
||||
if (this._config.focus) {
|
||||
this._focustrap.activate();
|
||||
}
|
||||
this._isTransitioning = false;
|
||||
EventHandler.trigger(this._element, EVENT_SHOWN, {
|
||||
relatedTarget
|
||||
});
|
||||
};
|
||||
this._queueCallback(transitionComplete, this._dialog, this._isAnimated());
|
||||
}
|
||||
_addEventListeners() {
|
||||
EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {
|
||||
if (event.key !== ESCAPE_KEY) {
|
||||
return;
|
||||
}
|
||||
if (this._config.keyboard) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
this._triggerBackdropTransition();
|
||||
});
|
||||
EventHandler.on(window, EVENT_RESIZE, () => {
|
||||
if (this._isShown && !this._isTransitioning) {
|
||||
this._adjustDialog();
|
||||
}
|
||||
});
|
||||
EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {
|
||||
// a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks
|
||||
EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {
|
||||
if (this._element !== event.target || this._element !== event2.target) {
|
||||
return;
|
||||
}
|
||||
if (this._config.backdrop === 'static') {
|
||||
this._triggerBackdropTransition();
|
||||
return;
|
||||
}
|
||||
if (this._config.backdrop) {
|
||||
this.hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
_hideModal() {
|
||||
this._element.style.display = 'none';
|
||||
this._element.setAttribute('aria-hidden', true);
|
||||
this._element.removeAttribute('aria-modal');
|
||||
this._element.removeAttribute('role');
|
||||
this._isTransitioning = false;
|
||||
this._backdrop.hide(() => {
|
||||
document.body.classList.remove(CLASS_NAME_OPEN);
|
||||
this._resetAdjustments();
|
||||
this._scrollBar.reset();
|
||||
EventHandler.trigger(this._element, EVENT_HIDDEN);
|
||||
});
|
||||
}
|
||||
_isAnimated() {
|
||||
return this._element.classList.contains(CLASS_NAME_FADE);
|
||||
}
|
||||
_triggerBackdropTransition() {
|
||||
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);
|
||||
if (hideEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;
|
||||
const initialOverflowY = this._element.style.overflowY;
|
||||
// return if the following background transition hasn't yet completed
|
||||
if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {
|
||||
return;
|
||||
}
|
||||
if (!isModalOverflowing) {
|
||||
this._element.style.overflowY = 'hidden';
|
||||
}
|
||||
this._element.classList.add(CLASS_NAME_STATIC);
|
||||
this._queueCallback(() => {
|
||||
this._element.classList.remove(CLASS_NAME_STATIC);
|
||||
this._queueCallback(() => {
|
||||
this._element.style.overflowY = initialOverflowY;
|
||||
}, this._dialog);
|
||||
}, this._dialog);
|
||||
this._element.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* The following methods are used to handle overflowing modals
|
||||
*/
|
||||
|
||||
_adjustDialog() {
|
||||
const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;
|
||||
const scrollbarWidth = this._scrollBar.getWidth();
|
||||
const isBodyOverflowing = scrollbarWidth > 0;
|
||||
if (isBodyOverflowing && !isModalOverflowing) {
|
||||
const property = index_js.isRTL() ? 'paddingLeft' : 'paddingRight';
|
||||
this._element.style[property] = `${scrollbarWidth}px`;
|
||||
}
|
||||
if (!isBodyOverflowing && isModalOverflowing) {
|
||||
const property = index_js.isRTL() ? 'paddingRight' : 'paddingLeft';
|
||||
this._element.style[property] = `${scrollbarWidth}px`;
|
||||
}
|
||||
}
|
||||
_resetAdjustments() {
|
||||
this._element.style.paddingLeft = '';
|
||||
this._element.style.paddingRight = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
|
||||
const target = SelectorEngine.getElementFromSelector(this);
|
||||
if (['A', 'AREA'].includes(this.tagName)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
EventHandler.one(target, EVENT_SHOW, showEvent => {
|
||||
if (showEvent.defaultPrevented) {
|
||||
// only register focus restorer if modal will actually get shown
|
||||
return;
|
||||
}
|
||||
EventHandler.one(target, EVENT_HIDDEN, () => {
|
||||
if (index_js.isVisible(this)) {
|
||||
this.focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// avoid conflict when clicking modal toggler while another one is open
|
||||
const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR);
|
||||
if (alreadyOpen) {
|
||||
Modal.getInstance(alreadyOpen).hide();
|
||||
}
|
||||
const data = Modal.getOrCreateInstance(target);
|
||||
data.toggle(this);
|
||||
});
|
||||
componentFunctions_js.enableDismissTrigger(Modal);
|
||||
|
||||
return Modal;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=modal.js.map
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
-238
@@ -1,238 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap otp-input.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')) :
|
||||
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.OtpInput = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine));
|
||||
})(this, (function (BaseComponent, EventHandler, SelectorEngine) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap otp-input.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'otpInput';
|
||||
const DATA_KEY = 'bs.otp-input';
|
||||
const EVENT_KEY = `.${DATA_KEY}`;
|
||||
const DATA_API_KEY = '.data-api';
|
||||
const EVENT_COMPLETE = `complete${EVENT_KEY}`;
|
||||
const EVENT_INPUT = `input${EVENT_KEY}`;
|
||||
const SELECTOR_DATA_OTP = '[data-bs-otp]';
|
||||
const SELECTOR_INPUT = 'input';
|
||||
const Default = {
|
||||
length: 6,
|
||||
mask: false
|
||||
};
|
||||
const DefaultType = {
|
||||
length: 'number',
|
||||
mask: 'boolean'
|
||||
};
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class OtpInput extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
super(element, config);
|
||||
this._inputs = SelectorEngine.find(SELECTOR_INPUT, this._element);
|
||||
this._setupInputs();
|
||||
this._addEventListeners();
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default;
|
||||
}
|
||||
static get DefaultType() {
|
||||
return DefaultType;
|
||||
}
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Public
|
||||
getValue() {
|
||||
return this._inputs.map(input => input.value).join('');
|
||||
}
|
||||
setValue(value) {
|
||||
const chars = String(value).split('');
|
||||
for (const [index, input] of this._inputs.entries()) {
|
||||
input.value = chars[index] || '';
|
||||
}
|
||||
this._checkComplete();
|
||||
}
|
||||
clear() {
|
||||
for (const input of this._inputs) {
|
||||
input.value = '';
|
||||
}
|
||||
this._inputs[0]?.focus();
|
||||
}
|
||||
focus() {
|
||||
// Focus first empty input, or last input if all filled
|
||||
const emptyInput = this._inputs.find(input => !input.value);
|
||||
if (emptyInput) {
|
||||
emptyInput.focus();
|
||||
} else {
|
||||
this._inputs.at(-1)?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Private
|
||||
_setupInputs() {
|
||||
for (const input of this._inputs) {
|
||||
// Set attributes for proper OTP handling
|
||||
input.setAttribute('maxlength', '1');
|
||||
input.setAttribute('inputmode', 'numeric');
|
||||
input.setAttribute('pattern', '\\d*');
|
||||
|
||||
// First input gets autocomplete for browser OTP autofill
|
||||
if (input === this._inputs[0]) {
|
||||
input.setAttribute('autocomplete', 'one-time-code');
|
||||
} else {
|
||||
input.setAttribute('autocomplete', 'off');
|
||||
}
|
||||
|
||||
// Mask input if configured
|
||||
if (this._config.mask) {
|
||||
input.setAttribute('type', 'password');
|
||||
}
|
||||
}
|
||||
}
|
||||
_addEventListeners() {
|
||||
for (const [index, input] of this._inputs.entries()) {
|
||||
EventHandler.on(input, 'input', event => this._handleInput(event, index));
|
||||
EventHandler.on(input, 'keydown', event => this._handleKeydown(event, index));
|
||||
EventHandler.on(input, 'paste', event => this._handlePaste(event));
|
||||
EventHandler.on(input, 'focus', event => this._handleFocus(event));
|
||||
}
|
||||
}
|
||||
_handleInput(event, index) {
|
||||
const input = event.target;
|
||||
|
||||
// Only allow digits
|
||||
if (!/^\d*$/.test(input.value)) {
|
||||
input.value = input.value.replace(/\D/g, '');
|
||||
}
|
||||
const {
|
||||
value
|
||||
} = input;
|
||||
|
||||
// Handle multi-character input (some browsers/autofill)
|
||||
if (value.length > 1) {
|
||||
// Distribute characters across inputs
|
||||
const chars = value.split('');
|
||||
input.value = chars[0] || '';
|
||||
for (let i = 1; i < chars.length && index + i < this._inputs.length; i++) {
|
||||
this._inputs[index + i].value = chars[i];
|
||||
}
|
||||
|
||||
// Focus appropriate input
|
||||
const nextIndex = Math.min(index + chars.length, this._inputs.length - 1);
|
||||
this._inputs[nextIndex].focus();
|
||||
} else if (value && index < this._inputs.length - 1) {
|
||||
// Auto-advance to next input
|
||||
this._inputs[index + 1].focus();
|
||||
}
|
||||
EventHandler.trigger(this._element, EVENT_INPUT, {
|
||||
value: this.getValue(),
|
||||
index
|
||||
});
|
||||
this._checkComplete();
|
||||
}
|
||||
_handleKeydown(event, index) {
|
||||
const {
|
||||
key
|
||||
} = event;
|
||||
switch (key) {
|
||||
case 'Backspace':
|
||||
{
|
||||
if (!this._inputs[index].value && index > 0) {
|
||||
// Move to previous input and clear it
|
||||
event.preventDefault();
|
||||
this._inputs[index - 1].value = '';
|
||||
this._inputs[index - 1].focus();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'Delete':
|
||||
{
|
||||
// Clear current and shift remaining values left
|
||||
event.preventDefault();
|
||||
for (let i = index; i < this._inputs.length - 1; i++) {
|
||||
this._inputs[i].value = this._inputs[i + 1].value;
|
||||
}
|
||||
this._inputs.at(-1).value = '';
|
||||
break;
|
||||
}
|
||||
case 'ArrowLeft':
|
||||
{
|
||||
if (index > 0) {
|
||||
event.preventDefault();
|
||||
this._inputs[index - 1].focus();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ArrowRight':
|
||||
{
|
||||
if (index < this._inputs.length - 1) {
|
||||
event.preventDefault();
|
||||
this._inputs[index + 1].focus();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// No default
|
||||
}
|
||||
}
|
||||
_handlePaste(event) {
|
||||
event.preventDefault();
|
||||
const pastedData = (event.clipboardData || window.clipboardData).getData('text');
|
||||
const digits = pastedData.replace(/\D/g, '').slice(0, this._inputs.length);
|
||||
if (digits) {
|
||||
this.setValue(digits);
|
||||
|
||||
// Focus last filled input or last input
|
||||
const lastIndex = Math.min(digits.length, this._inputs.length) - 1;
|
||||
this._inputs[lastIndex].focus();
|
||||
}
|
||||
}
|
||||
_handleFocus(event) {
|
||||
// Select the content on focus for easy replacement
|
||||
event.target.select();
|
||||
}
|
||||
_checkComplete() {
|
||||
const value = this.getValue();
|
||||
const isComplete = value.length === this._inputs.length && this._inputs.every(input => input.value !== '');
|
||||
if (isComplete) {
|
||||
EventHandler.trigger(this._element, EVENT_COMPLETE, {
|
||||
value
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
EventHandler.on(document, `DOMContentLoaded${EVENT_KEY}${DATA_API_KEY}`, () => {
|
||||
for (const element of SelectorEngine.find(SELECTOR_DATA_OTP)) {
|
||||
OtpInput.getOrCreateInstance(element);
|
||||
}
|
||||
});
|
||||
|
||||
return OtpInput;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=otp-input.js.map
|
||||
Vendored
-1
File diff suppressed because one or more lines are too long
Vendored
+4
-40
@@ -4,10 +4,10 @@
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./tooltip.js'), require('./dom/event-handler.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['./tooltip', './dom/event-handler'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Popover = factory(global.Tooltip, global.EventHandler));
|
||||
})(this, (function (Tooltip, EventHandler) { 'use strict';
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./tooltip.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['./tooltip'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Popover = factory(global.Tooltip));
|
||||
})(this, (function (Tooltip) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
@@ -24,10 +24,6 @@
|
||||
const NAME = 'popover';
|
||||
const SELECTOR_TITLE = '.popover-header';
|
||||
const SELECTOR_CONTENT = '.popover-body';
|
||||
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="popover"]';
|
||||
const EVENT_CLICK = 'click';
|
||||
const EVENT_FOCUSIN = 'focusin';
|
||||
const EVENT_MOUSEENTER = 'mouseenter';
|
||||
const Default = {
|
||||
...Tooltip.Default,
|
||||
content: '',
|
||||
@@ -74,38 +70,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation - auto-initialize popovers
|
||||
*/
|
||||
|
||||
const initPopover = event => {
|
||||
const target = event.target.closest(SELECTOR_DATA_TOGGLE);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent default for click events to avoid navigation
|
||||
if (event.type === 'click') {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// Get or create instance
|
||||
const popover = Popover.getOrCreateInstance(target);
|
||||
|
||||
// Trigger the appropriate action based on event type
|
||||
if (event.type === 'click') {
|
||||
popover.toggle();
|
||||
} else if (event.type === 'focusin') {
|
||||
popover._activeTrigger.focus = true;
|
||||
popover._enter();
|
||||
}
|
||||
};
|
||||
|
||||
// Support click (default), hover, and focus triggers
|
||||
EventHandler.on(document, EVENT_CLICK, SELECTOR_DATA_TOGGLE, initPopover);
|
||||
EventHandler.on(document, EVENT_FOCUSIN, SELECTOR_DATA_TOGGLE, initPopover);
|
||||
EventHandler.on(document, EVENT_MOUSEENTER, SELECTOR_DATA_TOGGLE, initPopover);
|
||||
|
||||
return Popover;
|
||||
|
||||
}));
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
-245
@@ -1,245 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap strength.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')) :
|
||||
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Strength = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine));
|
||||
})(this, (function (BaseComponent, EventHandler, SelectorEngine) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap strength.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'strength';
|
||||
const DATA_KEY = 'bs.strength';
|
||||
const EVENT_KEY = `.${DATA_KEY}`;
|
||||
const DATA_API_KEY = '.data-api';
|
||||
const EVENT_STRENGTH_CHANGE = `strengthChange${EVENT_KEY}`;
|
||||
const SELECTOR_DATA_STRENGTH = '[data-bs-strength]';
|
||||
const STRENGTH_LEVELS = ['weak', 'fair', 'good', 'strong'];
|
||||
const Default = {
|
||||
input: null,
|
||||
// Selector or element for password input
|
||||
minLength: 8,
|
||||
messages: {
|
||||
weak: 'Weak',
|
||||
fair: 'Fair',
|
||||
good: 'Good',
|
||||
strong: 'Strong'
|
||||
},
|
||||
weights: {
|
||||
minLength: 1,
|
||||
extraLength: 1,
|
||||
lowercase: 1,
|
||||
uppercase: 1,
|
||||
numbers: 1,
|
||||
special: 1,
|
||||
multipleSpecial: 1,
|
||||
longPassword: 1
|
||||
},
|
||||
thresholds: [2, 4, 6],
|
||||
// weak ≤2, fair ≤4, good ≤6, strong >6
|
||||
scorer: null // Custom scoring function (password) => number
|
||||
};
|
||||
const DefaultType = {
|
||||
input: '(string|element|null)',
|
||||
minLength: 'number',
|
||||
messages: 'object',
|
||||
weights: 'object',
|
||||
thresholds: 'array',
|
||||
scorer: '(function|null)'
|
||||
};
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Strength extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
super(element, config);
|
||||
this._input = this._getInput();
|
||||
this._segments = SelectorEngine.find('.strength-segment', this._element);
|
||||
this._textElement = SelectorEngine.findOne('.strength-text', this._element.parentElement);
|
||||
this._currentStrength = null;
|
||||
if (this._input) {
|
||||
this._addEventListeners();
|
||||
// Check initial value
|
||||
this._evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default;
|
||||
}
|
||||
static get DefaultType() {
|
||||
return DefaultType;
|
||||
}
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Public
|
||||
getStrength() {
|
||||
return this._currentStrength;
|
||||
}
|
||||
evaluate() {
|
||||
this._evaluate();
|
||||
}
|
||||
|
||||
// Private
|
||||
_getInput() {
|
||||
if (this._config.input) {
|
||||
return typeof this._config.input === 'string' ? SelectorEngine.findOne(this._config.input) : this._config.input;
|
||||
}
|
||||
|
||||
// Look for preceding password input
|
||||
const parent = this._element.parentElement;
|
||||
return SelectorEngine.findOne('input[type="password"]', parent);
|
||||
}
|
||||
_addEventListeners() {
|
||||
EventHandler.on(this._input, 'input', () => this._evaluate());
|
||||
EventHandler.on(this._input, 'change', () => this._evaluate());
|
||||
}
|
||||
_evaluate() {
|
||||
const password = this._input.value;
|
||||
const score = this._calculateScore(password);
|
||||
const strength = this._scoreToStrength(score);
|
||||
if (strength !== this._currentStrength) {
|
||||
this._currentStrength = strength;
|
||||
this._updateUI(strength, score);
|
||||
EventHandler.trigger(this._element, EVENT_STRENGTH_CHANGE, {
|
||||
strength,
|
||||
score,
|
||||
password: password.length > 0 ? '***' : '' // Don't expose actual password
|
||||
});
|
||||
}
|
||||
}
|
||||
_calculateScore(password) {
|
||||
if (!password) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Use custom scorer if provided
|
||||
if (typeof this._config.scorer === 'function') {
|
||||
return this._config.scorer(password);
|
||||
}
|
||||
const {
|
||||
weights
|
||||
} = this._config;
|
||||
let score = 0;
|
||||
|
||||
// Length scoring
|
||||
if (password.length >= this._config.minLength) {
|
||||
score += weights.minLength;
|
||||
}
|
||||
if (password.length >= this._config.minLength + 4) {
|
||||
score += weights.extraLength;
|
||||
}
|
||||
|
||||
// Character variety
|
||||
if (/[a-z]/.test(password)) {
|
||||
score += weights.lowercase;
|
||||
}
|
||||
if (/[A-Z]/.test(password)) {
|
||||
score += weights.uppercase;
|
||||
}
|
||||
if (/\d/.test(password)) {
|
||||
score += weights.numbers;
|
||||
}
|
||||
|
||||
// Special characters
|
||||
if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
|
||||
score += weights.special;
|
||||
}
|
||||
|
||||
// Extra points for more special chars or length
|
||||
if (/[!@#$%^&*(),.?":{}|<>].*[!@#$%^&*(),.?":{}|<>]/.test(password)) {
|
||||
score += weights.multipleSpecial;
|
||||
}
|
||||
if (password.length >= 16) {
|
||||
score += weights.longPassword;
|
||||
}
|
||||
return score;
|
||||
}
|
||||
_scoreToStrength(score) {
|
||||
if (score === 0) {
|
||||
return null;
|
||||
}
|
||||
const [weak, fair, good] = this._config.thresholds;
|
||||
if (score <= weak) {
|
||||
return 'weak';
|
||||
}
|
||||
if (score <= fair) {
|
||||
return 'fair';
|
||||
}
|
||||
if (score <= good) {
|
||||
return 'good';
|
||||
}
|
||||
return 'strong';
|
||||
}
|
||||
_updateUI(strength) {
|
||||
// Update data attribute on element
|
||||
if (strength) {
|
||||
this._element.dataset.bsStrength = strength;
|
||||
} else {
|
||||
delete this._element.dataset.bsStrength;
|
||||
}
|
||||
|
||||
// Update segmented meter
|
||||
const strengthIndex = strength ? STRENGTH_LEVELS.indexOf(strength) : -1;
|
||||
for (const [index, segment] of this._segments.entries()) {
|
||||
if (index <= strengthIndex) {
|
||||
segment.classList.add('active');
|
||||
} else {
|
||||
segment.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
// Update text feedback
|
||||
if (this._textElement) {
|
||||
if (strength && this._config.messages[strength]) {
|
||||
this._textElement.textContent = this._config.messages[strength];
|
||||
this._textElement.dataset.bsStrength = strength;
|
||||
|
||||
// Also set the color via inheriting from parent or using CSS variable
|
||||
const colorMap = {
|
||||
weak: 'danger',
|
||||
fair: 'warning',
|
||||
good: 'info',
|
||||
strong: 'success'
|
||||
};
|
||||
this._textElement.style.setProperty('--strength-color', `var(--${colorMap[strength]}-text)`);
|
||||
} else {
|
||||
this._textElement.textContent = '';
|
||||
delete this._textElement.dataset.bsStrength;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
EventHandler.on(document, `DOMContentLoaded${EVENT_KEY}${DATA_API_KEY}`, () => {
|
||||
for (const element of SelectorEngine.find(SELECTOR_DATA_STRENGTH)) {
|
||||
Strength.getOrCreateInstance(element);
|
||||
}
|
||||
});
|
||||
|
||||
return Strength;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=strength.js.map
|
||||
Vendored
-1
File diff suppressed because one or more lines are too long
Vendored
-98
@@ -1,98 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap toggler.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('./util/component-functions.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './util/component-functions'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Toggler = factory(global.BaseComponent, global.EventHandler, global.ComponentFunctions));
|
||||
})(this, (function (BaseComponent, EventHandler, componentFunctions_js) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap toggler.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const NAME = 'toggler';
|
||||
const DATA_KEY = 'bs.toggler';
|
||||
const EVENT_KEY = `.${DATA_KEY}`;
|
||||
const EVENT_TOGGLE = `toggle${EVENT_KEY}`;
|
||||
const EVENT_TOGGLED = `toggled${EVENT_KEY}`;
|
||||
const EVENT_CLICK = 'click';
|
||||
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="toggler"]';
|
||||
const DefaultType = {
|
||||
attribute: 'string',
|
||||
value: '(string|number|boolean)'
|
||||
};
|
||||
const Default = {
|
||||
attribute: 'class',
|
||||
value: null
|
||||
};
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
*/
|
||||
|
||||
class Toggler extends BaseComponent {
|
||||
// Getters
|
||||
static get Default() {
|
||||
return Default;
|
||||
}
|
||||
static get DefaultType() {
|
||||
return DefaultType;
|
||||
}
|
||||
static get NAME() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
// Public
|
||||
toggle() {
|
||||
const toggleEvent = EventHandler.trigger(this._element, EVENT_TOGGLE);
|
||||
if (toggleEvent.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
this._execute();
|
||||
EventHandler.trigger(this._element, EVENT_TOGGLED);
|
||||
}
|
||||
|
||||
// Private
|
||||
_execute() {
|
||||
const {
|
||||
attribute,
|
||||
value
|
||||
} = this._config;
|
||||
if (attribute === 'id') {
|
||||
return; // You have to be kidding
|
||||
}
|
||||
if (attribute === 'class') {
|
||||
this._element.classList.toggle(value);
|
||||
return;
|
||||
}
|
||||
|
||||
// Compare as strings since getAttribute() always returns a string
|
||||
if (this._element.getAttribute(attribute) === String(value)) {
|
||||
this._element.removeAttribute(attribute);
|
||||
return;
|
||||
}
|
||||
this._element.setAttribute(attribute, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation
|
||||
*/
|
||||
|
||||
componentFunctions_js.eventActionOnPlugin(Toggler, EVENT_CLICK, SELECTOR_DATA_TOGGLE, 'toggle');
|
||||
|
||||
return Toggler;
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=toggler.js.map
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"toggler.js","sources":["../src/toggler.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap toggler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport BaseComponent from './base-component.js'\nimport EventHandler from './dom/event-handler.js'\nimport { eventActionOnPlugin } from './util/component-functions.js'\n\n/**\n * Constants\n */\n\nconst NAME = 'toggler'\nconst DATA_KEY = 'bs.toggler'\nconst EVENT_KEY = `.${DATA_KEY}`\n\nconst EVENT_TOGGLE = `toggle${EVENT_KEY}`\nconst EVENT_TOGGLED = `toggled${EVENT_KEY}`\nconst EVENT_CLICK = 'click'\n\nconst SELECTOR_DATA_TOGGLE = '[data-bs-toggle=\"toggler\"]'\n\nconst DefaultType = {\n attribute: 'string',\n value: '(string|number|boolean)'\n}\n\nconst Default = {\n attribute: 'class',\n value: null\n}\n\n/**\n * Class definition\n */\n\nclass Toggler extends BaseComponent {\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n toggle() {\n const toggleEvent = EventHandler.trigger(this._element, EVENT_TOGGLE)\n\n if (toggleEvent.defaultPrevented) {\n return\n }\n\n this._execute()\n\n EventHandler.trigger(this._element, EVENT_TOGGLED)\n }\n\n // Private\n _execute() {\n const { attribute, value } = this._config\n\n if (attribute === 'id') {\n return // You have to be kidding\n }\n\n if (attribute === 'class') {\n this._element.classList.toggle(value)\n return\n }\n\n // Compare as strings since getAttribute() always returns a string\n if (this._element.getAttribute(attribute) === String(value)) {\n this._element.removeAttribute(attribute)\n return\n }\n\n this._element.setAttribute(attribute, value)\n }\n}\n\n/**\n * Data API implementation\n */\n\neventActionOnPlugin(Toggler, EVENT_CLICK, SELECTOR_DATA_TOGGLE, 'toggle')\n\nexport default Toggler\n"],"names":["NAME","DATA_KEY","EVENT_KEY","EVENT_TOGGLE","EVENT_TOGGLED","EVENT_CLICK","SELECTOR_DATA_TOGGLE","DefaultType","attribute","value","Default","Toggler","BaseComponent","toggle","toggleEvent","EventHandler","trigger","_element","defaultPrevented","_execute","_config","classList","getAttribute","String","removeAttribute","setAttribute","eventActionOnPlugin"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;;EAMA;EACA;EACA;;EAEA,MAAMA,IAAI,GAAG,SAAS;EACtB,MAAMC,QAAQ,GAAG,YAAY;EAC7B,MAAMC,SAAS,GAAG,CAAA,CAAA,EAAID,QAAQ,CAAA,CAAE;EAEhC,MAAME,YAAY,GAAG,CAAA,MAAA,EAASD,SAAS,CAAA,CAAE;EACzC,MAAME,aAAa,GAAG,CAAA,OAAA,EAAUF,SAAS,CAAA,CAAE;EAC3C,MAAMG,WAAW,GAAG,OAAO;EAE3B,MAAMC,oBAAoB,GAAG,4BAA4B;EAEzD,MAAMC,WAAW,GAAG;EAClBC,EAAAA,SAAS,EAAE,QAAQ;EACnBC,EAAAA,KAAK,EAAE;EACT,CAAC;EAED,MAAMC,OAAO,GAAG;EACdF,EAAAA,SAAS,EAAE,OAAO;EAClBC,EAAAA,KAAK,EAAE;EACT,CAAC;;EAED;EACA;EACA;;EAEA,MAAME,OAAO,SAASC,aAAa,CAAC;EAClC;IACA,WAAWF,OAAOA,GAAG;EACnB,IAAA,OAAOA,OAAO;EAChB,EAAA;IAEA,WAAWH,WAAWA,GAAG;EACvB,IAAA,OAAOA,WAAW;EACpB,EAAA;IAEA,WAAWP,IAAIA,GAAG;EAChB,IAAA,OAAOA,IAAI;EACb,EAAA;;EAEA;EACAa,EAAAA,MAAMA,GAAG;MACP,MAAMC,WAAW,GAAGC,YAAY,CAACC,OAAO,CAAC,IAAI,CAACC,QAAQ,EAAEd,YAAY,CAAC;MAErE,IAAIW,WAAW,CAACI,gBAAgB,EAAE;EAChC,MAAA;EACF,IAAA;MAEA,IAAI,CAACC,QAAQ,EAAE;MAEfJ,YAAY,CAACC,OAAO,CAAC,IAAI,CAACC,QAAQ,EAAEb,aAAa,CAAC;EACpD,EAAA;;EAEA;EACAe,EAAAA,QAAQA,GAAG;MACT,MAAM;QAAEX,SAAS;EAAEC,MAAAA;OAAO,GAAG,IAAI,CAACW,OAAO;MAEzC,IAAIZ,SAAS,KAAK,IAAI,EAAE;EACtB,MAAA,OAAM;EACR,IAAA;MAEA,IAAIA,SAAS,KAAK,OAAO,EAAE;QACzB,IAAI,CAACS,QAAQ,CAACI,SAAS,CAACR,MAAM,CAACJ,KAAK,CAAC;EACrC,MAAA;EACF,IAAA;;EAEA;EACA,IAAA,IAAI,IAAI,CAACQ,QAAQ,CAACK,YAAY,CAACd,SAAS,CAAC,KAAKe,MAAM,CAACd,KAAK,CAAC,EAAE;EAC3D,MAAA,IAAI,CAACQ,QAAQ,CAACO,eAAe,CAAChB,SAAS,CAAC;EACxC,MAAA;EACF,IAAA;MAEA,IAAI,CAACS,QAAQ,CAACQ,YAAY,CAACjB,SAAS,EAAEC,KAAK,CAAC;EAC9C,EAAA;EACF;;EAEA;EACA;EACA;;AAEAiB,2CAAmB,CAACf,OAAO,EAAEN,WAAW,EAAEC,oBAAoB,EAAE,QAAQ,CAAC;;;;;;;;"}
|
||||
Vendored
+81
-187
@@ -4,10 +4,29 @@
|
||||
* 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('./util/index.js'), require('./util/sanitizer.js'), require('./util/template-factory.js'), require('./util/floating-ui.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['@floating-ui/dom', './base-component', './dom/event-handler', './dom/manipulator', './util/index', './util/sanitizer', './util/template-factory', './util/floating-ui'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Tooltip = factory(global["@floating-ui/dom"], global.BaseComponent, global.EventHandler, global.Manipulator, global.Index, global.Sanitizer, global.TemplateFactory, global.FloatingUi));
|
||||
})(this, (function (dom, BaseComponent, EventHandler, Manipulator, index_js, sanitizer_js, TemplateFactory, floatingUi_js) { 'use strict';
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@popperjs/core'), require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./util/index.js'), require('./util/sanitizer.js'), require('./util/template-factory.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['@popperjs/core', './base-component', './dom/event-handler', './dom/manipulator', './util/index', './util/sanitizer', './util/template-factory'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Tooltip = factory(global["@popperjs/core"], global.BaseComponent, global.EventHandler, global.Manipulator, global.Index, global.Sanitizer, global.TemplateFactory));
|
||||
})(this, (function (Popper, BaseComponent, EventHandler, Manipulator, index_js, sanitizer_js, TemplateFactory) { 'use strict';
|
||||
|
||||
function _interopNamespaceDefault(e) {
|
||||
const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });
|
||||
if (e) {
|
||||
for (const k in e) {
|
||||
if (k !== 'default') {
|
||||
const d = Object.getOwnPropertyDescriptor(e, k);
|
||||
Object.defineProperty(n, k, d.get ? d : {
|
||||
enumerable: true,
|
||||
get: () => e[k]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
n.default = e;
|
||||
return Object.freeze(n);
|
||||
}
|
||||
|
||||
const Popper__namespace = /*#__PURE__*/_interopNamespaceDefault(Popper);
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
@@ -28,7 +47,6 @@
|
||||
const CLASS_NAME_SHOW = 'show';
|
||||
const SELECTOR_TOOLTIP_INNER = '.tooltip-inner';
|
||||
const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`;
|
||||
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="tooltip"]';
|
||||
const EVENT_MODAL_HIDE = 'hide.bs.modal';
|
||||
const TRIGGER_HOVER = 'hover';
|
||||
const TRIGGER_FOCUS = 'focus';
|
||||
@@ -62,7 +80,7 @@
|
||||
html: false,
|
||||
offset: [0, 6],
|
||||
placement: 'top',
|
||||
floatingConfig: null,
|
||||
popperConfig: null,
|
||||
sanitize: true,
|
||||
sanitizeFn: null,
|
||||
selector: false,
|
||||
@@ -81,7 +99,7 @@
|
||||
html: 'boolean',
|
||||
offset: '(array|string|function)',
|
||||
placement: '(string|function)',
|
||||
floatingConfig: '(null|object|function)',
|
||||
popperConfig: '(null|object|function)',
|
||||
sanitize: 'boolean',
|
||||
sanitizeFn: '(null|function)',
|
||||
selector: '(string|boolean)',
|
||||
@@ -96,8 +114,8 @@
|
||||
|
||||
class Tooltip extends BaseComponent {
|
||||
constructor(element, config) {
|
||||
if (typeof dom.computePosition === 'undefined') {
|
||||
throw new TypeError('Bootstrap\'s tooltips require Floating UI (https://floating-ui.com)');
|
||||
if (typeof Popper__namespace === 'undefined') {
|
||||
throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org/docs/v2/)');
|
||||
}
|
||||
super(element, config);
|
||||
|
||||
@@ -106,15 +124,12 @@
|
||||
this._timeout = 0;
|
||||
this._isHovered = null;
|
||||
this._activeTrigger = {};
|
||||
this._floatingCleanup = null;
|
||||
this._popper = null;
|
||||
this._templateFactory = null;
|
||||
this._newContent = null;
|
||||
this._mediaQueryListeners = [];
|
||||
this._responsivePlacements = null;
|
||||
|
||||
// Protected
|
||||
this.tip = null;
|
||||
this._parseResponsivePlacements();
|
||||
this._setListeners();
|
||||
if (!this._config.selector) {
|
||||
this._fixTitle();
|
||||
@@ -158,11 +173,10 @@
|
||||
if (this._element.getAttribute('data-bs-original-title')) {
|
||||
this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'));
|
||||
}
|
||||
this._disposeFloating();
|
||||
this._disposeMediaQueryListeners();
|
||||
this._disposePopper();
|
||||
super.dispose();
|
||||
}
|
||||
async show() {
|
||||
show() {
|
||||
if (this._element.style.display === 'none') {
|
||||
throw new Error('Please use show on visible elements');
|
||||
}
|
||||
@@ -175,7 +189,9 @@
|
||||
if (showEvent.defaultPrevented || !isInTheDom) {
|
||||
return;
|
||||
}
|
||||
this._disposeFloating();
|
||||
|
||||
// TODO: v6 remove this or make it optional
|
||||
this._disposePopper();
|
||||
const tip = this._getTipElement();
|
||||
this._element.setAttribute('aria-describedby', tip.getAttribute('id'));
|
||||
const {
|
||||
@@ -185,7 +201,7 @@
|
||||
container.append(tip);
|
||||
EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED));
|
||||
}
|
||||
await this._createFloating(tip);
|
||||
this._popper = this._createPopper(tip);
|
||||
tip.classList.add(CLASS_NAME_SHOW);
|
||||
|
||||
// If this is a touch-enabled device we add extra
|
||||
@@ -234,7 +250,7 @@
|
||||
return;
|
||||
}
|
||||
if (!this._isHovered) {
|
||||
this._disposeFloating();
|
||||
this._disposePopper();
|
||||
}
|
||||
this._element.removeAttribute('aria-describedby');
|
||||
EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN));
|
||||
@@ -242,8 +258,8 @@
|
||||
this._queueCallback(complete, this.tip, this._isAnimated());
|
||||
}
|
||||
update() {
|
||||
if (this._floatingCleanup && this.tip) {
|
||||
this._updateFloatingPosition();
|
||||
if (this._popper) {
|
||||
this._popper.update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +293,7 @@
|
||||
setContent(content) {
|
||||
this._newContent = content;
|
||||
if (this._isShown()) {
|
||||
this._disposeFloating();
|
||||
this._disposePopper();
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
@@ -314,103 +330,10 @@
|
||||
_isShown() {
|
||||
return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW);
|
||||
}
|
||||
_getPlacement(tip) {
|
||||
// If we have responsive placements, get the one for current viewport
|
||||
if (this._responsivePlacements) {
|
||||
const placement = floatingUi_js.getResponsivePlacement(this._responsivePlacements, 'top');
|
||||
return AttachmentMap[placement.toUpperCase()] || placement;
|
||||
}
|
||||
|
||||
// Execute placement (can be a function)
|
||||
_createPopper(tip) {
|
||||
const placement = index_js.execute(this._config.placement, [this, tip, this._element]);
|
||||
return AttachmentMap[placement.toUpperCase()] || placement;
|
||||
}
|
||||
_parseResponsivePlacements() {
|
||||
// Only parse if placement is a string (not a function)
|
||||
if (typeof this._config.placement !== 'string') {
|
||||
this._responsivePlacements = null;
|
||||
return;
|
||||
}
|
||||
this._responsivePlacements = floatingUi_js.parseResponsivePlacement(this._config.placement, 'top');
|
||||
if (this._responsivePlacements) {
|
||||
this._setupMediaQueryListeners();
|
||||
}
|
||||
}
|
||||
_setupMediaQueryListeners() {
|
||||
this._disposeMediaQueryListeners();
|
||||
this._mediaQueryListeners = floatingUi_js.createBreakpointListeners(() => {
|
||||
if (this._isShown()) {
|
||||
this._updateFloatingPosition();
|
||||
}
|
||||
});
|
||||
}
|
||||
_disposeMediaQueryListeners() {
|
||||
floatingUi_js.disposeBreakpointListeners(this._mediaQueryListeners);
|
||||
this._mediaQueryListeners = [];
|
||||
}
|
||||
async _createFloating(tip) {
|
||||
const placement = this._getPlacement(tip);
|
||||
const arrowElement = tip.querySelector(`.${this.constructor.NAME}-arrow`);
|
||||
|
||||
// Initial position update
|
||||
await this._updateFloatingPosition(tip, placement, arrowElement);
|
||||
|
||||
// Set up auto-update for scroll/resize
|
||||
this._floatingCleanup = dom.autoUpdate(this._element, tip, () => this._updateFloatingPosition(tip, null, arrowElement));
|
||||
}
|
||||
async _updateFloatingPosition(tip = this.tip, placement = null, arrowElement = null) {
|
||||
if (!tip) {
|
||||
return;
|
||||
}
|
||||
if (!placement) {
|
||||
placement = this._getPlacement(tip);
|
||||
}
|
||||
if (!arrowElement) {
|
||||
arrowElement = tip.querySelector(`.${this.constructor.NAME}-arrow`);
|
||||
}
|
||||
const middleware = this._getFloatingMiddleware(arrowElement);
|
||||
const floatingConfig = this._getFloatingConfig(placement, middleware);
|
||||
const {
|
||||
x,
|
||||
y,
|
||||
placement: finalPlacement,
|
||||
middlewareData
|
||||
} = await dom.computePosition(this._element, tip, floatingConfig);
|
||||
|
||||
// Apply position to tooltip
|
||||
Object.assign(tip.style, {
|
||||
position: 'absolute',
|
||||
left: `${x}px`,
|
||||
top: `${y}px`
|
||||
});
|
||||
|
||||
// Ensure arrow is absolutely positioned within tooltip
|
||||
if (arrowElement) {
|
||||
arrowElement.style.position = 'absolute';
|
||||
}
|
||||
|
||||
// Set placement attribute for CSS arrow styling
|
||||
Manipulator.setDataAttribute(tip, 'placement', finalPlacement);
|
||||
|
||||
// Position arrow along the edge (center it) if present
|
||||
// The CSS handles which edge to place it on via data-bs-placement
|
||||
if (arrowElement && middlewareData.arrow) {
|
||||
const {
|
||||
x: arrowX,
|
||||
y: arrowY
|
||||
} = middlewareData.arrow;
|
||||
const isVertical = finalPlacement.startsWith('top') || finalPlacement.startsWith('bottom');
|
||||
|
||||
// Only set the cross-axis position (centering along the edge)
|
||||
// The main-axis position (which edge) is handled by CSS
|
||||
Object.assign(arrowElement.style, {
|
||||
left: isVertical && arrowX !== null ? `${arrowX}px` : '',
|
||||
top: !isVertical && arrowY !== null ? `${arrowY}px` : '',
|
||||
// Reset the other axis to let CSS handle it
|
||||
right: '',
|
||||
bottom: ''
|
||||
});
|
||||
}
|
||||
const attachment = AttachmentMap[placement.toUpperCase()];
|
||||
return Popper__namespace.createPopper(this._element, tip, this._getPopperConfig(attachment));
|
||||
}
|
||||
_getOffset() {
|
||||
const {
|
||||
@@ -420,57 +343,50 @@
|
||||
return offset.split(',').map(value => Number.parseInt(value, 10));
|
||||
}
|
||||
if (typeof offset === 'function') {
|
||||
// Floating UI passes different args, adapt the interface for offset function callbacks
|
||||
return ({
|
||||
placement,
|
||||
rects
|
||||
}) => {
|
||||
const result = offset({
|
||||
placement,
|
||||
reference: rects.reference,
|
||||
floating: rects.floating
|
||||
}, this._element);
|
||||
return result;
|
||||
};
|
||||
return popperData => offset(popperData, this._element);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
_resolvePossibleFunction(arg) {
|
||||
return index_js.execute(arg, [this._element, this._element]);
|
||||
}
|
||||
_getFloatingMiddleware(arrowElement) {
|
||||
const offsetValue = this._getOffset();
|
||||
const middleware = [
|
||||
// Offset middleware - handles distance from reference
|
||||
dom.offset(typeof offsetValue === 'function' ? offsetValue : {
|
||||
mainAxis: offsetValue[1] || 0,
|
||||
crossAxis: offsetValue[0] || 0
|
||||
}),
|
||||
// Flip middleware - handles fallback placements
|
||||
dom.flip({
|
||||
fallbackPlacements: this._config.fallbackPlacements
|
||||
}),
|
||||
// Shift middleware - prevents overflow
|
||||
dom.shift({
|
||||
boundary: this._config.boundary === 'clippingParents' ? 'clippingAncestors' : this._config.boundary
|
||||
})];
|
||||
|
||||
// Arrow middleware - positions the arrow element
|
||||
if (arrowElement) {
|
||||
middleware.push(dom.arrow({
|
||||
element: arrowElement
|
||||
}));
|
||||
}
|
||||
return middleware;
|
||||
}
|
||||
_getFloatingConfig(placement, middleware) {
|
||||
const defaultConfig = {
|
||||
placement,
|
||||
middleware
|
||||
_getPopperConfig(attachment) {
|
||||
const defaultBsPopperConfig = {
|
||||
placement: attachment,
|
||||
modifiers: [{
|
||||
name: 'flip',
|
||||
options: {
|
||||
fallbackPlacements: this._config.fallbackPlacements
|
||||
}
|
||||
}, {
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: this._getOffset()
|
||||
}
|
||||
}, {
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
boundary: this._config.boundary
|
||||
}
|
||||
}, {
|
||||
name: 'arrow',
|
||||
options: {
|
||||
element: `.${this.constructor.NAME}-arrow`
|
||||
}
|
||||
}, {
|
||||
name: 'preSetPlacement',
|
||||
enabled: true,
|
||||
phase: 'beforeMain',
|
||||
fn: data => {
|
||||
// Pre-set Popper's placement attribute in order to read the arrow sizes properly.
|
||||
// Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement
|
||||
this._getTipElement().setAttribute('data-popper-placement', data.state.placement);
|
||||
}
|
||||
}]
|
||||
};
|
||||
return {
|
||||
...defaultConfig,
|
||||
...index_js.execute(this._config.floatingConfig, [undefined, defaultConfig])
|
||||
...defaultBsPopperConfig,
|
||||
...index_js.execute(this._config.popperConfig, [undefined, defaultBsPopperConfig])
|
||||
};
|
||||
}
|
||||
_setListeners() {
|
||||
@@ -592,10 +508,10 @@
|
||||
// `Object.fromEntries(keysWithDifferentValues)`
|
||||
return config;
|
||||
}
|
||||
_disposeFloating() {
|
||||
if (this._floatingCleanup) {
|
||||
this._floatingCleanup();
|
||||
this._floatingCleanup = null;
|
||||
_disposePopper() {
|
||||
if (this._popper) {
|
||||
this._popper.destroy();
|
||||
this._popper = null;
|
||||
}
|
||||
if (this.tip) {
|
||||
this.tip.remove();
|
||||
@@ -604,28 +520,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data API implementation - auto-initialize tooltips
|
||||
*/
|
||||
|
||||
const initTooltip = event => {
|
||||
const target = event.target.closest(SELECTOR_DATA_TOGGLE);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get or create instance and trigger the appropriate action
|
||||
const tooltip = Tooltip.getOrCreateInstance(target);
|
||||
|
||||
// For focus events, manually trigger enter to show
|
||||
if (event.type === 'focusin') {
|
||||
tooltip._activeTrigger.focus = true;
|
||||
tooltip._enter();
|
||||
}
|
||||
};
|
||||
EventHandler.on(document, EVENT_FOCUSIN, SELECTOR_DATA_TOGGLE, initTooltip);
|
||||
EventHandler.on(document, EVENT_MOUSEENTER, SELECTOR_DATA_TOGGLE, initTooltip);
|
||||
|
||||
return Tooltip;
|
||||
|
||||
}));
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
-29
@@ -33,37 +33,8 @@
|
||||
instance[method]();
|
||||
});
|
||||
};
|
||||
const eventActionOnPlugin = (Plugin, onEvent, stringSelector, method, callback = null) => {
|
||||
eventAction(`${onEvent}.${Plugin.NAME}`, stringSelector, data => {
|
||||
const instances = data.targets.filter(Boolean).map(element => Plugin.getOrCreateInstance(element));
|
||||
if (typeof callback === 'function') {
|
||||
callback({
|
||||
...data,
|
||||
instances
|
||||
});
|
||||
}
|
||||
for (const instance of instances) {
|
||||
instance[method]();
|
||||
}
|
||||
});
|
||||
};
|
||||
const eventAction = (onEvent, stringSelector, callback) => {
|
||||
const selector = `${stringSelector}:not(.disabled):not(:disabled)`;
|
||||
EventHandler.on(document, onEvent, selector, function (event) {
|
||||
if (['A', 'AREA'].includes(this.tagName)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
const selector = SelectorEngine.getSelectorFromElement(this);
|
||||
const targets = selector ? SelectorEngine.find(selector) : [this];
|
||||
callback({
|
||||
targets,
|
||||
event
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.enableDismissTrigger = enableDismissTrigger;
|
||||
exports.eventActionOnPlugin = eventActionOnPlugin;
|
||||
|
||||
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"version":3,"file":"component-functions.js","sources":["../../src/util/component-functions.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isDisabled } from './index.js'\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`\n const name = component.NAME\n\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)\n const instance = component.getOrCreateInstance(target)\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]()\n })\n}\n\nconst eventActionOnPlugin = (Plugin, onEvent, stringSelector, method, callback = null) => {\n eventAction(`${onEvent}.${Plugin.NAME}`, stringSelector, data => {\n const instances = data.targets.filter(Boolean).map(element => Plugin.getOrCreateInstance(element))\n if (typeof callback === 'function') {\n callback({ ...data, instances })\n }\n\n for (const instance of instances) {\n instance[method]()\n }\n })\n}\n\nconst eventAction = (onEvent, stringSelector, callback) => {\n const selector = `${stringSelector}:not(.disabled):not(:disabled)`\n EventHandler.on(document, onEvent, selector, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n const selector = SelectorEngine.getSelectorFromElement(this)\n const targets = selector ? SelectorEngine.find(selector) : [this]\n\n callback({ targets, event })\n })\n}\n\nexport {\n enableDismissTrigger,\n eventActionOnPlugin\n}\n"],"names":["enableDismissTrigger","component","method","clickEvent","EVENT_KEY","name","NAME","EventHandler","on","document","event","includes","tagName","preventDefault","isDisabled","target","SelectorEngine","getElementFromSelector","closest","instance","getOrCreateInstance","eventActionOnPlugin","Plugin","onEvent","stringSelector","callback","eventAction","data","instances","targets","filter","Boolean","map","element","selector","getSelectorFromElement","find"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;AAMA,QAAMA,oBAAoB,GAAGA,CAACC,SAAS,EAAEC,MAAM,GAAG,MAAM,KAAK;EAC3D,EAAA,MAAMC,UAAU,GAAG,CAAA,aAAA,EAAgBF,SAAS,CAACG,SAAS,CAAA,CAAE;EACxD,EAAA,MAAMC,IAAI,GAAGJ,SAAS,CAACK,IAAI;EAE3BC,EAAAA,YAAY,CAACC,EAAE,CAACC,QAAQ,EAAEN,UAAU,EAAE,CAAA,kBAAA,EAAqBE,IAAI,CAAA,EAAA,CAAI,EAAE,UAAUK,KAAK,EAAE;EACpF,IAAA,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAACC,QAAQ,CAAC,IAAI,CAACC,OAAO,CAAC,EAAE;QACxCF,KAAK,CAACG,cAAc,EAAE;EACxB,IAAA;EAEA,IAAA,IAAIC,mBAAU,CAAC,IAAI,CAAC,EAAE;EACpB,MAAA;EACF,IAAA;EAEA,IAAA,MAAMC,MAAM,GAAGC,cAAc,CAACC,sBAAsB,CAAC,IAAI,CAAC,IAAI,IAAI,CAACC,OAAO,CAAC,CAAA,CAAA,EAAIb,IAAI,EAAE,CAAC;EACtF,IAAA,MAAMc,QAAQ,GAAGlB,SAAS,CAACmB,mBAAmB,CAACL,MAAM,CAAC;;EAEtD;EACAI,IAAAA,QAAQ,CAACjB,MAAM,CAAC,EAAE;EACpB,EAAA,CAAC,CAAC;EACJ;AAEA,QAAMmB,mBAAmB,GAAGA,CAACC,MAAM,EAAEC,OAAO,EAAEC,cAAc,EAAEtB,MAAM,EAAEuB,QAAQ,GAAG,IAAI,KAAK;EACxFC,EAAAA,WAAW,CAAC,CAAA,EAAGH,OAAO,CAAA,CAAA,EAAID,MAAM,CAAChB,IAAI,CAAA,CAAE,EAAEkB,cAAc,EAAEG,IAAI,IAAI;MAC/D,MAAMC,SAAS,GAAGD,IAAI,CAACE,OAAO,CAACC,MAAM,CAACC,OAAO,CAAC,CAACC,GAAG,CAACC,OAAO,IAAIX,MAAM,CAACF,mBAAmB,CAACa,OAAO,CAAC,CAAC;EAClG,IAAA,IAAI,OAAOR,QAAQ,KAAK,UAAU,EAAE;EAClCA,MAAAA,QAAQ,CAAC;EAAE,QAAA,GAAGE,IAAI;EAAEC,QAAAA;EAAU,OAAC,CAAC;EAClC,IAAA;EAEA,IAAA,KAAK,MAAMT,QAAQ,IAAIS,SAAS,EAAE;EAChCT,MAAAA,QAAQ,CAACjB,MAAM,CAAC,EAAE;EACpB,IAAA;EACF,EAAA,CAAC,CAAC;EACJ;EAEA,MAAMwB,WAAW,GAAGA,CAACH,OAAO,EAAEC,cAAc,EAAEC,QAAQ,KAAK;EACzD,EAAA,MAAMS,QAAQ,GAAG,CAAA,EAAGV,cAAc,CAAA,8BAAA,CAAgC;IAClEjB,YAAY,CAACC,EAAE,CAACC,QAAQ,EAAEc,OAAO,EAAEW,QAAQ,EAAE,UAAUxB,KAAK,EAAE;EAC5D,IAAA,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAACC,QAAQ,CAAC,IAAI,CAACC,OAAO,CAAC,EAAE;QACxCF,KAAK,CAACG,cAAc,EAAE;EACxB,IAAA;EAEA,IAAA,MAAMqB,QAAQ,GAAGlB,cAAc,CAACmB,sBAAsB,CAAC,IAAI,CAAC;EAC5D,IAAA,MAAMN,OAAO,GAAGK,QAAQ,GAAGlB,cAAc,CAACoB,IAAI,CAACF,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;EAEjET,IAAAA,QAAQ,CAAC;QAAEI,OAAO;EAAEnB,MAAAA;EAAM,KAAC,CAAC;EAC9B,EAAA,CAAC,CAAC;EACJ,CAAC;;;;;;;;;;;"}
|
||||
{"version":3,"file":"component-functions.js","sources":["../../src/util/component-functions.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isDisabled } from './index.js'\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`\n const name = component.NAME\n\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)\n const instance = component.getOrCreateInstance(target)\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]()\n })\n}\n\nexport {\n enableDismissTrigger\n}\n"],"names":["enableDismissTrigger","component","method","clickEvent","EVENT_KEY","name","NAME","EventHandler","on","document","event","includes","tagName","preventDefault","isDisabled","target","SelectorEngine","getElementFromSelector","closest","instance","getOrCreateInstance"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;AAMA,QAAMA,oBAAoB,GAAGA,CAACC,SAAS,EAAEC,MAAM,GAAG,MAAM,KAAK;EAC3D,EAAA,MAAMC,UAAU,GAAG,CAAA,aAAA,EAAgBF,SAAS,CAACG,SAAS,CAAA,CAAE;EACxD,EAAA,MAAMC,IAAI,GAAGJ,SAAS,CAACK,IAAI;EAE3BC,EAAAA,YAAY,CAACC,EAAE,CAACC,QAAQ,EAAEN,UAAU,EAAE,CAAA,kBAAA,EAAqBE,IAAI,CAAA,EAAA,CAAI,EAAE,UAAUK,KAAK,EAAE;EACpF,IAAA,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAACC,QAAQ,CAAC,IAAI,CAACC,OAAO,CAAC,EAAE;QACxCF,KAAK,CAACG,cAAc,EAAE;EACxB,IAAA;EAEA,IAAA,IAAIC,mBAAU,CAAC,IAAI,CAAC,EAAE;EACpB,MAAA;EACF,IAAA;EAEA,IAAA,MAAMC,MAAM,GAAGC,cAAc,CAACC,sBAAsB,CAAC,IAAI,CAAC,IAAI,IAAI,CAACC,OAAO,CAAC,CAAA,CAAA,EAAIb,IAAI,EAAE,CAAC;EACtF,IAAA,MAAMc,QAAQ,GAAGlB,SAAS,CAACmB,mBAAmB,CAACL,MAAM,CAAC;;EAEtD;EACAI,IAAAA,QAAQ,CAACjB,MAAM,CAAC,EAAE;EACpB,EAAA,CAAC,CAAC;EACJ;;;;;;;;;;"}
|
||||
Vendored
-151
@@ -1,151 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap floating-ui.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' ? factory(exports, require('./index.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['exports', './index'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.FloatingUi = {}, global.Index));
|
||||
})(this, (function (exports, index_js) { 'use strict';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap util/floating-ui.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Breakpoints for responsive placement (matches SCSS $grid-breakpoints)
|
||||
*/
|
||||
const BREAKPOINTS = {
|
||||
sm: 576,
|
||||
md: 768,
|
||||
lg: 1024,
|
||||
xl: 1280,
|
||||
'2xl': 1536
|
||||
};
|
||||
|
||||
/**
|
||||
* Default placement with RTL support
|
||||
*/
|
||||
const getDefaultPlacement = (fallback = 'bottom') => {
|
||||
if (fallback.includes('-start') || fallback.includes('-end')) {
|
||||
const [side, alignment] = fallback.split('-');
|
||||
const flippedAlignment = alignment === 'start' ? 'end' : 'start';
|
||||
return index_js.isRTL() ? `${side}-${flippedAlignment}` : fallback;
|
||||
}
|
||||
return fallback;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a placement string that may contain responsive prefixes
|
||||
* Example: "bottom-start md:top-end lg:right" returns { xs: 'bottom-start', md: 'top-end', lg: 'right' }
|
||||
*
|
||||
* @param {string} placementString - The placement string to parse
|
||||
* @param {string} defaultPlacement - The default placement to use for xs/base
|
||||
* @returns {object|null} - Object with breakpoint keys and placement values, or null if not responsive
|
||||
*/
|
||||
const parseResponsivePlacement = (placementString, defaultPlacement = 'bottom') => {
|
||||
// Check if placement contains responsive prefixes (e.g., "bottom-start md:top-end")
|
||||
if (!placementString || !placementString.includes(':')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse the placement string into breakpoint-keyed object
|
||||
const parts = placementString.split(/\s+/);
|
||||
const placements = {
|
||||
xs: defaultPlacement
|
||||
}; // Default fallback
|
||||
|
||||
for (const part of parts) {
|
||||
if (part.includes(':')) {
|
||||
// Responsive placement like "md:top-end"
|
||||
const [breakpoint, placement] = part.split(':');
|
||||
if (BREAKPOINTS[breakpoint] !== undefined) {
|
||||
placements[breakpoint] = placement;
|
||||
}
|
||||
} else {
|
||||
// Base placement (no prefix = xs/default)
|
||||
placements.xs = part;
|
||||
}
|
||||
}
|
||||
return placements;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the active placement for the current viewport width
|
||||
*
|
||||
* @param {object} responsivePlacements - Object with breakpoint keys and placement values
|
||||
* @param {string} defaultPlacement - Fallback placement
|
||||
* @returns {string} - The active placement for current viewport
|
||||
*/
|
||||
const getResponsivePlacement = (responsivePlacements, defaultPlacement = 'bottom') => {
|
||||
if (!responsivePlacements) {
|
||||
return defaultPlacement;
|
||||
}
|
||||
|
||||
// Get current viewport width
|
||||
const viewportWidth = window.innerWidth;
|
||||
|
||||
// Find the largest breakpoint that matches
|
||||
let activePlacement = responsivePlacements.xs || defaultPlacement;
|
||||
|
||||
// Check breakpoints in order (sm, md, lg, xl, 2xl)
|
||||
const breakpointOrder = ['sm', 'md', 'lg', 'xl', '2xl'];
|
||||
for (const breakpoint of breakpointOrder) {
|
||||
const minWidth = BREAKPOINTS[breakpoint];
|
||||
if (viewportWidth >= minWidth && responsivePlacements[breakpoint]) {
|
||||
activePlacement = responsivePlacements[breakpoint];
|
||||
}
|
||||
}
|
||||
return activePlacement;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create media query listeners for responsive placement changes
|
||||
*
|
||||
* @param {Function} callback - Callback to run when breakpoint changes
|
||||
* @returns {Array} - Array of { mql, handler } objects for cleanup
|
||||
*/
|
||||
const createBreakpointListeners = callback => {
|
||||
const listeners = [];
|
||||
for (const breakpoint of Object.keys(BREAKPOINTS)) {
|
||||
const minWidth = BREAKPOINTS[breakpoint];
|
||||
const mql = window.matchMedia(`(min-width: ${minWidth}px)`);
|
||||
mql.addEventListener('change', callback);
|
||||
listeners.push({
|
||||
mql,
|
||||
handler: callback
|
||||
});
|
||||
}
|
||||
return listeners;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clean up media query listeners
|
||||
*
|
||||
* @param {Array} listeners - Array of { mql, handler } objects
|
||||
*/
|
||||
const disposeBreakpointListeners = listeners => {
|
||||
for (const {
|
||||
mql,
|
||||
handler
|
||||
} of listeners) {
|
||||
mql.removeEventListener('change', handler);
|
||||
}
|
||||
};
|
||||
|
||||
exports.BREAKPOINTS = BREAKPOINTS;
|
||||
exports.createBreakpointListeners = createBreakpointListeners;
|
||||
exports.disposeBreakpointListeners = disposeBreakpointListeners;
|
||||
exports.getDefaultPlacement = getDefaultPlacement;
|
||||
exports.getResponsivePlacement = getResponsivePlacement;
|
||||
exports.parseResponsivePlacement = parseResponsivePlacement;
|
||||
|
||||
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=floating-ui.js.map
|
||||
Vendored
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -16,7 +16,7 @@
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
const MAX_UID = 1_000_000;
|
||||
const MAX_UID = 1000000;
|
||||
const MILLISECONDS_MULTIPLIER = 1000;
|
||||
const TRANSITION_END = 'transitionend';
|
||||
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-4
@@ -9,14 +9,11 @@ export { default as Alert } from './src/alert.js'
|
||||
export { default as Button } from './src/button.js'
|
||||
export { default as Carousel } from './src/carousel.js'
|
||||
export { default as Collapse } from './src/collapse.js'
|
||||
export { default as Dialog } from './src/dialog.js'
|
||||
export { default as Dropdown } from './src/dropdown.js'
|
||||
export { default as Modal } from './src/modal.js'
|
||||
export { default as Offcanvas } from './src/offcanvas.js'
|
||||
export { default as Strength } from './src/strength.js'
|
||||
export { default as OtpInput } from './src/otp-input.js'
|
||||
export { default as Popover } from './src/popover.js'
|
||||
export { default as ScrollSpy } from './src/scrollspy.js'
|
||||
export { default as Tab } from './src/tab.js'
|
||||
export { default as Toast } from './src/toast.js'
|
||||
export { default as Toggler } from './src/toggler.js'
|
||||
export { default as Tooltip } from './src/tooltip.js'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user