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