Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 44ef0da4c5 | |||
| adb33831b8 | |||
| 6275ada1ed | |||
| 1b2583ba0c | |||
| 65a4072b55 | |||
| 627a295ef6 | |||
| 7cfdeeabef | |||
| 6b27acfc85 | |||
| 1438f19f7f | |||
| 7f774f4f99 | |||
| 540a2ed46c |
+12
-12
@@ -6,7 +6,7 @@
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/bootstrap-grid.min.css",
|
||||
"maxSize": "8.5 kB"
|
||||
"maxSize": "10.25 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/bootstrap-reboot.css",
|
||||
@@ -14,47 +14,47 @@
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/bootstrap-reboot.min.css",
|
||||
"maxSize": "4.25 kB"
|
||||
"maxSize": "6.75 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/bootstrap-utilities.css",
|
||||
"maxSize": "14.5 kB"
|
||||
"maxSize": "14.25 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/bootstrap-utilities.min.css",
|
||||
"maxSize": "12.75 kB"
|
||||
"maxSize": "15.0 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/bootstrap.css",
|
||||
"maxSize": "36.0 kB"
|
||||
"maxSize": "37.5 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/bootstrap.min.css",
|
||||
"maxSize": "32.5 kB"
|
||||
"maxSize": "36.25 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/bootstrap.bundle.js",
|
||||
"maxSize": "49.75 kB"
|
||||
"maxSize": "67.75 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/bootstrap.bundle.min.js",
|
||||
"maxSize": "26.0 kB"
|
||||
"maxSize": "41.0 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/bootstrap.esm.js",
|
||||
"maxSize": "36.0 kB"
|
||||
"maxSize": "39.0 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/bootstrap.esm.min.js",
|
||||
"maxSize": "22.25 kB"
|
||||
"maxSize": "24.0 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/bootstrap.js",
|
||||
"maxSize": "36.5 kB"
|
||||
"maxSize": "39.75 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/bootstrap.min.js",
|
||||
"maxSize": "19.75 kB"
|
||||
"maxSize": "21.25 kB"
|
||||
}
|
||||
],
|
||||
"ci": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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!')
|
||||
Vendored
+13
-13
@@ -4,19 +4,19 @@
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root {
|
||||
--bs-blue-025: color-mix(in lab, #fff 94%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-050: color-mix(in lab, #fff 90%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-100: color-mix(in lab, #fff 80%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-200: color-mix(in lab, #fff 60%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-300: color-mix(in lab, #fff 40%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-400: color-mix(in lab, #fff 20%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-500: oklch(60% 0.24 258deg);
|
||||
--bs-blue-600: color-mix(in lab, #000 16%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-700: color-mix(in lab, #000 32%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-800: color-mix(in lab, #000 48%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-900: color-mix(in lab, #000 64%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-950: color-mix(in lab, #000 76%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-975: color-mix(in lab, #000 88%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-025: color-mix(in lab, #fff 94%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-050: color-mix(in lab, #fff 90%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-100: color-mix(in lab, #fff 80%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-200: color-mix(in lab, #fff 60%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-300: color-mix(in lab, #fff 40%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-400: color-mix(in lab, #fff 20%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-500: oklch(60% 0.24 240deg);
|
||||
--bs-blue-600: color-mix(in lab, #000 16%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-700: color-mix(in lab, #000 32%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-800: color-mix(in lab, #000 48%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-900: color-mix(in lab, #000 64%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-950: color-mix(in lab, #000 76%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-975: color-mix(in lab, #000 88%, oklch(60% 0.24 240deg));
|
||||
--bs-indigo-025: color-mix(in lab, #fff 94%, oklch(56% 0.26 288deg));
|
||||
--bs-indigo-050: color-mix(in lab, #fff 90%, oklch(56% 0.26 288deg));
|
||||
--bs-indigo-100: color-mix(in lab, #fff 80%, oklch(56% 0.26 288deg));
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+25
-26
@@ -4,19 +4,19 @@
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root {
|
||||
--bs-blue-025: color-mix(in lab, #fff 94%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-050: color-mix(in lab, #fff 90%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-100: color-mix(in lab, #fff 80%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-200: color-mix(in lab, #fff 60%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-300: color-mix(in lab, #fff 40%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-400: color-mix(in lab, #fff 20%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-500: oklch(60% 0.24 258deg);
|
||||
--bs-blue-600: color-mix(in lab, #000 16%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-700: color-mix(in lab, #000 32%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-800: color-mix(in lab, #000 48%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-900: color-mix(in lab, #000 64%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-950: color-mix(in lab, #000 76%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-975: color-mix(in lab, #000 88%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-025: color-mix(in lab, #fff 94%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-050: color-mix(in lab, #fff 90%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-100: color-mix(in lab, #fff 80%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-200: color-mix(in lab, #fff 60%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-300: color-mix(in lab, #fff 40%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-400: color-mix(in lab, #fff 20%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-500: oklch(60% 0.24 240deg);
|
||||
--bs-blue-600: color-mix(in lab, #000 16%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-700: color-mix(in lab, #000 32%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-800: color-mix(in lab, #000 48%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-900: color-mix(in lab, #000 64%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-950: color-mix(in lab, #000 76%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-975: color-mix(in lab, #000 88%, oklch(60% 0.24 240deg));
|
||||
--bs-indigo-025: color-mix(in lab, #fff 94%, oklch(56% 0.26 288deg));
|
||||
--bs-indigo-050: color-mix(in lab, #fff 90%, oklch(56% 0.26 288deg));
|
||||
--bs-indigo-100: color-mix(in lab, #fff 80%, oklch(56% 0.26 288deg));
|
||||
@@ -312,7 +312,8 @@
|
||||
|
||||
@layer colors, theme, config, root, reboot, layout, content, forms, components, custom, helpers, utilities;
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-primary-base: var(--bs-blue-500);
|
||||
--bs-primary-text: light-dark(var(--bs-blue-600), var(--bs-blue-400));
|
||||
--bs-primary-text-emphasis: light-dark(var(--bs-blue-800), var(--bs-blue-200));
|
||||
@@ -409,19 +410,16 @@
|
||||
--bs-border-emphasized: light-dark(var(--bs-gray-400), var(--bs-gray-600));
|
||||
--bs-border-white: var(--bs-white);
|
||||
--bs-border-black: var(--bs-black);
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
color-scheme: light;
|
||||
--bs-white-rgb: to-rgb(#fff);
|
||||
--bs-black-rgb: to-rgb(#000);
|
||||
--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-root-font-size: 16px;
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-font-size-base: 14px;
|
||||
--bs-font-size-xs: .75rem;
|
||||
@@ -450,6 +448,7 @@
|
||||
--bs-body-color-rgb: to-rgb(var(--bs-color-body));
|
||||
--bs-body-bg-rgb: to-rgb(var(--bs-bg-body));
|
||||
--bs-heading-color: inherit;
|
||||
--bs-hr-border-color: var(--bs-border-color);
|
||||
--bs-link-color: light-dark(var(--bs-primary-base), var(--bs-primary-text));
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: color-mix(in oklch, var(--bs-link-color) 90%, #000);
|
||||
@@ -471,17 +470,18 @@
|
||||
--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: 3px;
|
||||
--bs-focus-ring-offset: -1px;
|
||||
--bs-focus-ring-offset: 1px;
|
||||
--bs-focus-ring-color: var(--bs-primary-focus-ring);
|
||||
--bs-focus-ring: var(--bs-focus-ring-width) solid var(--bs-focus-ring-color);
|
||||
--bs-form-valid-color: var(--bs-success);
|
||||
--bs-form-valid-border-color: var(--bs-success);
|
||||
--bs-form-invalid-color: var(--bs-danger);
|
||||
--bs-form-invalid-border-color: var(--bs-danger);
|
||||
color-scheme: light;
|
||||
--bs-root-font-size: 16px;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
color-scheme: dark;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: to-rgb(#fff);
|
||||
--bs-heading-color: inherit;
|
||||
@@ -491,6 +491,7 @@
|
||||
--bs-form-valid-border-color: var(--bs-green-300);
|
||||
--bs-form-invalid-color: var(--bs-red-300);
|
||||
--bs-form-invalid-border-color: var(--bs-red-300);
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
@layer reboot {
|
||||
@@ -521,10 +522,8 @@
|
||||
}
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-block-start: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
border-block-start: var(--bs-border-width) solid var(--bs-hr-border-color);
|
||||
}
|
||||
h6,
|
||||
.h6, h5,
|
||||
@@ -633,13 +632,13 @@
|
||||
top: -0.5em;
|
||||
}
|
||||
a {
|
||||
color: var(--bs-link-color);
|
||||
color: var(--bs-theme-text, var(--bs-link-color));
|
||||
-webkit-text-decoration: var(--bs-link-decoration);
|
||||
text-decoration: var(--bs-link-decoration);
|
||||
text-underline-offset: 0.2em;
|
||||
}
|
||||
a:hover {
|
||||
color: var(--bs-link-hover-color);
|
||||
color: var(--bs-theme-text-emphasis, var(--bs-link-hover-color));
|
||||
-webkit-text-decoration: var(--bs-link-hover-decoration, var(--bs-link-decoration));
|
||||
text-decoration: var(--bs-link-hover-decoration, var(--bs-link-decoration));
|
||||
}
|
||||
@@ -678,7 +677,7 @@
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 95%;
|
||||
color: var(--bs-bg-body);
|
||||
background-color: var(--bs-color-body);
|
||||
background-color: var(--bs-fg-body);
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
kbd kbd {
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+67
-152
@@ -4,19 +4,19 @@
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root {
|
||||
--bs-blue-025: color-mix(in lab, #fff 94%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-050: color-mix(in lab, #fff 90%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-100: color-mix(in lab, #fff 80%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-200: color-mix(in lab, #fff 60%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-300: color-mix(in lab, #fff 40%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-400: color-mix(in lab, #fff 20%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-500: oklch(60% 0.24 258deg);
|
||||
--bs-blue-600: color-mix(in lab, #000 16%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-700: color-mix(in lab, #000 32%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-800: color-mix(in lab, #000 48%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-900: color-mix(in lab, #000 64%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-950: color-mix(in lab, #000 76%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-975: color-mix(in lab, #000 88%, oklch(60% 0.24 258deg));
|
||||
--bs-blue-025: color-mix(in lab, #fff 94%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-050: color-mix(in lab, #fff 90%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-100: color-mix(in lab, #fff 80%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-200: color-mix(in lab, #fff 60%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-300: color-mix(in lab, #fff 40%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-400: color-mix(in lab, #fff 20%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-500: oklch(60% 0.24 240deg);
|
||||
--bs-blue-600: color-mix(in lab, #000 16%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-700: color-mix(in lab, #000 32%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-800: color-mix(in lab, #000 48%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-900: color-mix(in lab, #000 64%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-950: color-mix(in lab, #000 76%, oklch(60% 0.24 240deg));
|
||||
--bs-blue-975: color-mix(in lab, #000 88%, oklch(60% 0.24 240deg));
|
||||
--bs-indigo-025: color-mix(in lab, #fff 94%, oklch(56% 0.26 288deg));
|
||||
--bs-indigo-050: color-mix(in lab, #fff 90%, oklch(56% 0.26 288deg));
|
||||
--bs-indigo-100: color-mix(in lab, #fff 80%, oklch(56% 0.26 288deg));
|
||||
@@ -312,7 +312,8 @@
|
||||
|
||||
@layer colors, theme, config, root, reboot, layout, content, forms, components, custom, helpers, utilities;
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-primary-base: var(--bs-blue-500);
|
||||
--bs-primary-text: light-dark(var(--bs-blue-600), var(--bs-blue-400));
|
||||
--bs-primary-text-emphasis: light-dark(var(--bs-blue-800), var(--bs-blue-200));
|
||||
@@ -409,19 +410,16 @@
|
||||
--bs-border-emphasized: light-dark(var(--bs-gray-400), var(--bs-gray-600));
|
||||
--bs-border-white: var(--bs-white);
|
||||
--bs-border-black: var(--bs-black);
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
color-scheme: light;
|
||||
--bs-white-rgb: to-rgb(#fff);
|
||||
--bs-black-rgb: to-rgb(#000);
|
||||
--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-root-font-size: 16px;
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-font-size-base: 14px;
|
||||
--bs-font-size-xs: .75rem;
|
||||
@@ -450,6 +448,7 @@
|
||||
--bs-body-color-rgb: to-rgb(var(--bs-color-body));
|
||||
--bs-body-bg-rgb: to-rgb(var(--bs-bg-body));
|
||||
--bs-heading-color: inherit;
|
||||
--bs-hr-border-color: var(--bs-border-color);
|
||||
--bs-link-color: light-dark(var(--bs-primary-base), var(--bs-primary-text));
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: color-mix(in oklch, var(--bs-link-color) 90%, #000);
|
||||
@@ -471,17 +470,18 @@
|
||||
--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: 3px;
|
||||
--bs-focus-ring-offset: -1px;
|
||||
--bs-focus-ring-offset: 1px;
|
||||
--bs-focus-ring-color: var(--bs-primary-focus-ring);
|
||||
--bs-focus-ring: var(--bs-focus-ring-width) solid var(--bs-focus-ring-color);
|
||||
--bs-form-valid-color: var(--bs-success);
|
||||
--bs-form-valid-border-color: var(--bs-success);
|
||||
--bs-form-invalid-color: var(--bs-danger);
|
||||
--bs-form-invalid-border-color: var(--bs-danger);
|
||||
color-scheme: light;
|
||||
--bs-root-font-size: 16px;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
color-scheme: dark;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: to-rgb(#fff);
|
||||
--bs-heading-color: inherit;
|
||||
@@ -491,108 +491,9 @@
|
||||
--bs-form-valid-border-color: var(--bs-green-300);
|
||||
--bs-form-invalid-color: var(--bs-red-300);
|
||||
--bs-form-invalid-border-color: var(--bs-red-300);
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
@layer helpers {
|
||||
.text-bg-primary {
|
||||
color: var(--bs-primary-text);
|
||||
--bs-bg: var(--bs-primary-bg-subtle);
|
||||
}
|
||||
.text-bg-accent {
|
||||
color: var(--bs-accent-text);
|
||||
--bs-bg: var(--bs-accent-bg-subtle);
|
||||
}
|
||||
.text-bg-success {
|
||||
color: var(--bs-success-text);
|
||||
--bs-bg: var(--bs-success-bg-subtle);
|
||||
}
|
||||
.text-bg-danger {
|
||||
color: var(--bs-danger-text);
|
||||
--bs-bg: var(--bs-danger-bg-subtle);
|
||||
}
|
||||
.text-bg-warning {
|
||||
color: var(--bs-warning-text);
|
||||
--bs-bg: var(--bs-warning-bg-subtle);
|
||||
}
|
||||
.text-bg-info {
|
||||
color: var(--bs-info-text);
|
||||
--bs-bg: var(--bs-info-bg-subtle);
|
||||
}
|
||||
.text-bg-inverse {
|
||||
color: var(--bs-inverse-text);
|
||||
--bs-bg: var(--bs-inverse-bg-subtle);
|
||||
}
|
||||
.text-bg-secondary {
|
||||
color: var(--bs-secondary-text);
|
||||
--bs-bg: var(--bs-secondary-bg-subtle);
|
||||
}
|
||||
}
|
||||
@layer helpers {
|
||||
.link-primary {
|
||||
--bs-link-color: var(--bs-primary-text);
|
||||
}
|
||||
.link-primary:hover, .link-primary:focus {
|
||||
--bs-link-color: var(--bs-primary-text-emphasis);
|
||||
--bs-link-hover-color: var(--bs-primary-text-emphasis);
|
||||
}
|
||||
.link-accent {
|
||||
--bs-link-color: var(--bs-accent-text);
|
||||
}
|
||||
.link-accent:hover, .link-accent:focus {
|
||||
--bs-link-color: var(--bs-accent-text-emphasis);
|
||||
--bs-link-hover-color: var(--bs-accent-text-emphasis);
|
||||
}
|
||||
.link-success {
|
||||
--bs-link-color: var(--bs-success-text);
|
||||
}
|
||||
.link-success:hover, .link-success:focus {
|
||||
--bs-link-color: var(--bs-success-text-emphasis);
|
||||
--bs-link-hover-color: var(--bs-success-text-emphasis);
|
||||
}
|
||||
.link-danger {
|
||||
--bs-link-color: var(--bs-danger-text);
|
||||
}
|
||||
.link-danger:hover, .link-danger:focus {
|
||||
--bs-link-color: var(--bs-danger-text-emphasis);
|
||||
--bs-link-hover-color: var(--bs-danger-text-emphasis);
|
||||
}
|
||||
.link-warning {
|
||||
--bs-link-color: var(--bs-warning-text);
|
||||
}
|
||||
.link-warning:hover, .link-warning:focus {
|
||||
--bs-link-color: var(--bs-warning-text-emphasis);
|
||||
--bs-link-hover-color: var(--bs-warning-text-emphasis);
|
||||
}
|
||||
.link-info {
|
||||
--bs-link-color: var(--bs-info-text);
|
||||
}
|
||||
.link-info:hover, .link-info:focus {
|
||||
--bs-link-color: var(--bs-info-text-emphasis);
|
||||
--bs-link-hover-color: var(--bs-info-text-emphasis);
|
||||
}
|
||||
.link-inverse {
|
||||
--bs-link-color: var(--bs-inverse-text);
|
||||
}
|
||||
.link-inverse:hover, .link-inverse:focus {
|
||||
--bs-link-color: var(--bs-inverse-text-emphasis);
|
||||
--bs-link-hover-color: var(--bs-inverse-text-emphasis);
|
||||
}
|
||||
.link-secondary {
|
||||
--bs-link-color: var(--bs-secondary-text);
|
||||
}
|
||||
.link-secondary:hover, .link-secondary:focus {
|
||||
--bs-link-color: var(--bs-secondary-text-emphasis);
|
||||
--bs-link-hover-color: var(--bs-secondary-text-emphasis);
|
||||
}
|
||||
.link-body-emphasis {
|
||||
color: color-mix(in srgb, var(--bs-emphasis-color), transparent var(--bs-link-opacity));
|
||||
text-decoration-color: color-mix(in srgb, var(--bs-emphasis-color), transparent var(--bs-link-underline-opacity));
|
||||
}
|
||||
.link-body-emphasis:hover, .link-body-emphasis:focus {
|
||||
color: color-mix(in srgb, var(--bs-emphasis-color), transparent var(--bs-link-opacity, 0.75));
|
||||
text-decoration-color: color-mix(in srgb, var(--bs-emphasis-color), transparent var(--bs-link-underline-opacity, 0.75));
|
||||
}
|
||||
}
|
||||
@layer helpers {
|
||||
.focus-ring:focus-visible {
|
||||
outline: var(--bs-focus-ring);
|
||||
@@ -770,8 +671,7 @@
|
||||
align-self: stretch;
|
||||
width: var(--bs-border-width);
|
||||
min-height: 1em;
|
||||
background-color: currentcolor;
|
||||
opacity: 0.25;
|
||||
background-color: var(--bs-border-color);
|
||||
}
|
||||
}
|
||||
@layer utilities {
|
||||
@@ -2091,36 +1991,6 @@
|
||||
--bs-fg: light-dark(var(--bs-gray-800), var(--bs-gray-200));
|
||||
color: var(--bs-fg);
|
||||
}
|
||||
.fg-10 {
|
||||
color: color-mix(in oklch, var(--bs-fg) 10%, transparent);
|
||||
}
|
||||
.fg-20 {
|
||||
color: color-mix(in oklch, var(--bs-fg) 20%, transparent);
|
||||
}
|
||||
.fg-30 {
|
||||
color: color-mix(in oklch, var(--bs-fg) 30%, transparent);
|
||||
}
|
||||
.fg-40 {
|
||||
color: color-mix(in oklch, var(--bs-fg) 40%, transparent);
|
||||
}
|
||||
.fg-50 {
|
||||
color: color-mix(in oklch, var(--bs-fg) 50%, transparent);
|
||||
}
|
||||
.fg-60 {
|
||||
color: color-mix(in oklch, var(--bs-fg) 60%, transparent);
|
||||
}
|
||||
.fg-70 {
|
||||
color: color-mix(in oklch, var(--bs-fg) 70%, transparent);
|
||||
}
|
||||
.fg-80 {
|
||||
color: color-mix(in oklch, var(--bs-fg) 80%, transparent);
|
||||
}
|
||||
.fg-90 {
|
||||
color: color-mix(in oklch, var(--bs-fg) 90%, transparent);
|
||||
}
|
||||
.fg-100 {
|
||||
color: var(--bs-fg);
|
||||
}
|
||||
.fg-contrast-primary {
|
||||
--bs-fg: var(--bs-white);
|
||||
color: var(--bs-fg);
|
||||
@@ -2153,6 +2023,36 @@
|
||||
--bs-fg: light-dark(var(--bs-gray-900), var(--bs-white));
|
||||
color: var(--bs-fg);
|
||||
}
|
||||
.fg-10 {
|
||||
color: color-mix(in oklch, var(--bs-fg) 10%, transparent);
|
||||
}
|
||||
.fg-20 {
|
||||
color: color-mix(in oklch, var(--bs-fg) 20%, transparent);
|
||||
}
|
||||
.fg-30 {
|
||||
color: color-mix(in oklch, var(--bs-fg) 30%, transparent);
|
||||
}
|
||||
.fg-40 {
|
||||
color: color-mix(in oklch, var(--bs-fg) 40%, transparent);
|
||||
}
|
||||
.fg-50 {
|
||||
color: color-mix(in oklch, var(--bs-fg) 50%, transparent);
|
||||
}
|
||||
.fg-60 {
|
||||
color: color-mix(in oklch, var(--bs-fg) 60%, transparent);
|
||||
}
|
||||
.fg-70 {
|
||||
color: color-mix(in oklch, var(--bs-fg) 70%, transparent);
|
||||
}
|
||||
.fg-80 {
|
||||
color: color-mix(in oklch, var(--bs-fg) 80%, transparent);
|
||||
}
|
||||
.fg-90 {
|
||||
color: color-mix(in oklch, var(--bs-fg) 90%, transparent);
|
||||
}
|
||||
.fg-100 {
|
||||
color: var(--bs-fg);
|
||||
}
|
||||
.link-10 {
|
||||
color: color-mix(in oklch, var(--bs-link-color) 10%, transparent);
|
||||
}
|
||||
@@ -2507,6 +2407,21 @@
|
||||
.bg-100 {
|
||||
background-color: var(--bs-bg);
|
||||
}
|
||||
.theme-contrast {
|
||||
background-color: var(--bs-theme-bg);
|
||||
color: var(--bs-theme-contrast);
|
||||
}
|
||||
.theme-subtle {
|
||||
background-color: var(--bs-theme-bg-subtle);
|
||||
color: var(--bs-theme-text);
|
||||
}
|
||||
.theme-muted {
|
||||
background-color: var(--bs-theme-bg-muted);
|
||||
color: var(--bs-theme-text-emphasis);
|
||||
}
|
||||
.theme-border {
|
||||
border: var(--bs-border-width) solid var(--bs-theme-border);
|
||||
}
|
||||
.bg-gradient {
|
||||
background-image: var(--bs-gradient);
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+970
-360
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1696
-274
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
+1691
-275
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
+1697
-278
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
+1
-1
@@ -1,6 +1,6 @@
|
||||
/*!
|
||||
* Bootstrap dialog.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 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
+1
-1
@@ -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) {
|
||||
|
||||
Vendored
+1
-1
@@ -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) {
|
||||
|
||||
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
+472
-49
@@ -1,6 +1,6 @@
|
||||
/*!
|
||||
* 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) {
|
||||
@@ -29,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}`;
|
||||
@@ -42,11 +50,28 @@
|
||||
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)';
|
||||
const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`;
|
||||
const SELECTOR_MENU = '.dropdown-menu';
|
||||
const SELECTOR_SUBMENU = '.dropdown-submenu';
|
||||
const SELECTOR_SUBMENU_TOGGLE = '.dropdown-submenu > .dropdown-item';
|
||||
const SELECTOR_NAVBAR_NAV = '.navbar-nav';
|
||||
const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)';
|
||||
const SELECTOR_VISIBLE_ITEMS = '.dropdown-item:not(.disabled):not(:disabled)';
|
||||
|
||||
// Default placement with RTL support
|
||||
const DEFAULT_PLACEMENT = index_js.isRTL() ? 'bottom-end' : 'bottom-start';
|
||||
// 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',
|
||||
@@ -54,7 +79,11 @@
|
||||
offset: [0, 2],
|
||||
floatingConfig: null,
|
||||
placement: DEFAULT_PLACEMENT,
|
||||
reference: 'toggle'
|
||||
reference: 'toggle',
|
||||
// Submenu options
|
||||
submenuTrigger: 'both',
|
||||
// 'click', 'hover', or 'both'
|
||||
submenuDelay: SUBMENU_CLOSE_DELAY
|
||||
};
|
||||
const DefaultType = {
|
||||
autoClose: '(boolean|string)',
|
||||
@@ -63,7 +92,9 @@
|
||||
offset: '(array|string|function)',
|
||||
floatingConfig: '(null|object|function)',
|
||||
placement: 'string',
|
||||
reference: '(string|element|object)'
|
||||
reference: '(string|element|object)',
|
||||
submenuTrigger: 'string',
|
||||
submenuDelay: 'number'
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -80,11 +111,19 @@
|
||||
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);
|
||||
|
||||
// Parse responsive placements on init
|
||||
this._parseResponsivePlacements();
|
||||
|
||||
// Set up submenu event listeners
|
||||
this._setupSubmenuListeners();
|
||||
}
|
||||
|
||||
// Getters
|
||||
@@ -125,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() {
|
||||
@@ -142,6 +182,8 @@
|
||||
dispose() {
|
||||
this._disposeFloating();
|
||||
this._disposeMediaQueryListeners();
|
||||
this._closeAllSubmenus();
|
||||
this._clearAllSubmenuTimeouts();
|
||||
super.dispose();
|
||||
}
|
||||
update() {
|
||||
@@ -157,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) {
|
||||
@@ -167,6 +212,7 @@
|
||||
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, 'placement');
|
||||
Manipulator.removeDataAttribute(this._menu, 'display');
|
||||
@@ -201,8 +247,7 @@
|
||||
this._floatingCleanup = dom.autoUpdate(referenceElement, this._menu, () => this._updateFloatingPosition(referenceElement));
|
||||
}
|
||||
async _updateFloatingPosition(referenceElement = null) {
|
||||
// Check if menu exists and is still in the DOM
|
||||
if (!this._menu || !this._menu.isConnected) {
|
||||
if (!this._menu) {
|
||||
return;
|
||||
}
|
||||
if (!referenceElement) {
|
||||
@@ -219,37 +264,17 @@
|
||||
const placement = this._getPlacement();
|
||||
const middleware = this._getFloatingMiddleware();
|
||||
const floatingConfig = this._getFloatingConfig(placement, middleware);
|
||||
const {
|
||||
x,
|
||||
y,
|
||||
placement: finalPlacement
|
||||
} = await dom.computePosition(referenceElement, this._menu, floatingConfig);
|
||||
|
||||
// Menu may have been disposed during the async computePosition call
|
||||
if (!this._menu || !this._menu.isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply position to dropdown menu
|
||||
Object.assign(this._menu.style, {
|
||||
position: 'absolute',
|
||||
left: `${x}px`,
|
||||
top: `${y}px`,
|
||||
margin: '0'
|
||||
});
|
||||
|
||||
// Set placement attribute for CSS styling
|
||||
Manipulator.setDataAttribute(this._menu, 'placement', finalPlacement);
|
||||
await this._applyFloatingPosition(referenceElement, this._menu, floatingConfig.placement, floatingConfig.middleware);
|
||||
}
|
||||
_isShown() {
|
||||
return this._menu.classList.contains(CLASS_NAME_SHOW);
|
||||
}
|
||||
_getPlacement() {
|
||||
// If we have responsive placements, find the appropriate one for current viewport
|
||||
if (this._responsivePlacements) {
|
||||
return floatingUi_js.getResponsivePlacement(this._responsivePlacements, DEFAULT_PLACEMENT);
|
||||
}
|
||||
return this._config.placement;
|
||||
const placement = this._responsivePlacements ? floatingUi_js.getResponsivePlacement(this._responsivePlacements, DEFAULT_PLACEMENT) : this._config.placement;
|
||||
|
||||
// Resolve logical placements (start/end) to physical (left/right) based on RTL
|
||||
return resolveLogicalPlacement(placement);
|
||||
}
|
||||
_parseResponsivePlacements() {
|
||||
this._responsivePlacements = floatingUi_js.parseResponsivePlacement(this._config.placement, DEFAULT_PLACEMENT);
|
||||
@@ -271,18 +296,18 @@
|
||||
}
|
||||
_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') {
|
||||
if (typeof offsetConfig === 'function') {
|
||||
// Floating UI passes different args, adapt the interface for offset function callbacks
|
||||
return ({
|
||||
placement,
|
||||
rects
|
||||
}) => {
|
||||
const result = offset({
|
||||
const result = offsetConfig({
|
||||
placement,
|
||||
reference: rects.reference,
|
||||
floating: rects.floating
|
||||
@@ -290,7 +315,7 @@
|
||||
return result;
|
||||
};
|
||||
}
|
||||
return offset;
|
||||
return offsetConfig;
|
||||
}
|
||||
_getFloatingMiddleware() {
|
||||
const offsetValue = this._getOffset();
|
||||
@@ -348,11 +373,296 @@
|
||||
this._floatingCleanup = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Shared helper for positioning any floating element
|
||||
async _applyFloatingPosition(reference, floating, placement, middleware) {
|
||||
if (!floating.isConnected) {
|
||||
return null;
|
||||
}
|
||||
const {
|
||||
x,
|
||||
y,
|
||||
placement: finalPlacement
|
||||
} = await dom.computePosition(reference, floating, {
|
||||
placement,
|
||||
middleware
|
||||
});
|
||||
if (!floating.isConnected) {
|
||||
return null;
|
||||
}
|
||||
Object.assign(floating.style, {
|
||||
position: 'absolute',
|
||||
left: `${x}px`,
|
||||
top: `${y}px`,
|
||||
margin: '0'
|
||||
});
|
||||
Manipulator.setDataAttribute(floating, 'placement', finalPlacement);
|
||||
return finalPlacement;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Submenu handling
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
_setupSubmenuListeners() {
|
||||
// Set up hover listeners for submenu triggers
|
||||
if (this._config.submenuTrigger === 'hover' || this._config.submenuTrigger === 'both') {
|
||||
EventHandler.on(this._menu, 'mouseenter', SELECTOR_SUBMENU_TOGGLE, event => {
|
||||
this._onSubmenuTriggerEnter(event);
|
||||
});
|
||||
EventHandler.on(this._menu, 'mouseleave', SELECTOR_SUBMENU, event => {
|
||||
this._onSubmenuLeave(event);
|
||||
});
|
||||
|
||||
// Track mouse movement for safe triangle calculation
|
||||
EventHandler.on(this._menu, 'mousemove', event => {
|
||||
this._trackMousePosition(event);
|
||||
});
|
||||
}
|
||||
|
||||
// Set up click listener for submenu triggers
|
||||
if (this._config.submenuTrigger === 'click' || this._config.submenuTrigger === 'both') {
|
||||
EventHandler.on(this._menu, 'click', SELECTOR_SUBMENU_TOGGLE, event => {
|
||||
this._onSubmenuTriggerClick(event);
|
||||
});
|
||||
}
|
||||
}
|
||||
_onSubmenuTriggerEnter(event) {
|
||||
const trigger = event.target.closest(SELECTOR_SUBMENU_TOGGLE);
|
||||
if (!trigger) {
|
||||
return;
|
||||
}
|
||||
const submenuWrapper = trigger.closest(SELECTOR_SUBMENU);
|
||||
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
|
||||
if (!submenu) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel any pending close timeout for this submenu
|
||||
this._cancelSubmenuCloseTimeout(submenu);
|
||||
|
||||
// Close other open submenus at the same level
|
||||
this._closeSiblingSubmenus(submenuWrapper);
|
||||
|
||||
// Open this submenu
|
||||
this._openSubmenu(trigger, submenu, submenuWrapper);
|
||||
}
|
||||
_onSubmenuLeave(event) {
|
||||
const submenuWrapper = event.target.closest(SELECTOR_SUBMENU);
|
||||
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
|
||||
if (!submenu || !this._openSubmenus.has(submenu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we're moving toward the submenu (safe triangle)
|
||||
if (this._isMovingTowardSubmenu(event, submenu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedule submenu close with delay
|
||||
this._scheduleSubmenuClose(submenu, submenuWrapper);
|
||||
}
|
||||
_onSubmenuTriggerClick(event) {
|
||||
const trigger = event.target.closest(SELECTOR_SUBMENU_TOGGLE);
|
||||
if (!trigger) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const submenuWrapper = trigger.closest(SELECTOR_SUBMENU);
|
||||
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
|
||||
if (!submenu) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle submenu
|
||||
if (this._openSubmenus.has(submenu)) {
|
||||
this._closeSubmenu(submenu, submenuWrapper);
|
||||
} else {
|
||||
this._closeSiblingSubmenus(submenuWrapper);
|
||||
this._openSubmenu(trigger, submenu, submenuWrapper);
|
||||
}
|
||||
}
|
||||
_openSubmenu(trigger, submenu, submenuWrapper) {
|
||||
if (this._openSubmenus.has(submenu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set ARIA attributes
|
||||
trigger.setAttribute('aria-expanded', 'true');
|
||||
trigger.setAttribute('aria-haspopup', 'true');
|
||||
|
||||
// Position and show submenu
|
||||
submenu.classList.add(CLASS_NAME_SHOW);
|
||||
submenuWrapper.classList.add(CLASS_NAME_SHOW);
|
||||
|
||||
// Set up Floating UI positioning for submenu
|
||||
const cleanup = this._createSubmenuFloating(trigger, submenu, submenuWrapper);
|
||||
this._openSubmenus.set(submenu, cleanup);
|
||||
|
||||
// Set up mouseenter on submenu to cancel close timeout
|
||||
EventHandler.on(submenu, 'mouseenter', () => {
|
||||
this._cancelSubmenuCloseTimeout(submenu);
|
||||
});
|
||||
}
|
||||
_closeSubmenu(submenu, submenuWrapper) {
|
||||
if (!this._openSubmenus.has(submenu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Close any nested submenus first
|
||||
const nestedSubmenus = SelectorEngine.find(`${SELECTOR_SUBMENU} ${SELECTOR_MENU}.${CLASS_NAME_SHOW}`, submenu);
|
||||
for (const nested of nestedSubmenus) {
|
||||
const nestedWrapper = nested.closest(SELECTOR_SUBMENU);
|
||||
this._closeSubmenu(nested, nestedWrapper);
|
||||
}
|
||||
|
||||
// Get the trigger
|
||||
const trigger = SelectorEngine.findOne(SELECTOR_SUBMENU_TOGGLE, submenuWrapper);
|
||||
|
||||
// Clean up Floating UI
|
||||
const cleanup = this._openSubmenus.get(submenu);
|
||||
if (cleanup) {
|
||||
cleanup();
|
||||
}
|
||||
this._openSubmenus.delete(submenu);
|
||||
|
||||
// Remove event listeners
|
||||
EventHandler.off(submenu, 'mouseenter');
|
||||
|
||||
// Update ARIA and visibility
|
||||
if (trigger) {
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
submenu.classList.remove(CLASS_NAME_SHOW);
|
||||
submenuWrapper.classList.remove(CLASS_NAME_SHOW);
|
||||
|
||||
// Clear inline styles
|
||||
submenu.style.position = '';
|
||||
submenu.style.left = '';
|
||||
submenu.style.top = '';
|
||||
submenu.style.margin = '';
|
||||
}
|
||||
_closeAllSubmenus() {
|
||||
for (const [submenu] of this._openSubmenus) {
|
||||
const submenuWrapper = submenu.closest(SELECTOR_SUBMENU);
|
||||
this._closeSubmenu(submenu, submenuWrapper);
|
||||
}
|
||||
}
|
||||
_closeSiblingSubmenus(currentSubmenuWrapper) {
|
||||
// Find all sibling submenu wrappers and close their menus
|
||||
const parent = currentSubmenuWrapper.parentNode;
|
||||
const siblingSubmenus = SelectorEngine.find(`${SELECTOR_SUBMENU} > ${SELECTOR_MENU}.${CLASS_NAME_SHOW}`, parent);
|
||||
for (const siblingMenu of siblingSubmenus) {
|
||||
const siblingWrapper = siblingMenu.closest(SELECTOR_SUBMENU);
|
||||
if (siblingWrapper !== currentSubmenuWrapper) {
|
||||
this._closeSubmenu(siblingMenu, siblingWrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
_createSubmenuFloating(trigger, submenu, submenuWrapper) {
|
||||
const referenceElement = submenuWrapper;
|
||||
const placement = resolveLogicalPlacement(SUBMENU_PLACEMENT);
|
||||
const middleware = [dom.offset({
|
||||
mainAxis: 0,
|
||||
crossAxis: -4
|
||||
}), dom.flip({
|
||||
fallbackPlacements: [resolveLogicalPlacement('start-start'), resolveLogicalPlacement('end-end'), resolveLogicalPlacement('start-end')]
|
||||
}), dom.shift({
|
||||
padding: 8
|
||||
})];
|
||||
const updatePosition = () => this._applyFloatingPosition(referenceElement, submenu, placement, middleware);
|
||||
updatePosition();
|
||||
return dom.autoUpdate(referenceElement, submenu, updatePosition);
|
||||
}
|
||||
_scheduleSubmenuClose(submenu, submenuWrapper) {
|
||||
this._cancelSubmenuCloseTimeout(submenu);
|
||||
const timeoutId = setTimeout(() => {
|
||||
this._closeSubmenu(submenu, submenuWrapper);
|
||||
this._submenuCloseTimeouts.delete(submenu);
|
||||
}, this._config.submenuDelay);
|
||||
this._submenuCloseTimeouts.set(submenu, timeoutId);
|
||||
}
|
||||
_cancelSubmenuCloseTimeout(submenu) {
|
||||
const timeoutId = this._submenuCloseTimeouts.get(submenu);
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
this._submenuCloseTimeouts.delete(submenu);
|
||||
}
|
||||
}
|
||||
_clearAllSubmenuTimeouts() {
|
||||
for (const timeoutId of this._submenuCloseTimeouts.values()) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
this._submenuCloseTimeouts.clear();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Hover intent / Safe triangle
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
_trackMousePosition(event) {
|
||||
this._hoverIntentData = {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
_isMovingTowardSubmenu(event, submenu) {
|
||||
if (!this._hoverIntentData) {
|
||||
return false;
|
||||
}
|
||||
const submenuRect = submenu.getBoundingClientRect();
|
||||
const currentPos = {
|
||||
x: event.clientX,
|
||||
y: event.clientY
|
||||
};
|
||||
const lastPos = {
|
||||
x: this._hoverIntentData.x,
|
||||
y: this._hoverIntentData.y
|
||||
};
|
||||
|
||||
// Create a triangle from current position to submenu edges
|
||||
// The triangle represents the "safe zone" for diagonal movement
|
||||
const isRtl = index_js.isRTL();
|
||||
|
||||
// Determine which edge of the submenu to target based on direction
|
||||
const targetX = isRtl ? submenuRect.right : submenuRect.left;
|
||||
const topCorner = {
|
||||
x: targetX,
|
||||
y: submenuRect.top
|
||||
};
|
||||
const bottomCorner = {
|
||||
x: targetX,
|
||||
y: submenuRect.bottom
|
||||
};
|
||||
|
||||
// Check if cursor is moving toward the submenu
|
||||
// by checking if the current position is within the safe triangle
|
||||
return this._pointInTriangle(currentPos, lastPos, topCorner, bottomCorner);
|
||||
}
|
||||
_pointInTriangle(point, v1, v2, v3) {
|
||||
// Barycentric coordinate method to check if point is inside triangle
|
||||
const d1 = triangleSign(point, v1, v2);
|
||||
const d2 = triangleSign(point, v2, v3);
|
||||
const d3 = triangleSign(point, v3, v1);
|
||||
const hasNeg = d1 < 0 || d2 < 0 || d3 < 0;
|
||||
const hasPos = d1 > 0 || d2 > 0 || d3 > 0;
|
||||
return !(hasNeg && hasPos);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Keyboard navigation
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
_selectMenuItem({
|
||||
key,
|
||||
target
|
||||
}) {
|
||||
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;
|
||||
}
|
||||
@@ -361,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;
|
||||
@@ -391,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
+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
+1
-1
@@ -1,6 +1,6 @@
|
||||
/*!
|
||||
* 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) {
|
||||
|
||||
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
+1
-1
@@ -1,6 +1,6 @@
|
||||
/*!
|
||||
* 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) {
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
/*!
|
||||
* Bootstrap backdrop.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
+30
-1
@@ -1,6 +1,6 @@
|
||||
/*!
|
||||
* Bootstrap component-functions.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) {
|
||||
@@ -33,8 +33,37 @@
|
||||
instance[method]();
|
||||
});
|
||||
};
|
||||
const eventActionOnPlugin = (Plugin, onEvent, stringSelector, method, callback = null) => {
|
||||
eventAction(`${onEvent}.${Plugin.NAME}`, stringSelector, data => {
|
||||
const instances = data.targets.filter(Boolean).map(element => Plugin.getOrCreateInstance(element));
|
||||
if (typeof callback === 'function') {
|
||||
callback({
|
||||
...data,
|
||||
instances
|
||||
});
|
||||
}
|
||||
for (const instance of instances) {
|
||||
instance[method]();
|
||||
}
|
||||
});
|
||||
};
|
||||
const eventAction = (onEvent, stringSelector, callback) => {
|
||||
const selector = `${stringSelector}:not(.disabled):not(:disabled)`;
|
||||
EventHandler.on(document, onEvent, selector, function (event) {
|
||||
if (['A', 'AREA'].includes(this.tagName)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
const selector = SelectorEngine.getSelectorFromElement(this);
|
||||
const targets = selector ? SelectorEngine.find(selector) : [this];
|
||||
callback({
|
||||
targets,
|
||||
event
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.enableDismissTrigger = enableDismissTrigger;
|
||||
exports.eventActionOnPlugin = eventActionOnPlugin;
|
||||
|
||||
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"version":3,"file":"component-functions.js","sources":["../../src/util/component-functions.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isDisabled } from './index.js'\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`\n const name = component.NAME\n\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)\n const instance = component.getOrCreateInstance(target)\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]()\n })\n}\n\nexport {\n enableDismissTrigger\n}\n"],"names":["enableDismissTrigger","component","method","clickEvent","EVENT_KEY","name","NAME","EventHandler","on","document","event","includes","tagName","preventDefault","isDisabled","target","SelectorEngine","getElementFromSelector","closest","instance","getOrCreateInstance"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;AAMA,QAAMA,oBAAoB,GAAGA,CAACC,SAAS,EAAEC,MAAM,GAAG,MAAM,KAAK;EAC3D,EAAA,MAAMC,UAAU,GAAG,CAAA,aAAA,EAAgBF,SAAS,CAACG,SAAS,CAAA,CAAE;EACxD,EAAA,MAAMC,IAAI,GAAGJ,SAAS,CAACK,IAAI;EAE3BC,EAAAA,YAAY,CAACC,EAAE,CAACC,QAAQ,EAAEN,UAAU,EAAE,CAAA,kBAAA,EAAqBE,IAAI,CAAA,EAAA,CAAI,EAAE,UAAUK,KAAK,EAAE;EACpF,IAAA,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAACC,QAAQ,CAAC,IAAI,CAACC,OAAO,CAAC,EAAE;QACxCF,KAAK,CAACG,cAAc,EAAE;EACxB,IAAA;EAEA,IAAA,IAAIC,mBAAU,CAAC,IAAI,CAAC,EAAE;EACpB,MAAA;EACF,IAAA;EAEA,IAAA,MAAMC,MAAM,GAAGC,cAAc,CAACC,sBAAsB,CAAC,IAAI,CAAC,IAAI,IAAI,CAACC,OAAO,CAAC,CAAA,CAAA,EAAIb,IAAI,EAAE,CAAC;EACtF,IAAA,MAAMc,QAAQ,GAAGlB,SAAS,CAACmB,mBAAmB,CAACL,MAAM,CAAC;;EAEtD;EACAI,IAAAA,QAAQ,CAACjB,MAAM,CAAC,EAAE;EACpB,EAAA,CAAC,CAAC;EACJ;;;;;;;;;;"}
|
||||
{"version":3,"file":"component-functions.js","sources":["../../src/util/component-functions.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler.js'\nimport SelectorEngine from '../dom/selector-engine.js'\nimport { isDisabled } from './index.js'\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`\n const name = component.NAME\n\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n if (isDisabled(this)) {\n return\n }\n\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`)\n const instance = component.getOrCreateInstance(target)\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]()\n })\n}\n\nconst eventActionOnPlugin = (Plugin, onEvent, stringSelector, method, callback = null) => {\n eventAction(`${onEvent}.${Plugin.NAME}`, stringSelector, data => {\n const instances = data.targets.filter(Boolean).map(element => Plugin.getOrCreateInstance(element))\n if (typeof callback === 'function') {\n callback({ ...data, instances })\n }\n\n for (const instance of instances) {\n instance[method]()\n }\n })\n}\n\nconst eventAction = (onEvent, stringSelector, callback) => {\n const selector = `${stringSelector}:not(.disabled):not(:disabled)`\n EventHandler.on(document, onEvent, selector, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault()\n }\n\n const selector = SelectorEngine.getSelectorFromElement(this)\n const targets = selector ? SelectorEngine.find(selector) : [this]\n\n callback({ targets, event })\n })\n}\n\nexport {\n enableDismissTrigger,\n eventActionOnPlugin\n}\n"],"names":["enableDismissTrigger","component","method","clickEvent","EVENT_KEY","name","NAME","EventHandler","on","document","event","includes","tagName","preventDefault","isDisabled","target","SelectorEngine","getElementFromSelector","closest","instance","getOrCreateInstance","eventActionOnPlugin","Plugin","onEvent","stringSelector","callback","eventAction","data","instances","targets","filter","Boolean","map","element","selector","getSelectorFromElement","find"],"mappings":";;;;;;;;;;;EAAA;EACA;EACA;EACA;EACA;EACA;;AAMA,QAAMA,oBAAoB,GAAGA,CAACC,SAAS,EAAEC,MAAM,GAAG,MAAM,KAAK;EAC3D,EAAA,MAAMC,UAAU,GAAG,CAAA,aAAA,EAAgBF,SAAS,CAACG,SAAS,CAAA,CAAE;EACxD,EAAA,MAAMC,IAAI,GAAGJ,SAAS,CAACK,IAAI;EAE3BC,EAAAA,YAAY,CAACC,EAAE,CAACC,QAAQ,EAAEN,UAAU,EAAE,CAAA,kBAAA,EAAqBE,IAAI,CAAA,EAAA,CAAI,EAAE,UAAUK,KAAK,EAAE;EACpF,IAAA,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAACC,QAAQ,CAAC,IAAI,CAACC,OAAO,CAAC,EAAE;QACxCF,KAAK,CAACG,cAAc,EAAE;EACxB,IAAA;EAEA,IAAA,IAAIC,mBAAU,CAAC,IAAI,CAAC,EAAE;EACpB,MAAA;EACF,IAAA;EAEA,IAAA,MAAMC,MAAM,GAAGC,cAAc,CAACC,sBAAsB,CAAC,IAAI,CAAC,IAAI,IAAI,CAACC,OAAO,CAAC,CAAA,CAAA,EAAIb,IAAI,EAAE,CAAC;EACtF,IAAA,MAAMc,QAAQ,GAAGlB,SAAS,CAACmB,mBAAmB,CAACL,MAAM,CAAC;;EAEtD;EACAI,IAAAA,QAAQ,CAACjB,MAAM,CAAC,EAAE;EACpB,EAAA,CAAC,CAAC;EACJ;AAEA,QAAMmB,mBAAmB,GAAGA,CAACC,MAAM,EAAEC,OAAO,EAAEC,cAAc,EAAEtB,MAAM,EAAEuB,QAAQ,GAAG,IAAI,KAAK;EACxFC,EAAAA,WAAW,CAAC,CAAA,EAAGH,OAAO,CAAA,CAAA,EAAID,MAAM,CAAChB,IAAI,CAAA,CAAE,EAAEkB,cAAc,EAAEG,IAAI,IAAI;MAC/D,MAAMC,SAAS,GAAGD,IAAI,CAACE,OAAO,CAACC,MAAM,CAACC,OAAO,CAAC,CAACC,GAAG,CAACC,OAAO,IAAIX,MAAM,CAACF,mBAAmB,CAACa,OAAO,CAAC,CAAC;EAClG,IAAA,IAAI,OAAOR,QAAQ,KAAK,UAAU,EAAE;EAClCA,MAAAA,QAAQ,CAAC;EAAE,QAAA,GAAGE,IAAI;EAAEC,QAAAA;EAAU,OAAC,CAAC;EAClC,IAAA;EAEA,IAAA,KAAK,MAAMT,QAAQ,IAAIS,SAAS,EAAE;EAChCT,MAAAA,QAAQ,CAACjB,MAAM,CAAC,EAAE;EACpB,IAAA;EACF,EAAA,CAAC,CAAC;EACJ;EAEA,MAAMwB,WAAW,GAAGA,CAACH,OAAO,EAAEC,cAAc,EAAEC,QAAQ,KAAK;EACzD,EAAA,MAAMS,QAAQ,GAAG,CAAA,EAAGV,cAAc,CAAA,8BAAA,CAAgC;IAClEjB,YAAY,CAACC,EAAE,CAACC,QAAQ,EAAEc,OAAO,EAAEW,QAAQ,EAAE,UAAUxB,KAAK,EAAE;EAC5D,IAAA,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAACC,QAAQ,CAAC,IAAI,CAACC,OAAO,CAAC,EAAE;QACxCF,KAAK,CAACG,cAAc,EAAE;EACxB,IAAA;EAEA,IAAA,MAAMqB,QAAQ,GAAGlB,cAAc,CAACmB,sBAAsB,CAAC,IAAI,CAAC;EAC5D,IAAA,MAAMN,OAAO,GAAGK,QAAQ,GAAGlB,cAAc,CAACoB,IAAI,CAACF,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;EAEjET,IAAAA,QAAQ,CAAC;QAAEI,OAAO;EAAEnB,MAAAA;EAAM,KAAC,CAAC;EAC9B,EAAA,CAAC,CAAC;EACJ,CAAC;;;;;;;;;;;"}
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
/*!
|
||||
* Bootstrap config.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 floating-ui.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 focustrap.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 index.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 sanitizer.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 scrollbar.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 swipe.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 template-factory.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) {
|
||||
|
||||
@@ -9,6 +9,7 @@ export { default as Alert } from './src/alert.js'
|
||||
export { default as Button } from './src/button.js'
|
||||
export { default as Carousel } from './src/carousel.js'
|
||||
export { default as Collapse } from './src/collapse.js'
|
||||
export { default as Datepicker } from './src/datepicker.js'
|
||||
export { default as Dialog } from './src/dialog.js'
|
||||
export { default as Dropdown } from './src/dropdown.js'
|
||||
export { default as Offcanvas } from './src/offcanvas.js'
|
||||
|
||||
@@ -9,6 +9,7 @@ import Alert from './src/alert.js'
|
||||
import Button from './src/button.js'
|
||||
import Carousel from './src/carousel.js'
|
||||
import Collapse from './src/collapse.js'
|
||||
import Datepicker from './src/datepicker.js'
|
||||
import Dialog from './src/dialog.js'
|
||||
import Dropdown from './src/dropdown.js'
|
||||
import Offcanvas from './src/offcanvas.js'
|
||||
@@ -26,6 +27,7 @@ export default {
|
||||
Button,
|
||||
Carousel,
|
||||
Collapse,
|
||||
Datepicker,
|
||||
Dialog,
|
||||
Dropdown,
|
||||
Offcanvas,
|
||||
|
||||
@@ -0,0 +1,482 @@
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Bootstrap datepicker.js
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { Calendar } from 'vanilla-calendar-pro'
|
||||
import BaseComponent from './base-component.js'
|
||||
import EventHandler from './dom/event-handler.js'
|
||||
import { isDisabled } from './util/index.js'
|
||||
|
||||
/**
|
||||
* 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 || 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 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)
|
||||
}
|
||||
})
|
||||
|
||||
export default Datepicker
|
||||
File diff suppressed because it is too large
Load Diff
@@ -243,6 +243,40 @@ describe('OtpInput', () => {
|
||||
|
||||
expect(inputs[0].value).toEqual('1')
|
||||
})
|
||||
|
||||
it('should distribute multi-character input across inputs', () => {
|
||||
fixtureEl.innerHTML = getOtpHtml()
|
||||
|
||||
const otpEl = fixtureEl.querySelector('.otp-input')
|
||||
new OtpInput(otpEl) // eslint-disable-line no-new
|
||||
const inputs = otpEl.querySelectorAll('input')
|
||||
|
||||
inputs[0].focus()
|
||||
// Simulate autofill that puts multiple characters in first input
|
||||
inputs[0].value = '1234'
|
||||
inputs[0].dispatchEvent(createEvent('input'))
|
||||
|
||||
expect(inputs[0].value).toEqual('1')
|
||||
expect(inputs[1].value).toEqual('2')
|
||||
expect(inputs[2].value).toEqual('3')
|
||||
expect(inputs[3].value).toEqual('4')
|
||||
expect(document.activeElement).toEqual(inputs[3])
|
||||
})
|
||||
|
||||
it('should not advance when entering digit in last input', () => {
|
||||
fixtureEl.innerHTML = getOtpHtml()
|
||||
|
||||
const otpEl = fixtureEl.querySelector('.otp-input')
|
||||
new OtpInput(otpEl) // eslint-disable-line no-new
|
||||
const inputs = otpEl.querySelectorAll('input')
|
||||
|
||||
inputs[5].focus()
|
||||
inputs[5].value = '9'
|
||||
inputs[5].dispatchEvent(createEvent('input'))
|
||||
|
||||
expect(inputs[5].value).toEqual('9')
|
||||
expect(document.activeElement).toEqual(inputs[5])
|
||||
})
|
||||
})
|
||||
|
||||
describe('keydown handling', () => {
|
||||
@@ -301,6 +335,86 @@ describe('OtpInput', () => {
|
||||
|
||||
expect(document.activeElement).toEqual(inputs[3])
|
||||
})
|
||||
|
||||
it('should not navigate left when at first input', () => {
|
||||
fixtureEl.innerHTML = getOtpHtml()
|
||||
|
||||
const otpEl = fixtureEl.querySelector('.otp-input')
|
||||
new OtpInput(otpEl) // eslint-disable-line no-new
|
||||
const inputs = otpEl.querySelectorAll('input')
|
||||
|
||||
inputs[0].focus()
|
||||
|
||||
const arrowEvent = new KeyboardEvent('keydown', {
|
||||
key: 'ArrowLeft',
|
||||
bubbles: true
|
||||
})
|
||||
inputs[0].dispatchEvent(arrowEvent)
|
||||
|
||||
expect(document.activeElement).toEqual(inputs[0])
|
||||
})
|
||||
|
||||
it('should not navigate right when at last input', () => {
|
||||
fixtureEl.innerHTML = getOtpHtml()
|
||||
|
||||
const otpEl = fixtureEl.querySelector('.otp-input')
|
||||
new OtpInput(otpEl) // eslint-disable-line no-new
|
||||
const inputs = otpEl.querySelectorAll('input')
|
||||
|
||||
inputs[5].focus()
|
||||
|
||||
const arrowEvent = new KeyboardEvent('keydown', {
|
||||
key: 'ArrowRight',
|
||||
bubbles: true
|
||||
})
|
||||
inputs[5].dispatchEvent(arrowEvent)
|
||||
|
||||
expect(document.activeElement).toEqual(inputs[5])
|
||||
})
|
||||
|
||||
it('should shift values left on Delete key', () => {
|
||||
fixtureEl.innerHTML = getOtpHtml()
|
||||
|
||||
const otpEl = fixtureEl.querySelector('.otp-input')
|
||||
const otp = new OtpInput(otpEl)
|
||||
const inputs = otpEl.querySelectorAll('input')
|
||||
|
||||
otp.setValue('123456')
|
||||
inputs[2].focus()
|
||||
|
||||
const deleteEvent = new KeyboardEvent('keydown', {
|
||||
key: 'Delete',
|
||||
bubbles: true
|
||||
})
|
||||
inputs[2].dispatchEvent(deleteEvent)
|
||||
|
||||
expect(inputs[0].value).toEqual('1')
|
||||
expect(inputs[1].value).toEqual('2')
|
||||
expect(inputs[2].value).toEqual('4')
|
||||
expect(inputs[3].value).toEqual('5')
|
||||
expect(inputs[4].value).toEqual('6')
|
||||
expect(inputs[5].value).toEqual('')
|
||||
})
|
||||
|
||||
it('should not move focus on backspace when current input has value', () => {
|
||||
fixtureEl.innerHTML = getOtpHtml()
|
||||
|
||||
const otpEl = fixtureEl.querySelector('.otp-input')
|
||||
new OtpInput(otpEl) // eslint-disable-line no-new
|
||||
const inputs = otpEl.querySelectorAll('input')
|
||||
|
||||
inputs[1].value = '5'
|
||||
inputs[1].focus()
|
||||
|
||||
const backspaceEvent = new KeyboardEvent('keydown', {
|
||||
key: 'Backspace',
|
||||
bubbles: true
|
||||
})
|
||||
inputs[1].dispatchEvent(backspaceEvent)
|
||||
|
||||
// Should stay on same input (browser handles clearing the value)
|
||||
expect(document.activeElement).toEqual(inputs[1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('paste handling', () => {
|
||||
|
||||
@@ -106,6 +106,40 @@ describe('Strength', () => {
|
||||
|
||||
expect(strength._input).toEqual(otherInput)
|
||||
})
|
||||
|
||||
it('should handle missing input gracefully', () => {
|
||||
fixtureEl.innerHTML = `
|
||||
<div class="strength" data-bs-strength>
|
||||
<div class="strength-segment"></div>
|
||||
<div class="strength-segment"></div>
|
||||
<div class="strength-segment"></div>
|
||||
<div class="strength-segment"></div>
|
||||
</div>
|
||||
`
|
||||
|
||||
const strengthEl = fixtureEl.querySelector('.strength')
|
||||
const strength = new Strength(strengthEl)
|
||||
|
||||
expect(strength._input).toBeNull()
|
||||
expect(strength.getStrength()).toBeNull()
|
||||
})
|
||||
|
||||
it('should use element directly when input config is an element', () => {
|
||||
fixtureEl.innerHTML = `
|
||||
<div>
|
||||
<input type="password" id="my-password" class="form-control">
|
||||
</div>
|
||||
<div class="strength" data-bs-strength>
|
||||
<div class="strength-segment"></div>
|
||||
</div>
|
||||
`
|
||||
|
||||
const strengthEl = fixtureEl.querySelector('.strength')
|
||||
const inputEl = fixtureEl.querySelector('#my-password')
|
||||
const strength = new Strength(strengthEl, { input: inputEl })
|
||||
|
||||
expect(strength._input).toEqual(inputEl)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getStrength', () => {
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="../../../dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<title>Datepicker</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container py-5">
|
||||
<h1>Datepicker <small class="text-body-secondary">Bootstrap Visual Test</small></h1>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Basic Input Datepicker</h2>
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="basicDatepicker" class="form-label">Select a date</label>
|
||||
<input type="text" class="form-control" id="basicDatepicker" data-bs-toggle="datepicker" placeholder="Click to select">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>With Min/Max Dates</h2>
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="minMaxDatepicker" class="form-label">Only dates in 2025</label>
|
||||
<input type="text" class="form-control" id="minMaxDatepicker" data-bs-toggle="datepicker" data-bs-date-min="2025-01-01" data-bs-date-max="2025-12-31" placeholder="Select a date in 2025">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Multiple Selection</h2>
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="multipleDatepicker" class="form-label">Select multiple dates</label>
|
||||
<input type="text" class="form-control" id="multipleDatepicker" data-bs-toggle="datepicker" data-bs-selection-mode="multiple" placeholder="Click to select multiple">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Range Selection</h2>
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="rangeDatepicker" class="form-label">Select a date range</label>
|
||||
<input type="text" class="form-control" id="rangeDatepicker" data-bs-toggle="datepicker" data-bs-selection-mode="multiple-ranged" placeholder="Select start and end">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Week Numbers</h2>
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="weekNumbersDatepicker" class="form-label">With week numbers</label>
|
||||
<input type="text" class="form-control" id="weekNumbersDatepicker" data-bs-toggle="datepicker" data-bs-show-week-numbers="true" placeholder="Select a date">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Sunday First</h2>
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="sundayFirstDatepicker" class="form-label">Week starts on Sunday</label>
|
||||
<input type="text" class="form-control" id="sundayFirstDatepicker" data-bs-toggle="datepicker" data-bs-first-weekday="0" placeholder="Select a date">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Dark Mode</h2>
|
||||
<div class="row mb-4" data-bs-theme="dark">
|
||||
<div class="col-md-6">
|
||||
<div class="p-4 bg-dark rounded">
|
||||
<label for="darkDatepicker" class="form-label text-light">Dark mode datepicker</label>
|
||||
<input type="text" class="form-control" id="darkDatepicker" data-bs-toggle="datepicker" placeholder="Select a date">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>JavaScript Initialization</h2>
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="jsDatepicker" class="form-label">Initialized via JavaScript</label>
|
||||
<input type="text" class="form-control" id="jsDatepicker" placeholder="Click to select">
|
||||
<div class="mt-2">
|
||||
<button type="button" class="btn btn-primary btn-sm" id="showBtn">Show</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" id="hideBtn">Hide</button>
|
||||
<button type="button" class="btn btn-info btn-sm" id="getDatesBtn">Get Dates</button>
|
||||
</div>
|
||||
<div id="selectedDatesOutput" class="mt-2 text-body-secondary"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Events Test</h2>
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="eventsDatepicker" class="form-label">Events datepicker</label>
|
||||
<input type="text" class="form-control" id="eventsDatepicker" data-bs-toggle="datepicker" placeholder="Select a date">
|
||||
<div id="eventsLog" class="mt-2 p-2 bg-light border rounded" style="min-height: 100px; font-family: monospace; font-size: 12px; white-space: pre-wrap;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="../../../dist/js/bootstrap.bundle.js"></script>
|
||||
<script>
|
||||
/* global bootstrap: false */
|
||||
|
||||
// Initialize via JavaScript
|
||||
const jsDatepickerEl = document.getElementById('jsDatepicker')
|
||||
const jsDatepicker = new bootstrap.Datepicker(jsDatepickerEl, {
|
||||
firstWeekday: 1,
|
||||
selectedDates: ['2025-01-15']
|
||||
})
|
||||
|
||||
document.getElementById('showBtn').addEventListener('click', () => {
|
||||
jsDatepicker.show()
|
||||
})
|
||||
|
||||
document.getElementById('hideBtn').addEventListener('click', () => {
|
||||
jsDatepicker.hide()
|
||||
})
|
||||
|
||||
document.getElementById('getDatesBtn').addEventListener('click', () => {
|
||||
const dates = jsDatepicker.getSelectedDates()
|
||||
document.getElementById('selectedDatesOutput').textContent = `Selected: ${dates.length ? dates.join(', ') : 'None'}`
|
||||
})
|
||||
|
||||
// Events test
|
||||
const eventsDatepickerEl = document.getElementById('eventsDatepicker')
|
||||
const eventsLog = document.getElementById('eventsLog')
|
||||
|
||||
function logEvent(eventName, detail) {
|
||||
const timestamp = new Date().toLocaleTimeString()
|
||||
const detailStr = detail ? ` - ${JSON.stringify(detail)}` : ''
|
||||
eventsLog.textContent += `[${timestamp}] ${eventName}${detailStr}\n`
|
||||
eventsLog.scrollTop = eventsLog.scrollHeight
|
||||
}
|
||||
|
||||
eventsDatepickerEl.addEventListener('show.bs.datepicker', () => {
|
||||
logEvent('show.bs.datepicker')
|
||||
})
|
||||
|
||||
eventsDatepickerEl.addEventListener('shown.bs.datepicker', () => {
|
||||
logEvent('shown.bs.datepicker')
|
||||
})
|
||||
|
||||
eventsDatepickerEl.addEventListener('hide.bs.datepicker', () => {
|
||||
logEvent('hide.bs.datepicker')
|
||||
})
|
||||
|
||||
eventsDatepickerEl.addEventListener('hidden.bs.datepicker', () => {
|
||||
logEvent('hidden.bs.datepicker')
|
||||
})
|
||||
|
||||
eventsDatepickerEl.addEventListener('change.bs.datepicker', event => {
|
||||
logEvent('change.bs.datepicker', { dates: event.dates })
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+311
-128
@@ -19,7 +19,8 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"postcss-prefix-custom-properties": "^0.1.0"
|
||||
"postcss-prefix-custom-properties": "^0.1.0",
|
||||
"vanilla-calendar-pro": "^3.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/check": "^0.9.5",
|
||||
@@ -46,7 +47,6 @@
|
||||
"autoprefixer": "^10.4.22",
|
||||
"bootstrap-vscode-theme": "^0.0.9",
|
||||
"bundlewatch": "^0.4.1",
|
||||
"clean-css-cli": "^5.6.3",
|
||||
"clipboard": "^2.0.11",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "8.57.1",
|
||||
@@ -74,6 +74,7 @@
|
||||
"karma-jasmine": "^5.1.0",
|
||||
"karma-jasmine-html-reporter": "^2.1.0",
|
||||
"karma-rollup-preprocessor": "7.0.7",
|
||||
"lightningcss": "^1.30.2",
|
||||
"lockfile-lint": "^4.14.1",
|
||||
"markdownlint-cli": "^0.45.0",
|
||||
"mime": "^4.1.0",
|
||||
@@ -93,6 +94,7 @@
|
||||
"shelljs": "^0.10.0",
|
||||
"stylelint": "^16.25.0",
|
||||
"stylelint-config-twbs-bootstrap": "^16.1.0",
|
||||
"stylelint-order": "^7.0.1",
|
||||
"terser": "^5.44.1",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"zod": "^4.1.12"
|
||||
@@ -6486,122 +6488,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/clean-css": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz",
|
||||
"integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"source-map": "~0.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/clean-css-cli": {
|
||||
"version": "5.6.3",
|
||||
"resolved": "https://registry.npmjs.org/clean-css-cli/-/clean-css-cli-5.6.3.tgz",
|
||||
"integrity": "sha512-MUAta8pEqA/d2DKQwtZU5nm0Og8TCyAglOx3GlWwjhGdKBwY4kVF6E5M6LU/jmmuswv+HbYqG/dKKkq5p1dD0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^3.5.2",
|
||||
"clean-css": "^5.3.3",
|
||||
"commander": "7.x",
|
||||
"glob": "^7.1.6"
|
||||
},
|
||||
"bin": {
|
||||
"cleancss": "bin/cleancss"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/clean-css-cli/node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/clean-css-cli/node_modules/commander": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
||||
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/clean-css-cli/node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/clean-css-cli/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/clean-css-cli/node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/clean-css/node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/clean-regexp": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz",
|
||||
@@ -7300,7 +7186,6 @@
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -11862,6 +11747,267 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
|
||||
"integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"lightningcss-android-arm64": "1.30.2",
|
||||
"lightningcss-darwin-arm64": "1.30.2",
|
||||
"lightningcss-darwin-x64": "1.30.2",
|
||||
"lightningcss-freebsd-x64": "1.30.2",
|
||||
"lightningcss-linux-arm-gnueabihf": "1.30.2",
|
||||
"lightningcss-linux-arm64-gnu": "1.30.2",
|
||||
"lightningcss-linux-arm64-musl": "1.30.2",
|
||||
"lightningcss-linux-x64-gnu": "1.30.2",
|
||||
"lightningcss-linux-x64-musl": "1.30.2",
|
||||
"lightningcss-win32-arm64-msvc": "1.30.2",
|
||||
"lightningcss-win32-x64-msvc": "1.30.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-android-arm64": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
|
||||
"integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-arm64": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
|
||||
"integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-x64": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
|
||||
"integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-freebsd-x64": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
|
||||
"integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
|
||||
"integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-gnu": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
|
||||
"integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-musl": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
|
||||
"integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-gnu": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
|
||||
"integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-musl": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
|
||||
"integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-arm64-msvc": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
|
||||
"integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-x64-msvc": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
|
||||
"integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||
@@ -15799,9 +15945,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-sorting": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-8.0.2.tgz",
|
||||
"integrity": "sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==",
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-9.1.0.tgz",
|
||||
"integrity": "sha512-Mn8KJ45HNNG6JBpBizXcyf6LqY/qyqetGcou/nprDnFwBFBLGj0j/sNKV2lj2KMOVOwdXu14aEzqJv8CIV6e8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
@@ -18377,6 +18523,30 @@
|
||||
"stylelint": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint-config-recess-order/node_modules/postcss-sorting": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-8.0.2.tgz",
|
||||
"integrity": "sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.4.20"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint-config-recess-order/node_modules/stylelint-order": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.4.tgz",
|
||||
"integrity": "sha512-0UuKo4+s1hgQ/uAxlYU4h0o0HS4NiQDud0NAUNI0aa8FJdmYHA5ZZTFHiV5FpmE3071e9pZx5j0QpVJW5zOCUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"postcss": "^8.4.32",
|
||||
"postcss-sorting": "^8.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"stylelint": "^14.0.0 || ^15.0.0 || ^16.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint-config-recommended": {
|
||||
"version": "14.0.1",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-14.0.1.tgz",
|
||||
@@ -18505,17 +18675,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint-order": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.4.tgz",
|
||||
"integrity": "sha512-0UuKo4+s1hgQ/uAxlYU4h0o0HS4NiQDud0NAUNI0aa8FJdmYHA5ZZTFHiV5FpmE3071e9pZx5j0QpVJW5zOCUA==",
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-7.0.1.tgz",
|
||||
"integrity": "sha512-GWPei1zBVDDjxM+/BmcSCiOcHNd8rSqW6FUZtqQGlTRpD0Z5nSzspzWD8rtKif5KPdzUG68DApKEV/y/I9VbTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"postcss": "^8.4.32",
|
||||
"postcss-sorting": "^8.0.2"
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-sorting": "^9.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"stylelint": "^14.0.0 || ^15.0.0 || ^16.0.1"
|
||||
"stylelint": "^16.18.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint-scss": {
|
||||
@@ -19737,6 +19910,16 @@
|
||||
"spdx-expression-parse": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vanilla-calendar-pro": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/vanilla-calendar-pro/-/vanilla-calendar-pro-3.0.5.tgz",
|
||||
"integrity": "sha512-4X9bmTo1/KzbZrB7B6mZXtvVXIhcKxaVSnFZuaVtps7tshKJDxgaIElkgdia6IjB5qWetWuu7kZ+ZaV1sPxy6w==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://buymeacoffee.com/uvarov"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
||||
+5
-3
@@ -49,7 +49,7 @@
|
||||
"css-lint-stylelint": "stylelint \"**/*.{css,scss}\" --cache --cache-location .cache/.stylelintcache",
|
||||
"css-lint-vars": "fusv scss/ site/src/scss/",
|
||||
"css-minify": "npm-run-all --aggregate-output --parallel css-minify-*",
|
||||
"css-minify-main": "cleancss -O1 --format breakWith=lf --with-rebase --source-map --source-map-inline-sources --output dist/css/ --batch --batch-suffix \".min\" \"dist/css/*.css\" \"!dist/css/*.min.css\" \"!dist/css/*.tmp.css\"",
|
||||
"css-minify-main": "node build/css-minify.mjs",
|
||||
"css-prefix": "npm-run-all --aggregate-output --parallel css-prefix-*",
|
||||
"css-prefix-main": "postcss --config build/postcss.config.mjs --replace \"dist/css/*.css\" \"!dist/css/*.min.css\" \"!dist/css/*.tmp.css\"",
|
||||
"css-prefix-examples": "postcss --config build/postcss.config.mjs --replace \"site/src/assets/examples/**/*.css\"",
|
||||
@@ -130,7 +130,7 @@
|
||||
"autoprefixer": "^10.4.22",
|
||||
"bootstrap-vscode-theme": "^0.0.9",
|
||||
"bundlewatch": "^0.4.1",
|
||||
"clean-css-cli": "^5.6.3",
|
||||
"lightningcss": "^1.30.2",
|
||||
"clipboard": "^2.0.11",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "8.57.1",
|
||||
@@ -177,6 +177,7 @@
|
||||
"shelljs": "^0.10.0",
|
||||
"stylelint": "^16.25.0",
|
||||
"stylelint-config-twbs-bootstrap": "^16.1.0",
|
||||
"stylelint-order": "^7.0.1",
|
||||
"terser": "^5.44.1",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"zod": "^4.1.12"
|
||||
@@ -210,6 +211,7 @@
|
||||
"volar-service-emmet": "0.0.63"
|
||||
},
|
||||
"dependencies": {
|
||||
"postcss-prefix-custom-properties": "^0.1.0"
|
||||
"postcss-prefix-custom-properties": "^0.1.0",
|
||||
"vanilla-calendar-pro": "^3.0.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
// stylelint-disable selector-max-attribute, property-disallowed-list, selector-no-qualifying-type -- VCP uses extensive data attributes and requires direct border-radius properties for range selection
|
||||
|
||||
@use "config" as *;
|
||||
@use "colors" as *;
|
||||
@use "variables" as *;
|
||||
@use "mixins/border-radius" as *;
|
||||
@use "mixins/focus-ring" as *;
|
||||
|
||||
// scss-docs-start datepicker-variables
|
||||
$datepicker-padding: 1rem !default;
|
||||
$datepicker-bg: var(--bg-body) !default;
|
||||
$datepicker-color: var(--fg-body) !default;
|
||||
$datepicker-border-color: var(--border-color-translucent) !default;
|
||||
$datepicker-border-width: var(--border-width) !default;
|
||||
$datepicker-border-radius: var(--border-radius-lg) !default;
|
||||
$datepicker-box-shadow: var(--box-shadow) !default;
|
||||
$datepicker-font-size: var(--font-size-sm) !default;
|
||||
$datepicker-min-width: 280px !default;
|
||||
|
||||
$datepicker-header-font-weight: 600 !default;
|
||||
$datepicker-weekday-color: var(--fg-3) !default;
|
||||
$datepicker-day-hover-bg: var(--bg-1) !default;
|
||||
$datepicker-day-selected-bg: var(--primary-bg) !default;
|
||||
$datepicker-day-selected-color: var(--primary-contrast) !default;
|
||||
$datepicker-day-today-bg: var(--bg-2) !default;
|
||||
$datepicker-day-today-color: var(--fg-1) !default;
|
||||
$datepicker-day-disabled-color: var(--fg-4) !default;
|
||||
// scss-docs-end datepicker-variables
|
||||
|
||||
@layer components {
|
||||
[data-vc="calendar"] {
|
||||
// scss-docs-start datepicker-css-vars
|
||||
--datepicker-padding: #{$datepicker-padding};
|
||||
--datepicker-bg: #{$datepicker-bg};
|
||||
--datepicker-color: #{$datepicker-color};
|
||||
--datepicker-border-color: #{$datepicker-border-color};
|
||||
--datepicker-border-width: #{$datepicker-border-width};
|
||||
--datepicker-border-radius: #{$datepicker-border-radius};
|
||||
--datepicker-box-shadow: #{$datepicker-box-shadow};
|
||||
--datepicker-font-size: #{$datepicker-font-size};
|
||||
--datepicker-min-width: #{$datepicker-min-width};
|
||||
--datepicker-zindex: #{$zindex-dropdown};
|
||||
|
||||
--datepicker-header-font-weight: #{$datepicker-header-font-weight};
|
||||
--datepicker-weekday-color: #{$datepicker-weekday-color};
|
||||
--datepicker-day-hover-bg: #{$datepicker-day-hover-bg};
|
||||
--datepicker-day-selected-bg: #{$datepicker-day-selected-bg};
|
||||
--datepicker-day-selected-color: #{$datepicker-day-selected-color};
|
||||
--datepicker-day-today-bg: #{$datepicker-day-today-bg};
|
||||
--datepicker-day-today-color: #{$datepicker-day-today-color};
|
||||
--datepicker-day-disabled-color: #{$datepicker-day-disabled-color};
|
||||
// scss-docs-end datepicker-css-vars
|
||||
|
||||
position: absolute;
|
||||
z-index: var(--datepicker-zindex);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: var(--datepicker-min-width);
|
||||
padding: var(--datepicker-padding);
|
||||
font-family: var(--font-sans-serif);
|
||||
font-size: var(--datepicker-font-size);
|
||||
color: var(--datepicker-color);
|
||||
color-scheme: light dark;
|
||||
background-color: var(--datepicker-bg);
|
||||
border: var(--datepicker-border-width) solid var(--datepicker-border-color);
|
||||
@include border-radius(var(--datepicker-border-radius));
|
||||
box-shadow: var(--datepicker-box-shadow);
|
||||
opacity: 1;
|
||||
|
||||
// Respond to Bootstrap's color mode system
|
||||
&[data-bs-theme="light"] {
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
&[data-bs-theme="dark"] {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
// Catch-all for focus styles
|
||||
button:focus-visible {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
@include focus-ring();
|
||||
}
|
||||
}
|
||||
|
||||
[data-vc-calendar-hidden] {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
// Inline calendars
|
||||
//
|
||||
// Remove popover styling for more neutral styling
|
||||
[data-vc="calendar"]:not([data-vc-input]) {
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[data-vc-position="bottom"] {
|
||||
margin-block-start: .25rem;
|
||||
}
|
||||
|
||||
[data-vc-position="top"] {
|
||||
margin-block-end: -.25rem;
|
||||
}
|
||||
|
||||
[data-vc-arrow] {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
color: var(--datepicker-color);
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
@include border-radius($border-radius);
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
inset: .25rem;
|
||||
content: "";
|
||||
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%236b7280' d='M12 16c-.3 0-.5-.1-.7-.3l-6-6c-.4-.4-.4-1 0-1.4s1-.4 1.4 0l5.3 5.3 5.3-5.3c.4-.4 1-.4 1.4 0s.4 1 0 1.4l-6 6c-.2.2-.4.3-.7.3'/></svg>");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--datepicker-day-hover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
[data-vc-arrow="prev"]::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
[data-vc-arrow="next"]::before {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
// Grid layout
|
||||
[data-vc="controls"] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-top: 1rem;
|
||||
padding-right: 1rem;
|
||||
padding-left: 1rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-vc="grid"] {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.75rem;
|
||||
}
|
||||
|
||||
[data-vc="column"] {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
min-width: 240px;
|
||||
}
|
||||
|
||||
//
|
||||
// Header
|
||||
//
|
||||
|
||||
[data-vc="header"] {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: .75rem;
|
||||
}
|
||||
|
||||
// Month and year
|
||||
[data-vc-header="content"] {
|
||||
display: inline-flex;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
[data-vc="month"],
|
||||
[data-vc="year"] {
|
||||
padding: .25rem .5rem;
|
||||
margin-inline: -.125rem;
|
||||
font-size: 1rem;
|
||||
font-weight: var(--datepicker-header-font-weight);
|
||||
color: var(--datepicker-color);
|
||||
// cursor: pointer;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
@include border-radius($border-radius);
|
||||
|
||||
&:disabled {
|
||||
color: var(--datepicker-day-disabled-color);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: var(--datepicker-day-hover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[data-vc="content"] {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
// Month/Year grids
|
||||
[data-vc="months"],
|
||||
[data-vc="years"] {
|
||||
display: grid;
|
||||
flex-grow: 1;
|
||||
grid-template-columns: repeat(var(--vc-columns, 4), minmax(0, 1fr));
|
||||
row-gap: 1rem;
|
||||
column-gap: .25rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
[data-vc="years"] {
|
||||
--vc-columns: 5;
|
||||
}
|
||||
|
||||
[data-vc-months-month],
|
||||
[data-vc-years-year] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 2.5rem;
|
||||
padding: .25rem;
|
||||
font-size: .75rem;
|
||||
font-weight: 600;
|
||||
line-height: 1rem;
|
||||
color: var(--datepicker-weekday-color);
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
@include border-radius($border-radius);
|
||||
|
||||
&:disabled {
|
||||
color: var(--datepicker-day-disabled-color);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: var(--datepicker-day-hover-bg);
|
||||
}
|
||||
|
||||
&[data-vc-months-month-selected],
|
||||
&[data-vc-years-year-selected] {
|
||||
color: var(--datepicker-day-selected-color);
|
||||
background-color: var(--datepicker-day-selected-bg);
|
||||
|
||||
&:hover {
|
||||
color: var(--datepicker-day-selected-color);
|
||||
background-color: var(--datepicker-day-selected-bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Week days header
|
||||
[data-vc="week"] {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
justify-items: center;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
[data-vc-week-day] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-width: 1.875rem;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: .75rem;
|
||||
font-weight: 600;
|
||||
line-height: 1rem;
|
||||
color: var(--datepicker-weekday-color);
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
button[data-vc-week-day] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// Dates grid
|
||||
[data-vc="dates"] {
|
||||
display: grid;
|
||||
flex-grow: 1;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-vc-date] {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding-top: .125rem;
|
||||
padding-bottom: .125rem;
|
||||
pointer-events: auto;
|
||||
|
||||
&:not(:has([data-vc-date-btn])),
|
||||
&[data-vc-date-disabled],
|
||||
&[data-vc-date-disabled] [data-vc-date-btn] {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Date button
|
||||
[data-vc-date-btn] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-width: 1.875rem;
|
||||
height: 100%;
|
||||
min-height: 1.875rem;
|
||||
padding: 0;
|
||||
font-size: .75rem;
|
||||
font-weight: 400;
|
||||
line-height: 1rem;
|
||||
color: var(--datepicker-color);
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
border-radius: $border-radius;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--datepicker-day-hover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
// Today
|
||||
[data-vc-date-today] [data-vc-date-btn] {
|
||||
font-weight: 600;
|
||||
color: var(--datepicker-day-today-color);
|
||||
background-color: var(--datepicker-day-today-bg);
|
||||
}
|
||||
|
||||
|
||||
// Outside month
|
||||
[data-vc-date-month="next"] [data-vc-date-btn],
|
||||
[data-vc-date-month="prev"] [data-vc-date-btn] {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
// Disabled
|
||||
[data-vc-date-disabled] [data-vc-date-btn] {
|
||||
color: var(--datepicker-day-disabled-color);
|
||||
}
|
||||
|
||||
// Range selection styles
|
||||
[data-vc-date-hover] [data-vc-date-btn] {
|
||||
background-color: var(--datepicker-day-hover-bg);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
[data-vc-date-hover="first"] [data-vc-date-btn] {
|
||||
border-start-start-radius: $border-radius;
|
||||
border-end-start-radius: $border-radius;
|
||||
}
|
||||
|
||||
[data-vc-date-hover="last"] [data-vc-date-btn] {
|
||||
border-start-end-radius: $border-radius;
|
||||
border-end-end-radius: $border-radius;
|
||||
}
|
||||
|
||||
[data-vc-date-hover="first-and-last"] [data-vc-date-btn] {
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
[data-vc-date-selected="middle"] [data-vc-date-btn] {
|
||||
border-radius: 0;
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
// Selected
|
||||
[data-vc-date-selected] [data-vc-date-btn] {
|
||||
color: var(--datepicker-day-selected-color);
|
||||
background-color: var(--datepicker-day-selected-bg);
|
||||
|
||||
}
|
||||
|
||||
[data-vc-date-selected="first"] [data-vc-date-btn] {
|
||||
border-top-left-radius: $border-radius;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: $border-radius;
|
||||
}
|
||||
|
||||
[data-vc-date-selected="last"] [data-vc-date-btn] {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: $border-radius;
|
||||
border-bottom-right-radius: $border-radius;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
[data-vc-date-selected="first-and-last"] [data-vc-date-btn] {
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -89,9 +89,9 @@ $nav-underline-link-active-color: var(--emphasis-color) !default;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
--focus-ring-offset: 1px;
|
||||
color: var(--nav-link-hover-color);
|
||||
@include focus-ring(true);
|
||||
--focus-ring-offset: 1px;
|
||||
}
|
||||
|
||||
&.active,
|
||||
|
||||
+15
-17
@@ -11,9 +11,10 @@
|
||||
@layer colors, theme, config, root, reboot, layout, content, forms, components, custom, helpers, utilities;
|
||||
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
|
||||
// scss-docs-start root-theme-variables
|
||||
--black: #{$black};
|
||||
--white: #{$white};
|
||||
|
||||
// Generate semantic theme colors
|
||||
@each $color-name, $color-map in $new-theme-colors {
|
||||
@each $key, $value in $color-map {
|
||||
@@ -32,15 +33,13 @@
|
||||
@each $color, $value in $theme-borders {
|
||||
--border-#{$color}: #{$value};
|
||||
}
|
||||
|
||||
--black: #{$black};
|
||||
--white: #{$white};
|
||||
// scss-docs-end root-theme-variables
|
||||
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
:root,
|
||||
[data-bs-theme="light"] {
|
||||
color-scheme: light;
|
||||
|
||||
// Note: Custom variable values only support SassScript inside `#{}`.
|
||||
|
||||
@@ -54,12 +53,6 @@
|
||||
--font-sans-serif: #{meta.inspect($font-family-sans-serif)};
|
||||
--font-monospace: #{meta.inspect($font-family-monospace)};
|
||||
--gradient: #{$gradient};
|
||||
|
||||
// Root and body
|
||||
// scss-docs-start root-body-variables
|
||||
@if $font-size-root != null {
|
||||
--root-font-size: #{$font-size-root};
|
||||
}
|
||||
--body-font-family: #{meta.inspect($font-family-base)};
|
||||
|
||||
// scss-docs-start root-font-size-variables
|
||||
@@ -91,13 +84,9 @@
|
||||
--body-font-size: #{$font-size-base};
|
||||
--body-font-weight: #{$font-weight-base};
|
||||
--body-line-height: #{$line-height-base};
|
||||
@if $body-text-align != null {
|
||||
--body-text-align: #{$body-text-align};
|
||||
}
|
||||
|
||||
--body-color-rgb: #{to-rgb($body-color)};
|
||||
--body-bg-rgb: #{to-rgb($body-bg)};
|
||||
// scss-docs-end root-body-variables
|
||||
|
||||
--heading-color: #{$headings-color};
|
||||
|
||||
@@ -145,12 +134,20 @@
|
||||
--form-valid-border-color: #{$form-valid-border-color};
|
||||
--form-invalid-color: #{$form-invalid-color};
|
||||
--form-invalid-border-color: #{$form-invalid-border-color};
|
||||
color-scheme: light;
|
||||
// scss-docs-end root-form-validation-variables
|
||||
|
||||
// Root and body
|
||||
@if $font-size-root != null {
|
||||
--root-font-size: #{$font-size-root};
|
||||
}
|
||||
@if $body-text-align != null {
|
||||
--body-text-align: #{$body-text-align};
|
||||
}
|
||||
}
|
||||
|
||||
@if $enable-dark-mode {
|
||||
@include color-mode(dark, true) {
|
||||
color-scheme: dark;
|
||||
|
||||
// scss-docs-start root-dark-mode-vars
|
||||
--emphasis-color: #{$body-emphasis-color-dark};
|
||||
@@ -164,6 +161,7 @@
|
||||
--form-valid-border-color: #{$form-valid-border-color-dark};
|
||||
--form-invalid-color: #{$form-invalid-color-dark};
|
||||
--form-invalid-border-color: #{$form-invalid-border-color-dark};
|
||||
color-scheme: dark;
|
||||
// scss-docs-end root-dark-mode-vars
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -17,6 +17,7 @@
|
||||
@forward "breadcrumb";
|
||||
@forward "card";
|
||||
@forward "carousel";
|
||||
@forward "datepicker";
|
||||
@forward "dialog";
|
||||
@forward "dropdown";
|
||||
@forward "list-group";
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
@use "../config" as *;
|
||||
@use "../variables" as *;
|
||||
@use "../mixins/border-radius" as *;
|
||||
@use "../mixins/box-shadow" as *;
|
||||
@use "../mixins/focus-ring" as *;
|
||||
@use "../mixins/transition" as *;
|
||||
@use "form-variables" as *;
|
||||
|
||||
// scss-docs-start form-adorn-variables
|
||||
$form-adorn-gap: .375rem !default;
|
||||
$form-adorn-icon-size: 1rem !default;
|
||||
$form-adorn-icon-color: var(--fg-2) !default;
|
||||
// scss-docs-end form-adorn-variables
|
||||
|
||||
@layer forms {
|
||||
.form-adorn {
|
||||
// Inherit form-control CSS variables for sizing
|
||||
--control-min-height: #{$control-min-height};
|
||||
--control-padding-y: #{$control-padding-y};
|
||||
--control-padding-x: #{$control-padding-x};
|
||||
--control-font-size: #{$control-font-size};
|
||||
--control-line-height: #{$control-line-height};
|
||||
--control-color: #{$control-color};
|
||||
--control-bg: #{$control-bg};
|
||||
--control-border-width: #{$control-border-width};
|
||||
--control-border-color: #{$control-border-color};
|
||||
--control-border-radius: #{$control-border-radius};
|
||||
|
||||
// Adorn-specific variables
|
||||
--form-adorn-gap: #{$form-adorn-gap};
|
||||
--form-adorn-icon-size: #{$form-adorn-icon-size};
|
||||
--form-adorn-icon-color: #{$form-adorn-icon-color};
|
||||
|
||||
// Flexbox layout
|
||||
display: flex;
|
||||
gap: var(--form-adorn-gap);
|
||||
align-items: center;
|
||||
|
||||
// Replicate .form-control styles on the wrapper
|
||||
min-height: var(--control-min-height);
|
||||
padding: var(--control-padding-y) var(--control-padding-x);
|
||||
font-size: var(--control-font-size);
|
||||
line-height: var(--control-line-height);
|
||||
color: var(--control-color);
|
||||
background-color: var(--control-bg);
|
||||
background-clip: padding-box;
|
||||
border: var(--control-border-width) solid var(--control-border-color);
|
||||
@include border-radius(var(--control-border-radius), 0);
|
||||
@include box-shadow($input-box-shadow);
|
||||
@include transition($input-transition);
|
||||
|
||||
// Focus state when ghost input is focused
|
||||
&:focus-within {
|
||||
--focus-ring-offset: -1px;
|
||||
border-color: $input-focus-border-color;
|
||||
@include focus-ring(true);
|
||||
}
|
||||
|
||||
// Ghost input fills remaining space
|
||||
> .form-ghost {
|
||||
flex: 1;
|
||||
min-width: 0; // Prevent text overflow
|
||||
}
|
||||
|
||||
|
||||
// Adornment at end (right in LTR) - input comes first visually
|
||||
&.form-adorn-end > .form-ghost {
|
||||
order: -1;
|
||||
}
|
||||
}
|
||||
|
||||
.form-adorn-icon {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--form-adorn-icon-color);
|
||||
pointer-events: none;
|
||||
|
||||
> svg {
|
||||
width: var(--form-adorn-icon-size);
|
||||
height: var(--form-adorn-icon-size);
|
||||
}
|
||||
}
|
||||
|
||||
.form-adorn-text {
|
||||
flex-shrink: 0;
|
||||
color: var(--form-adorn-icon-color);
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
// Sizing variants
|
||||
.form-adorn-sm {
|
||||
--control-min-height: #{$control-min-height-sm};
|
||||
--control-padding-y: #{$control-padding-y-sm};
|
||||
--control-padding-x: #{$control-padding-x-sm};
|
||||
--control-font-size: #{$control-font-size-sm};
|
||||
--control-line-height: #{$control-line-height-sm};
|
||||
--control-border-radius: #{$control-border-radius-sm};
|
||||
}
|
||||
|
||||
.form-adorn-lg {
|
||||
--control-min-height: #{$control-min-height-lg};
|
||||
--control-padding-y: #{$control-padding-y-lg};
|
||||
--control-padding-x: #{$control-padding-x-lg};
|
||||
--control-font-size: #{$control-font-size-lg};
|
||||
--control-line-height: #{$control-line-height-lg};
|
||||
--control-border-radius: #{$control-border-radius-lg};
|
||||
}
|
||||
}
|
||||
@@ -53,11 +53,11 @@
|
||||
|
||||
// Customize the `:focus` state to imitate native WebKit styles.
|
||||
&:focus-visible {
|
||||
--focus-ring-offset: -1px;
|
||||
color: $input-focus-color;
|
||||
background-color: $input-focus-bg;
|
||||
border-color: $input-focus-border-color;
|
||||
@include focus-ring(true);
|
||||
--focus-ring-offset: -1px;
|
||||
}
|
||||
|
||||
&::-webkit-date-and-time-value {
|
||||
@@ -243,4 +243,31 @@
|
||||
&.form-control-sm { height: $input-height-sm; }
|
||||
&.form-control-lg { height: $input-height-lg; }
|
||||
}
|
||||
|
||||
// Ghost input - removes all visual styling
|
||||
// Used inside custom wrappers that handle their own styling
|
||||
.form-ghost {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--fg-3);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: var(--fg-4);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,8 @@ $input-group-addon-border-color: $input-border-color !default;
|
||||
|
||||
// stylelint-disable-next-line no-duplicate-selectors
|
||||
.input-group {
|
||||
|
||||
$validation-messages: "";
|
||||
&:not(.has-validation) {
|
||||
> :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),
|
||||
> .dropdown-toggle:nth-last-child(n + 3),
|
||||
@@ -130,11 +132,6 @@ $input-group-addon-border-color: $input-border-color !default;
|
||||
}
|
||||
}
|
||||
|
||||
$validation-messages: "";
|
||||
@each $state in map.keys($form-validation-states) {
|
||||
$validation-messages: $validation-messages + ":not(." + string.unquote($state) + "-tooltip)" + ":not(." + string.unquote($state) + "-feedback)";
|
||||
}
|
||||
|
||||
> :not(:first-child):not(.dropdown-menu)#{$validation-messages} {
|
||||
margin-inline-start: calc(-1 * #{$input-border-width});
|
||||
@include border-start-radius(0);
|
||||
@@ -144,5 +141,8 @@ $input-group-addon-border-color: $input-border-color !default;
|
||||
> .form-floating:not(:first-child) > .form-select {
|
||||
@include border-start-radius(0);
|
||||
}
|
||||
@each $state in map.keys($form-validation-states) {
|
||||
$validation-messages: $validation-messages + ":not(." + string.unquote($state) + "-tooltip)" + ":not(." + string.unquote($state) + "-feedback)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@ $otp-input-gap: .5rem !default;
|
||||
border-color: var(--form-valid-border-color);
|
||||
|
||||
&:focus {
|
||||
border-color: var(--form-valid-border-color);
|
||||
--focus-ring-color: rgba(var(--success-rgb), .25);
|
||||
border-color: var(--form-valid-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,8 +62,8 @@ $otp-input-gap: .5rem !default;
|
||||
border-color: var(--form-invalid-border-color);
|
||||
|
||||
&:focus {
|
||||
border-color: var(--form-invalid-border-color);
|
||||
--focus-ring-color: rgba(var(--danger-rgb), .25);
|
||||
border-color: var(--form-invalid-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,4 +9,5 @@
|
||||
@forward "input-group";
|
||||
@forward "strength";
|
||||
@forward "otp-input";
|
||||
@forward "form-adorn";
|
||||
@forward "validation";
|
||||
|
||||
@@ -30,13 +30,13 @@
|
||||
}
|
||||
|
||||
@include media-breakpoint-up($breakpoint, $grid-breakpoints) {
|
||||
// Extend each breakpoint which is smaller or equal to the current breakpoint
|
||||
$extend-breakpoint: true;
|
||||
|
||||
%responsive-container-#{$breakpoint} {
|
||||
max-width: $container-max-width;
|
||||
}
|
||||
|
||||
// Extend each breakpoint which is smaller or equal to the current breakpoint
|
||||
$extend-breakpoint: true;
|
||||
|
||||
@each $name, $width in $grid-breakpoints {
|
||||
@if ($extend-breakpoint) {
|
||||
.container#{breakpoint-infix($name, $grid-breakpoints)} {
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
- title: Floating labels
|
||||
- title: OTP input
|
||||
- title: Password strength
|
||||
- title: Form adorn
|
||||
- title: Layout
|
||||
- title: Validation
|
||||
|
||||
@@ -92,6 +93,7 @@
|
||||
- title: Carousel
|
||||
- title: Close button
|
||||
- title: Collapse
|
||||
- title: Datepicker
|
||||
- title: Dialog
|
||||
- title: Dropdown
|
||||
- title: List group
|
||||
|
||||
@@ -35,6 +35,10 @@
|
||||
d="M1.114 8.063V7.9c1.005-.102 1.497-.615 1.497-1.6V4.503c0-1.094.39-1.538 1.354-1.538h.273V2h-.376C2.25 2 1.49 2.759 1.49 4.352v1.524c0 1.094-.376 1.456-1.49 1.456v1.299c1.114 0 1.49.362 1.49 1.456v1.524c0 1.593.759 2.352 2.372 2.352h.376v-.964h-.273c-.964 0-1.354-.444-1.354-1.538V9.663c0-.984-.492-1.497-1.497-1.6ZM14.886 7.9v.164c-1.005.103-1.497.616-1.497 1.6v1.798c0 1.094-.39 1.538-1.354 1.538h-.273v.964h.376c1.613 0 2.372-.759 2.372-2.352v-1.524c0-1.094.376-1.456 1.49-1.456v-1.3c-1.114 0-1.49-.362-1.49-1.456V4.352C14.51 2.759 13.75 2 12.138 2h-.376v.964h.273c.964 0 1.354.444 1.354 1.538V6.3c0 .984.492 1.497 1.497 1.6ZM7.5 11.5V9.207l-1.621 1.621-.707-.707L6.792 8.5H4.5v-1h2.293L5.172 5.879l.707-.707L7.5 6.792V4.5h1v2.293l1.621-1.621.707.707L9.208 7.5H11.5v1H9.207l1.621 1.621-.707.707L8.5 9.208V11.5h-1Z"
|
||||
></path>
|
||||
</symbol>
|
||||
<symbol id="calendar-week" viewBox="0 0 16 16">
|
||||
<path d="M11 6.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5zm-3 0a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5zm-5 3a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5zm3 0a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5z"/>
|
||||
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5M1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4z"/>
|
||||
</symbol>
|
||||
<symbol id="check2" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"
|
||||
@@ -62,6 +66,9 @@
|
||||
d="M5.854 4.854a.5.5 0 1 0-.708-.708l-3.5 3.5a.5.5 0 0 0 0 .708l3.5 3.5a.5.5 0 0 0 .708-.708L2.707 8l3.147-3.146zm4.292 0a.5.5 0 0 1 .708-.708l3.5 3.5a.5.5 0 0 1 0 .708l-3.5 3.5a.5.5 0 0 1-.708-.708L13.293 8l-3.147-3.146z"
|
||||
></path>
|
||||
</symbol>
|
||||
<symbol id="envelope" viewBox="0 0 16 16">
|
||||
<path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1zm13 2.383-4.708 2.825L15 11.105zm-.034 6.876-5.64-3.471L8 9.583l-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h12a1 1 0 0 0 .966-.741M1 11.105l4.708-2.897L1 5.383z"/>
|
||||
</symbol>
|
||||
<symbol id="file-earmark-richtext" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"
|
||||
@@ -128,6 +135,9 @@
|
||||
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"
|
||||
></path>
|
||||
</symbol>
|
||||
<symbol id="search" viewBox="0 0 16 16">
|
||||
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/>
|
||||
</symbol>
|
||||
<symbol id="sun-fill" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
---
|
||||
title: Datepicker
|
||||
description: A flexible date picker component powered by Vanilla Calendar Pro, with Bootstrap styling and data attribute support.
|
||||
toc: true
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The Bootstrap Datepicker is a wrapper around [Vanilla Calendar Pro](https://vanilla-calendar.pro/) that provides a consistent, accessible date selection experience. It supports light/dark themes, input binding, and flexible configuration via data attributes or JavaScript.
|
||||
|
||||
<Example code={`<label for="datepicker1" class="form-label">Datepicker</label>
|
||||
<input type="text" class="form-control w-12" data-bs-toggle="datepicker" placeholder="Choose date…">`} />
|
||||
|
||||
Note that we're using a width utility of `.w-12` to ensure the input is wide enough to accommodate the date format and imply some affordance for the expected type of input.
|
||||
|
||||
## How it works
|
||||
|
||||
- Add `data-bs-toggle="datepicker"` to any `<input>` element to enable the datepicker
|
||||
- Use `type="text"` to avoid conflicts with native browser date pickers
|
||||
- When focused, the calendar popup appears below the input
|
||||
- Selecting a date updates the input value and closes the picker
|
||||
- The picker respects Bootstrap's color modes (`data-bs-theme`)
|
||||
- Configurable with any [Vanilla Calendar Pro option](https://vanilla-calendar.pro/docs/reference/settings) via `vcpOptions` when initializing with JavaScript
|
||||
|
||||
## Examples
|
||||
|
||||
### With icon
|
||||
|
||||
Use the [form adorn component](/docs/forms/form-adorn) to add a calendar icon alongside the datepicker input. When the input is inside a `.form-adorn` wrapper, the calendar automatically positions relative to the wrapper instead of the input.
|
||||
|
||||
<Example code={`<label for="datepickerIconStart" class="form-label">Select date</label>
|
||||
<div class="form-adorn w-12">
|
||||
<div class="form-adorn-icon">
|
||||
<svg class="bi" width="16" height="16"><use href="#calendar-week" /></svg>
|
||||
</div>
|
||||
<input type="text" class="form-ghost" id="datepickerIconStart" data-bs-toggle="datepicker" placeholder="Choose date…">
|
||||
</div>`} />
|
||||
|
||||
### Min & Max dates
|
||||
|
||||
Restrict the selectable date range using `data-bs-date-min` and `data-bs-date-max`.
|
||||
|
||||
<Example code={`<label for="datepicker2" class="form-label">Event date (2025 only)</label>
|
||||
<input type="text" class="form-control w-12" id="datepicker2" data-bs-toggle="datepicker" data-bs-date-min="2025-01-01" data-bs-date-max="2025-12-31" placeholder="Select a date in 2025">`} />
|
||||
|
||||
### Multiple dates
|
||||
|
||||
Enable multiple date selection with `data-bs-selection-mode="multiple"`.
|
||||
|
||||
<Example code={`<label for="datepicker3" class="form-label">Select multiple dates</label>
|
||||
<input type="text" class="form-control" id="datepicker3" data-bs-toggle="datepicker" data-bs-selection-mode="multiple" placeholder="Select date range…">`} />
|
||||
|
||||
### Multiple months
|
||||
|
||||
Display multiple months side-by-side with the `displayMonthsCount` option. This is useful for date range selection where users need to see more context.
|
||||
|
||||
<Example code={`<label for="datepickerMultiMonth" class="form-label">Select date range</label>
|
||||
<input type="text" class="form-control" id="datepickerMultiMonth" data-bs-toggle="datepicker" data-bs-selection-mode="multiple-ranged" data-bs-display-months-count="2" placeholder="Select start and end dates">`} />
|
||||
|
||||
### Date range
|
||||
|
||||
Select a range of dates with `data-bs-selection-mode="multiple-ranged"`. Use `data-bs-selected-dates` to preselect a date range.
|
||||
|
||||
<Example code={`<label for="datepicker4" class="form-label">Select date range</label>
|
||||
<input type="text" class="form-control" id="datepicker4" data-bs-toggle="datepicker" data-bs-selection-mode="multiple-ranged" data-bs-selected-dates='["2025-06-10", "2025-06-18"]' placeholder="Select start and end dates…">`} />
|
||||
|
||||
### Multi-month date range
|
||||
|
||||
For selecting date ranges that span multiple months, combine `data-bs-selection-mode="multiple-ranged"` with `data-bs-display-months-count="2"` to show two months side-by-side, making it easier for users to select across month boundaries.
|
||||
|
||||
<Example code={`<label for="datepickerRangeTwoMonths" class="form-label">Select date range</label>
|
||||
<input type="text" class="form-control" id="datepickerRangeTwoMonths" data-bs-toggle="datepicker" data-bs-selection-mode="multiple-ranged" data-bs-display-months-count="2" data-bs-selected-dates='["2025-06-25", "2025-07-08"]' placeholder="Select start and end dates…">`} />
|
||||
|
||||
## Options
|
||||
|
||||
### First day of week
|
||||
|
||||
Set the first day of the week (0 = Sunday, 1 = Monday, etc.) with `data-bs-first-weekday`.
|
||||
|
||||
<Example code={`<label for="datepicker6" class="form-label">Week starts on Sunday</label>
|
||||
<input type="text" class="form-control w-12" id="datepicker6" data-bs-toggle="datepicker" data-bs-first-weekday="0" placeholder="Select a date">`} />
|
||||
|
||||
### Placement
|
||||
|
||||
Control where the calendar appears relative to the input with `data-bs-placement`. Options are `left` (default), `center`, `right`, and `auto`.
|
||||
|
||||
<Example code={`<div class="d-flex gap-3">
|
||||
<div>
|
||||
<label for="datepickerLeft" class="form-label">Left aligned</label>
|
||||
<input type="text" class="form-control" id="datepickerLeft" data-bs-toggle="datepicker" data-bs-placement="left" placeholder="Left">
|
||||
</div>
|
||||
<div>
|
||||
<label for="datepickerCenter" class="form-label">Center aligned</label>
|
||||
<input type="text" class="form-control" id="datepickerCenter" data-bs-toggle="datepicker" data-bs-placement="center" placeholder="Center">
|
||||
</div>
|
||||
<div>
|
||||
<label for="datepickerRight" class="form-label">Right aligned</label>
|
||||
<input type="text" class="form-control" id="datepickerRight" data-bs-toggle="datepicker" data-bs-placement="right" placeholder="Right">
|
||||
</div>
|
||||
</div>`} />
|
||||
|
||||
### Button trigger
|
||||
|
||||
Use a button instead of an input for use cases like dashboard date filters. Add `data-bs-datepicker-display` to the text element to preserve icons when the date updates.
|
||||
|
||||
<Example code={`<button type="button" class="btn btn-outline-secondary" data-bs-toggle="datepicker">
|
||||
<svg class="bi" width="16" height="16"><use href="#calendar-week" /></svg>
|
||||
<span data-bs-datepicker-display>Select date</span>
|
||||
</button>`} />
|
||||
|
||||
For date range selection (e.g., dashboard time filters), use `data-bs-selection-mode="multiple-ranged"`. The calendar will close after both start and end dates are selected.
|
||||
|
||||
<Example code={`<button type="button" class="btn btn-outline-secondary" data-bs-toggle="datepicker" data-bs-selection-mode="multiple-ranged">
|
||||
<svg class="bi" width="16" height="16"><use href="#calendar-week" /></svg>
|
||||
<span data-bs-datepicker-display>Last 7 days</span>
|
||||
</button>`} />
|
||||
|
||||
You can also display the selected date in a separate element using the `displayElement` option via JavaScript:
|
||||
|
||||
```js
|
||||
const datepicker = new bootstrap.Datepicker(buttonElement, {
|
||||
selectionMode: 'multiple-ranged',
|
||||
displayElement: '#date-display' // Selector or element
|
||||
})
|
||||
```
|
||||
|
||||
### Inline mode
|
||||
|
||||
Render the calendar inline (always visible, no popup) with `data-bs-inline="true"`. This is useful for embedding a calendar directly in the page.
|
||||
|
||||
<Example code={`<div data-bs-toggle="datepicker" data-bs-inline="true"></div>`} />
|
||||
|
||||
Inline datepickers with date range selection:
|
||||
|
||||
<Example code={`<div data-bs-toggle="datepicker" data-bs-inline="true" data-bs-selection-mode="multiple-ranged"></div>`} />
|
||||
|
||||
Multiple months inline:
|
||||
|
||||
<Example code={`<div data-bs-toggle="datepicker" data-bs-inline="true" data-bs-display-months-count="2"></div>`} />
|
||||
|
||||
To bind to a form field, include a hidden input inside the container. The value will be updated with the selected date(s) in `YYYY-MM-DD` format:
|
||||
|
||||
<Example code={`<form>
|
||||
<div data-bs-toggle="datepicker" data-bs-inline="true">
|
||||
<input type="hidden" name="selected_date">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mt-3">Submit</button>
|
||||
</form>`} />
|
||||
|
||||
### Custom date formatting
|
||||
|
||||
Control how dates are displayed using the `dateFormat` option. Pass an [`Intl.DateTimeFormat` options object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options) or a custom function.
|
||||
|
||||
```js
|
||||
// Using Intl.DateTimeFormat options
|
||||
const datepicker = new bootstrap.Datepicker(element, {
|
||||
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' }
|
||||
// Output: "Dec 23, 2025 – Dec 28, 2025"
|
||||
})
|
||||
|
||||
// Using a custom function
|
||||
const datepicker = new bootstrap.Datepicker(element, {
|
||||
dateFormat: (date, locale) => {
|
||||
return date.toLocaleDateString(locale, { month: 'short', day: 'numeric' })
|
||||
}
|
||||
// Output: "Dec 23 – Dec 28"
|
||||
})
|
||||
```
|
||||
|
||||
## Dark mode
|
||||
|
||||
The datepicker automatically adapts to Bootstrap's color modes. When `data-bs-theme="dark"` is set on a parent element or the `<html>` tag, the calendar popup inherits that theme.
|
||||
|
||||
### Inherited from parent
|
||||
|
||||
When a parent element has a theme, both the input and calendar popup inherit it:
|
||||
|
||||
<Example code={`<div data-bs-theme="dark" class="p-3 bg-body fg-body rounded">
|
||||
<label for="datepickerDark" class="form-label">Dark mode datepicker</label>
|
||||
<input type="text" class="form-control" id="datepickerDark" data-bs-toggle="datepicker" placeholder="Select a date">
|
||||
</div>`} />
|
||||
|
||||
### Datepicker-only theme
|
||||
|
||||
Use `data-bs-datepicker-theme` to set the datepicker popup's theme independently of the input. This is useful when you want a light input with a dark datepicker, or vice versa:
|
||||
|
||||
<Example code={`<label for="datepickerTheme" class="form-label">Light input, dark datepicker</label>
|
||||
<input type="text" class="form-control w-12" id="datepickerTheme" data-bs-toggle="datepicker" data-bs-datepicker-theme="dark" placeholder="Select a date">`} />
|
||||
|
||||
## Usage
|
||||
|
||||
### Via data attributes
|
||||
|
||||
Add `data-bs-toggle="datepicker"` to any input element to initialize it as a datepicker.
|
||||
|
||||
```html
|
||||
<input type="text" class="form-control" data-bs-toggle="datepicker">
|
||||
```
|
||||
|
||||
### Via JavaScript
|
||||
|
||||
Initialize datepickers programmatically:
|
||||
|
||||
```js
|
||||
const datepickerEl = document.getElementById('myDatepicker')
|
||||
const datepicker = new bootstrap.Datepicker(datepickerEl, {
|
||||
selectionMode: 'single',
|
||||
firstWeekday: 1
|
||||
})
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
<BsTable>
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `dateMin` | string, number, Date | `null` | Minimum selectable date. Format: `YYYY-MM-DD` |
|
||||
| `dateMax` | string, number, Date | `null` | Maximum selectable date. Format: `YYYY-MM-DD` |
|
||||
| `dateFormat` | object, function | `null` | Date formatting. Pass `Intl.DateTimeFormat` options or a `function(date, locale)`. |
|
||||
| `displayElement` | string, element, boolean | `null` | Element to show formatted date. For buttons, defaults to the button itself. Set to `false` to disable. |
|
||||
| `displayMonthsCount` | number | `1` | Number of months to display side-by-side in the calendar. |
|
||||
| `firstWeekday` | number | `1` | First day of week (0 = Sunday, 1 = Monday, etc.) |
|
||||
| `inline` | boolean | `false` | Render calendar inline (always visible, no popup). |
|
||||
| `locale` | string | `'default'` | Locale for date formatting (e.g., `'en-US'`, `'de-DE'`) |
|
||||
| `positionElement` | string, element | `null` | Element to position calendar relative to. Auto-detects `.form-adorn` wrapper if present. |
|
||||
| `selectedDates` | array | `[]` | Pre-selected dates in `YYYY-MM-DD` format |
|
||||
| `selectionMode` | string | `'single'` | Selection mode: `'single'`, `'multiple'`, or `'multiple-ranged'` |
|
||||
| `placement` | string | `'left'` | Calendar position relative to input: `'left'`, `'center'`, `'right'`, `'auto'` |
|
||||
| `datepickerTheme` | string | `null` | Force datepicker popup theme: `'light'`, `'dark'`, `'auto'`, or `null` to inherit from ancestor `[data-bs-theme]` |
|
||||
| `vcpOptions` | object | `{}` | Pass-through object for any [Vanilla Calendar Pro option](https://vanilla-calendar.pro/docs/reference/settings) |
|
||||
</BsTable>
|
||||
|
||||
### Advanced configuration
|
||||
|
||||
For features not directly exposed by Bootstrap's options, use `vcpOptions` to pass any Vanilla Calendar Pro setting:
|
||||
|
||||
```js
|
||||
const datepicker = new bootstrap.Datepicker(element, {
|
||||
vcpOptions: {
|
||||
disableDatesPast: true, // Disable past dates
|
||||
disableWeekdays: [0, 6], // Disable weekends
|
||||
disableDates: ['2025-12-25', '2025-12-26'], // Disable specific dates
|
||||
selectedHolidays: ['2025-01-01'], // Highlight holidays
|
||||
selectionTimeMode: 24 // Enable 24-hour time selection
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
See the [Vanilla Calendar Pro documentation](https://vanilla-calendar.pro/docs/reference/settings) for all available options.
|
||||
|
||||
### Methods
|
||||
|
||||
<BsTable>
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| `show()` | Shows the datepicker calendar |
|
||||
| `hide()` | Hides the datepicker calendar |
|
||||
| `toggle()` | Toggles the datepicker visibility |
|
||||
| `getSelectedDates()` | Returns an array of selected dates in `YYYY-MM-DD` format |
|
||||
| `setSelectedDates(dates)` | Sets the selected dates. Expects an array of `YYYY-MM-DD` strings |
|
||||
| `dispose()` | Destroys the datepicker instance |
|
||||
| `getInstance(element)` | Static method to get the datepicker instance from a DOM element |
|
||||
| `getOrCreateInstance(element)` | Static method to get or create a datepicker instance |
|
||||
</BsTable>
|
||||
|
||||
### Events
|
||||
|
||||
<BsTable>
|
||||
| Event | Description |
|
||||
| --- | --- |
|
||||
| `show.bs.datepicker` | Fires immediately when the `show` method is called |
|
||||
| `shown.bs.datepicker` | Fires when the datepicker has been made visible |
|
||||
| `hide.bs.datepicker` | Fires immediately when the `hide` method is called |
|
||||
| `hidden.bs.datepicker` | Fires when the datepicker has been hidden |
|
||||
| `change.bs.datepicker` | Fires when a date is selected. Event includes `dates` (array) and `event` properties |
|
||||
</BsTable>
|
||||
|
||||
```js
|
||||
const datepickerEl = document.getElementById('myDatepicker')
|
||||
datepickerEl.addEventListener('change.bs.datepicker', event => {
|
||||
console.log('Selected dates:', event.dates)
|
||||
})
|
||||
```
|
||||
@@ -18,13 +18,11 @@ Here are our guidelines and reasons for choosing what to override in Reboot:
|
||||
- For easier scaling across device sizes, block elements should use `rem`s for `margin`s.
|
||||
- Keep declarations of `font`-related properties to a minimum, using `inherit` whenever possible.
|
||||
|
||||
## CSS variables
|
||||
## Root CSS variables
|
||||
|
||||
With v5.1.1, we standardized our required `@import`s across all our CSS bundles (including `bootstrap.css`, `bootstrap-reboot.css`, and `bootstrap-grid.css`) to include `_root.scss`. This adds `:root` level CSS variables to all bundles, regardless of how many of them are used in that bundle. Ultimately Bootstrap 5 will continue to see more [CSS variables]([[docsref:/customize/css-variables]]) added over time, in order to provide more real-time customization without the need to always recompile Sass. Our approach is to take our source Sass variables and transform them into CSS variables. That way, even if you don’t use CSS variables, you still have all the power of Sass. **This is still in-progress and will take time to fully implement.**
|
||||
We add `:root` level CSS variables to all CSS bundles to provide real-time customization without the need to always recompile Sass. For example, consider these `:root` CSS variables for common `<body>` styles:
|
||||
|
||||
For example, consider these `:root` CSS variables for common `<body>` styles:
|
||||
|
||||
<ScssDocs name="root-body-variables" file="scss/_root.scss" />
|
||||
<ScssDocs name="root-border-var" file="scss/_root.scss" />
|
||||
|
||||
In practice, those variables are then applied in Reboot like so:
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
---
|
||||
title: Form adorn
|
||||
description: Decorate inputs with icons, text, and more using a custom wrapper that easily handles styling and positioning.
|
||||
toc: true
|
||||
---
|
||||
|
||||
## How it works
|
||||
|
||||
The `.form-adorn` wrapper replicates `.form-control` styling (border, background, focus states) while using flexbox to position adornments alongside a ghost input. The `.form-ghost` input inside has no visual styling—it's transparent and inherits from the wrapper.
|
||||
|
||||
## Example
|
||||
|
||||
Wrap an icon and a `.form-ghost` input inside `.form-adorn`. Place the adornment before the input in the DOM for start position (left in LTR).
|
||||
|
||||
<Example code={`<div class="form-adorn">
|
||||
<div class="form-adorn-icon">
|
||||
<svg class="bi" width="16" height="16"><use href="#search" /></svg>
|
||||
</div>
|
||||
<input type="text" class="form-ghost" placeholder="Search...">
|
||||
</div>`} />
|
||||
|
||||
Use `.form-adorn-end` to position the adornment on the trailing side (keeps DOM order, uses CSS to flip visually):
|
||||
|
||||
<Example code={`<div class="form-adorn form-adorn-end">
|
||||
<div class="form-adorn-icon">
|
||||
<svg class="bi" width="16" height="16"><use href="#envelope" /></svg>
|
||||
</div>
|
||||
<input type="email" class="form-ghost" placeholder="you@example.com">
|
||||
</div>`} />
|
||||
|
||||
## With labels
|
||||
|
||||
Add a label outside the `.form-adorn` wrapper for proper form semantics:
|
||||
|
||||
<Example class="vstack gap-3" code={`<div>
|
||||
<label for="searchInput" class="form-label">Search</label>
|
||||
<div class="form-adorn">
|
||||
<div class="form-adorn-icon">
|
||||
<svg class="bi" width="16" height="16"><use href="#search" /></svg>
|
||||
</div>
|
||||
<input type="text" class="form-ghost" id="searchInput" placeholder="Search...">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="emailInput" class="form-label">Email address</label>
|
||||
<div class="form-adorn form-adorn-end">
|
||||
<div class="form-adorn-icon">
|
||||
<svg class="bi" width="16" height="16"><use href="#envelope" /></svg>
|
||||
</div>
|
||||
<input type="email" class="form-ghost" id="emailInput" placeholder="you@example.com">
|
||||
</div>
|
||||
</div>`} />
|
||||
|
||||
## Text adornments
|
||||
|
||||
Use `.form-adorn-text` for currency symbols, units, domain suffixes, and other text-based adornments. Text adornments auto-size to their content.
|
||||
|
||||
<Example class="vstack gap-3" code={`<div class="form-adorn">
|
||||
<span class="form-adorn-text">$</span>
|
||||
<input type="text" class="form-ghost" placeholder="0.00">
|
||||
</div>
|
||||
<div class="form-adorn form-adorn-end">
|
||||
<span class="form-adorn-text">USD</span>
|
||||
<input type="text" class="form-ghost" placeholder="Amount">
|
||||
</div>
|
||||
<div class="form-adorn">
|
||||
<span class="form-adorn-text">https://</span>
|
||||
<input type="text" class="form-ghost" placeholder="example.com">
|
||||
</div>
|
||||
<div class="form-adorn form-adorn-end">
|
||||
<span class="form-adorn-text">@example.com</span>
|
||||
<input type="text" class="form-ghost" placeholder="username">
|
||||
</div>`} />
|
||||
|
||||
## Sizing
|
||||
|
||||
Use `.form-adorn-sm` or `.form-adorn-lg` on the wrapper to adjust sizing.
|
||||
|
||||
<Example class="vstack gap-3" code={`<div class="form-adorn form-adorn-sm">
|
||||
<div class="form-adorn-icon">
|
||||
<svg class="bi" width="16" height="16"><use href="#search" /></svg>
|
||||
</div>
|
||||
<input type="text" class="form-ghost" placeholder="Small input">
|
||||
</div>
|
||||
<div class="form-adorn">
|
||||
<div class="form-adorn-icon">
|
||||
<svg class="bi" width="16" height="16"><use href="#search" /></svg>
|
||||
</div>
|
||||
<input type="text" class="form-ghost" placeholder="Default input">
|
||||
</div>
|
||||
<div class="form-adorn form-adorn-lg">
|
||||
<div class="form-adorn-icon">
|
||||
<svg class="bi" width="16" height="16"><use href="#search" /></svg>
|
||||
</div>
|
||||
<input type="text" class="form-ghost" placeholder="Large input">
|
||||
</div>`} />
|
||||
|
||||
## Ghost input
|
||||
|
||||
The `.form-ghost` class strips all visual styling from an input, making it transparent. It's designed for use inside custom wrappers like `.form-adorn` that handle their own border, background, and focus states.
|
||||
|
||||
<Example code={`<input type="text" class="form-ghost" placeholder="Ghost input (no styling)">`} />
|
||||
|
||||
## CSS
|
||||
|
||||
### Sass variables
|
||||
|
||||
<ScssDocs name="form-adorn-variables" file="scss/forms/_form-adorn.scss" />
|
||||
@@ -13,11 +13,11 @@
|
||||
}
|
||||
|
||||
.astro-code {
|
||||
--bs-font-monospace: "Geist Mono";
|
||||
display: flex;
|
||||
padding: var(--bd-example-padding);
|
||||
margin-bottom: 0;
|
||||
line-height: 20px;
|
||||
--bs-font-monospace: "Geist Mono";
|
||||
background-color: var(--bd-pre-bg) !important; // stylelint-disable-line declaration-no-important
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user