Compare commits

..

1 Commits

Author SHA1 Message Date
Mark Otto 2b9f1fcaab Rebuild dist for v6 2025-12-29 15:00:16 -08:00
86 changed files with 466 additions and 5870 deletions
+12 -12
View File
@@ -6,7 +6,7 @@
},
{
"path": "./dist/css/bootstrap-grid.min.css",
"maxSize": "10.25 kB"
"maxSize": "8.5 kB"
},
{
"path": "./dist/css/bootstrap-reboot.css",
@@ -14,47 +14,47 @@
},
{
"path": "./dist/css/bootstrap-reboot.min.css",
"maxSize": "6.75 kB"
"maxSize": "4.25 kB"
},
{
"path": "./dist/css/bootstrap-utilities.css",
"maxSize": "14.25 kB"
"maxSize": "14.5 kB"
},
{
"path": "./dist/css/bootstrap-utilities.min.css",
"maxSize": "15.0 kB"
"maxSize": "12.75 kB"
},
{
"path": "./dist/css/bootstrap.css",
"maxSize": "37.5 kB"
"maxSize": "36.0 kB"
},
{
"path": "./dist/css/bootstrap.min.css",
"maxSize": "36.25 kB"
"maxSize": "32.5 kB"
},
{
"path": "./dist/js/bootstrap.bundle.js",
"maxSize": "67.75 kB"
"maxSize": "49.75 kB"
},
{
"path": "./dist/js/bootstrap.bundle.min.js",
"maxSize": "41.0 kB"
"maxSize": "26.0 kB"
},
{
"path": "./dist/js/bootstrap.esm.js",
"maxSize": "39.0 kB"
"maxSize": "36.0 kB"
},
{
"path": "./dist/js/bootstrap.esm.min.js",
"maxSize": "24.0 kB"
"maxSize": "22.25 kB"
},
{
"path": "./dist/js/bootstrap.js",
"maxSize": "39.75 kB"
"maxSize": "36.5 kB"
},
{
"path": "./dist/js/bootstrap.min.js",
"maxSize": "21.25 kB"
"maxSize": "19.75 kB"
}
],
"ci": {
-15
View File
@@ -2,23 +2,8 @@
"extends": [
"stylelint-config-twbs-bootstrap"
],
"plugins": [
"stylelint-order"
],
"reportInvalidScopeDisables": true,
"reportNeedlessDisables": true,
"rules": {
"order/order": [
[
{ "type": "at-rule", "name": "use" },
{ "type": "at-rule", "name": "forward" },
"dollar-variables",
"custom-properties",
"declarations",
"rules"
]
]
},
"overrides": [
{
"files": "**/*.scss",
-61
View File
@@ -1,61 +0,0 @@
#!/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!')
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+6 -6
View File
@@ -312,8 +312,7 @@
@layer colors, theme, config, root, reboot, layout, content, forms, components, custom, helpers, utilities;
:root {
--bs-black: #000;
--bs-white: #fff;
color-scheme: light dark;
--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));
@@ -410,16 +409,19 @@
--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);
color-scheme: light dark;
--bs-black: #000;
--bs-white: #fff;
}
: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;
@@ -477,11 +479,10 @@
--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,7 +492,6 @@
--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 {
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+6 -6
View File
@@ -312,8 +312,7 @@
@layer colors, theme, config, root, reboot, layout, content, forms, components, custom, helpers, utilities;
:root {
--bs-black: #000;
--bs-white: #fff;
color-scheme: light dark;
--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));
@@ -410,16 +409,19 @@
--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);
color-scheme: light dark;
--bs-black: #000;
--bs-white: #fff;
}
: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;
@@ -477,11 +479,10 @@
--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,7 +492,6 @@
--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 {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+11 -445
View File
@@ -313,8 +313,7 @@
@layer colors, theme, config, root, reboot, layout, content, forms, components, custom, helpers, utilities;
:root {
--bs-black: #000;
--bs-white: #fff;
color-scheme: light dark;
--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));
@@ -411,16 +410,19 @@
--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);
color-scheme: light dark;
--bs-black: #000;
--bs-white: #fff;
}
: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;
@@ -478,11 +480,10 @@
--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;
@@ -492,7 +493,6 @@
--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 {
@@ -2731,12 +2731,12 @@
cursor: pointer;
}
.form-control:focus-visible {
--bs-focus-ring-offset: -1px;
color: var(--bs-color-body);
background-color: var(--bs-bg-body);
border-color: var(--bs-primary-border);
outline: var(--bs-focus-ring);
outline-offset: var(--bs-focus-ring-offset);
--bs-focus-ring-offset: -1px;
}
.form-control::-webkit-date-and-time-value {
min-width: 85px;
@@ -2847,28 +2847,6 @@
.form-control-color.form-control-lg {
height: 3rem;
}
.form-ghost {
display: block;
width: 100%;
padding: 0;
font: inherit;
color: inherit;
-webkit-appearance: none;
appearance: none;
background: transparent;
border: 0;
}
.form-ghost:focus {
outline: 0;
}
.form-ghost::placeholder {
color: var(--bs-fg-3);
opacity: 1;
}
.form-ghost:disabled {
color: var(--bs-fg-4);
cursor: not-allowed;
}
}
@layer forms {
b-checkgroup {
@@ -3316,7 +3294,7 @@
border-start-end-radius: 0;
border-end-end-radius: 0;
}
.input-group > :not(:first-child):not(.dropdown-menu) {
.input-group > :not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) {
margin-inline-start: calc(-1 * var(--bs-border-width));
border-start-start-radius: 0;
border-end-start-radius: 0;
@@ -3458,15 +3436,15 @@
border-color: var(--bs-form-valid-border-color);
}
.otp.is-valid .form-control:focus, .was-validated .otp:valid .form-control:focus {
--bs-focus-ring-color: rgba(var(--bs-success-rgb), .25);
border-color: var(--bs-form-valid-border-color);
--bs-focus-ring-color: rgba(var(--bs-success-rgb), .25);
}
.otp.is-invalid .form-control, .was-validated .otp:invalid .form-control {
border-color: var(--bs-form-invalid-border-color);
}
.otp.is-invalid .form-control:focus, .was-validated .otp:invalid .form-control:focus {
--bs-focus-ring-color: rgba(var(--bs-danger-rgb), .25);
border-color: var(--bs-form-invalid-border-color);
--bs-focus-ring-color: rgba(var(--bs-danger-rgb), .25);
}
.otp.input-group {
gap: 0;
@@ -3493,90 +3471,6 @@
--bs-otp-font-size: 21.875px;
}
}
@layer forms {
.form-adorn {
--bs-control-min-height: 2.5rem;
--bs-control-padding-y: 0.375rem;
--bs-control-padding-x: 0.75rem;
--bs-control-font-size: 14px;
--bs-control-line-height: 1.5;
--bs-control-color: var(--bs-color-body);
--bs-control-bg: var(--bs-bg-body);
--bs-control-border-width: var(--bs-border-width);
--bs-control-border-color: var(--bs-border-color);
--bs-control-border-radius: var(--bs-border-radius);
--bs-form-adorn-gap: 0.375rem;
--bs-form-adorn-icon-size: 1rem;
--bs-form-adorn-icon-color: var(--bs-fg-2);
display: flex;
gap: var(--bs-form-adorn-gap);
align-items: center;
min-height: var(--bs-control-min-height);
padding: var(--bs-control-padding-y) var(--bs-control-padding-x);
font-size: var(--bs-control-font-size);
line-height: var(--bs-control-line-height);
color: var(--bs-control-color);
background-color: var(--bs-control-bg);
background-clip: padding-box;
border: var(--bs-control-border-width) solid var(--bs-control-border-color);
border-radius: var(--bs-control-border-radius);
box-shadow: var(--bs-box-shadow-inset);
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
@media (prefers-reduced-motion: reduce) {
.form-adorn {
transition: none;
}
}
.form-adorn:focus-within {
--bs-focus-ring-offset: -1px;
border-color: var(--bs-primary-border);
outline: var(--bs-focus-ring);
outline-offset: var(--bs-focus-ring-offset);
}
.form-adorn > .form-ghost {
flex: 1;
min-width: 0;
}
.form-adorn.form-adorn-end > .form-ghost {
order: -1;
}
.form-adorn-icon {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
color: var(--bs-form-adorn-icon-color);
pointer-events: none;
}
.form-adorn-icon > svg {
width: var(--bs-form-adorn-icon-size);
height: var(--bs-form-adorn-icon-size);
}
.form-adorn-text {
flex-shrink: 0;
color: var(--bs-form-adorn-icon-color);
pointer-events: none;
-webkit-user-select: none;
user-select: none;
}
.form-adorn-sm {
--bs-control-min-height: 2rem;
--bs-control-padding-y: 0.25rem;
--bs-control-padding-x: 0.5rem;
--bs-control-font-size: 12.25px;
--bs-control-line-height: 1.25;
--bs-control-border-radius: var(--bs-border-radius-sm);
}
.form-adorn-lg {
--bs-control-min-height: 3rem;
--bs-control-padding-y: 0.5rem;
--bs-control-padding-x: 1rem;
--bs-control-font-size: 17.5px;
--bs-control-line-height: 2;
--bs-control-border-radius: var(--bs-border-radius-lg);
}
}
@layer components {
.tooltip {
--bs-tooltip-zindex: 1080;
@@ -4882,334 +4776,6 @@
--bs-carousel-control-icon-filter: invert(1) grayscale(100);
}
}
@layer components {
[data-vc=calendar] {
--bs-datepicker-padding: 1rem;
--bs-datepicker-bg: var(--bs-bg-body);
--bs-datepicker-color: var(--bs-fg-body);
--bs-datepicker-border-color: var(--bs-border-color-translucent);
--bs-datepicker-border-width: var(--bs-border-width);
--bs-datepicker-border-radius: var(--bs-border-radius-lg);
--bs-datepicker-box-shadow: var(--bs-box-shadow);
--bs-datepicker-font-size: var(--bs-font-size-sm);
--bs-datepicker-min-width: 280px;
--bs-datepicker-zindex: 1000;
--bs-datepicker-header-font-weight: 600;
--bs-datepicker-weekday-color: var(--bs-fg-3);
--bs-datepicker-day-hover-bg: var(--bs-bg-1);
--bs-datepicker-day-selected-bg: var(--bs-primary-bg);
--bs-datepicker-day-selected-color: var(--bs-primary-contrast);
--bs-datepicker-day-today-bg: var(--bs-bg-2);
--bs-datepicker-day-today-color: var(--bs-fg-1);
--bs-datepicker-day-disabled-color: var(--bs-fg-4);
position: absolute;
z-index: var(--bs-datepicker-zindex);
box-sizing: border-box;
display: flex;
flex-direction: column;
min-width: var(--bs-datepicker-min-width);
padding: var(--bs-datepicker-padding);
font-family: var(--bs-font-sans-serif);
font-size: var(--bs-datepicker-font-size);
color: var(--bs-datepicker-color);
color-scheme: light dark;
background-color: var(--bs-datepicker-bg);
border: var(--bs-datepicker-border-width) solid var(--bs-datepicker-border-color);
border-radius: var(--bs-datepicker-border-radius);
box-shadow: var(--bs-datepicker-box-shadow);
opacity: 1;
}
[data-vc=calendar][data-bs-theme=light] {
color-scheme: light;
}
[data-vc=calendar][data-bs-theme=dark] {
color-scheme: dark;
}
[data-vc=calendar] button:focus-visible {
position: relative;
z-index: 1;
outline: var(--bs-focus-ring);
}
[data-vc-calendar-hidden] {
pointer-events: none;
opacity: 0;
}
[data-vc=calendar]:not([data-vc-input]) {
position: relative;
width: -moz-fit-content;
width: fit-content;
padding: 0;
border: 0;
box-shadow: none;
}
[data-vc-position=bottom] {
margin-block-start: 0.25rem;
}
[data-vc-position=top] {
margin-block-end: -0.25rem;
}
[data-vc-arrow] {
position: relative;
display: block;
width: 2rem;
height: 2rem;
color: var(--bs-datepicker-color);
pointer-events: auto;
cursor: pointer;
background-color: transparent;
border: 0;
border-radius: 0.5rem;
}
[data-vc-arrow]::before {
position: absolute;
inset: 0.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;
}
[data-vc-arrow]:hover {
background-color: var(--bs-datepicker-day-hover-bg);
}
[data-vc-arrow=prev]::before {
transform: rotate(90deg);
}
[data-vc-arrow=next]::before {
transform: rotate(-90deg);
}
[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;
}
[data-vc=header] {
position: relative;
display: flex;
align-items: center;
margin-bottom: 0.75rem;
}
[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: 0.25rem 0.5rem;
margin-inline: -0.125rem;
font-size: 1rem;
font-weight: var(--bs-datepicker-header-font-weight);
color: var(--bs-datepicker-color);
background-color: transparent;
border: 0;
border-radius: 0.5rem;
}
[data-vc=month]:disabled,
[data-vc=year]:disabled {
color: var(--bs-datepicker-day-disabled-color);
pointer-events: none;
}
[data-vc=month]:hover:not(:disabled),
[data-vc=year]:hover:not(:disabled) {
background-color: var(--bs-datepicker-day-hover-bg);
}
[data-vc=content] {
display: flex;
flex-grow: 1;
flex-direction: column;
}
[data-vc=months],
[data-vc=years] {
display: grid;
flex-grow: 1;
grid-template-columns: repeat(var(--bs-vc-columns, 4), minmax(0, 1fr));
row-gap: 1rem;
-moz-column-gap: 0.25rem;
column-gap: 0.25rem;
align-items: center;
}
[data-vc=years] {
--bs-vc-columns: 5;
}
[data-vc-months-month],
[data-vc-years-year] {
display: flex;
align-items: center;
justify-content: center;
height: 2.5rem;
padding: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
line-height: 1rem;
color: var(--bs-datepicker-weekday-color);
text-align: center;
word-break: break-all;
cursor: pointer;
background-color: transparent;
border: 0;
border-radius: 0.5rem;
}
[data-vc-months-month]:disabled,
[data-vc-years-year]:disabled {
color: var(--bs-datepicker-day-disabled-color);
pointer-events: none;
}
[data-vc-months-month]:hover:not(:disabled),
[data-vc-years-year]:hover:not(:disabled) {
background-color: var(--bs-datepicker-day-hover-bg);
}
[data-vc-months-month][data-vc-months-month-selected], [data-vc-months-month][data-vc-years-year-selected],
[data-vc-years-year][data-vc-months-month-selected],
[data-vc-years-year][data-vc-years-year-selected] {
color: var(--bs-datepicker-day-selected-color);
background-color: var(--bs-datepicker-day-selected-bg);
}
[data-vc-months-month][data-vc-months-month-selected]:hover, [data-vc-months-month][data-vc-years-year-selected]:hover,
[data-vc-years-year][data-vc-months-month-selected]:hover,
[data-vc-years-year][data-vc-years-year-selected]:hover {
color: var(--bs-datepicker-day-selected-color);
background-color: var(--bs-datepicker-day-selected-bg);
}
[data-vc=week] {
display: grid;
grid-template-columns: repeat(7, 1fr);
justify-items: center;
margin-bottom: 0.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: 0.75rem;
font-weight: 600;
line-height: 1rem;
color: var(--bs-datepicker-weekday-color);
background-color: transparent;
border: 0;
}
button[data-vc-week-day] {
cursor: pointer;
}
[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: 0.125rem;
padding-bottom: 0.125rem;
pointer-events: auto;
}
[data-vc-date]:not(:has([data-vc-date-btn])), [data-vc-date][data-vc-date-disabled], [data-vc-date][data-vc-date-disabled] [data-vc-date-btn] {
pointer-events: none;
}
[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: 0.75rem;
font-weight: 400;
line-height: 1rem;
color: var(--bs-datepicker-color);
cursor: pointer;
background-color: transparent;
border: 0;
border-radius: 0.5rem;
}
[data-vc-date-btn]:hover {
background-color: var(--bs-datepicker-day-hover-bg);
}
[data-vc-date-today] [data-vc-date-btn] {
font-weight: 600;
color: var(--bs-datepicker-day-today-color);
background-color: var(--bs-datepicker-day-today-bg);
}
[data-vc-date-month=next] [data-vc-date-btn],
[data-vc-date-month=prev] [data-vc-date-btn] {
opacity: 0.5;
}
[data-vc-date-disabled] [data-vc-date-btn] {
color: var(--bs-datepicker-day-disabled-color);
}
[data-vc-date-hover] [data-vc-date-btn] {
background-color: var(--bs-datepicker-day-hover-bg);
border-radius: 0;
}
[data-vc-date-hover=first] [data-vc-date-btn] {
border-start-start-radius: 0.5rem;
border-end-start-radius: 0.5rem;
}
[data-vc-date-hover=last] [data-vc-date-btn] {
border-start-end-radius: 0.5rem;
border-end-end-radius: 0.5rem;
}
[data-vc-date-hover=first-and-last] [data-vc-date-btn] {
border-radius: 0.5rem;
}
[data-vc-date-selected=middle] [data-vc-date-btn] {
border-radius: 0;
opacity: 0.8;
}
[data-vc-date-selected] [data-vc-date-btn] {
color: var(--bs-datepicker-day-selected-color);
background-color: var(--bs-datepicker-day-selected-bg);
}
[data-vc-date-selected=first] [data-vc-date-btn] {
border-top-left-radius: 0.5rem;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0.5rem;
}
[data-vc-date-selected=last] [data-vc-date-btn] {
border-top-left-radius: 0;
border-top-right-radius: 0.5rem;
border-bottom-right-radius: 0.5rem;
border-bottom-left-radius: 0;
}
[data-vc-date-selected=first-and-last] [data-vc-date-btn] {
border-radius: 0.5rem;
}
}
@layer components {
.dialog-open {
overflow: hidden;
@@ -5855,10 +5421,10 @@
background-color: var(--bs-nav-link-hover-bg);
}
.nav-link:focus-visible {
--bs-focus-ring-offset: 1px;
color: var(--bs-nav-link-hover-color);
outline: var(--bs-focus-ring);
outline-offset: var(--bs-focus-ring-offset);
--bs-focus-ring-offset: 1px;
}
.nav-link.active, .nav-link:active {
color: var(--bs-nav-link-active-color);
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+69 -501
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -4
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+70 -499
View File
@@ -1,9 +1,8 @@
/*!
* Bootstrap v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
import { Calendar } from 'vanilla-calendar-pro';
import { computePosition, autoUpdate, offset, flip, shift, arrow } from '@floating-ui/dom';
/**
@@ -792,11 +791,11 @@ const eventAction = (onEvent, stringSelector, callback) => {
* Constants
*/
const NAME$j = 'alert';
const DATA_KEY$e = 'bs.alert';
const EVENT_KEY$f = `.${DATA_KEY$e}`;
const EVENT_CLOSE = `close${EVENT_KEY$f}`;
const EVENT_CLOSED = `closed${EVENT_KEY$f}`;
const NAME$i = 'alert';
const DATA_KEY$d = 'bs.alert';
const EVENT_KEY$e = `.${DATA_KEY$d}`;
const EVENT_CLOSE = `close${EVENT_KEY$e}`;
const EVENT_CLOSED = `closed${EVENT_KEY$e}`;
const CLASS_NAME_FADE$4 = 'fade';
const CLASS_NAME_SHOW$7 = 'show';
@@ -807,7 +806,7 @@ const CLASS_NAME_SHOW$7 = 'show';
class Alert extends BaseComponent {
// Getters
static get NAME() {
return NAME$j;
return NAME$i;
}
// Public
@@ -847,13 +846,13 @@ enableDismissTrigger(Alert, 'close');
* Constants
*/
const NAME$i = 'button';
const DATA_KEY$d = 'bs.button';
const EVENT_KEY$e = `.${DATA_KEY$d}`;
const DATA_API_KEY$9 = '.data-api';
const NAME$h = 'button';
const DATA_KEY$c = 'bs.button';
const EVENT_KEY$d = `.${DATA_KEY$c}`;
const DATA_API_KEY$8 = '.data-api';
const CLASS_NAME_ACTIVE$3 = 'active';
const SELECTOR_DATA_TOGGLE$9 = '[data-bs-toggle="button"]';
const EVENT_CLICK_DATA_API$7 = `click${EVENT_KEY$e}${DATA_API_KEY$9}`;
const SELECTOR_DATA_TOGGLE$8 = '[data-bs-toggle="button"]';
const EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$d}${DATA_API_KEY$8}`;
/**
* Class definition
@@ -862,7 +861,7 @@ const EVENT_CLICK_DATA_API$7 = `click${EVENT_KEY$e}${DATA_API_KEY$9}`;
class Button extends BaseComponent {
// Getters
static get NAME() {
return NAME$i;
return NAME$h;
}
// Public
@@ -876,9 +875,9 @@ class Button extends BaseComponent {
* Data API implementation
*/
EventHandler.on(document, EVENT_CLICK_DATA_API$7, SELECTOR_DATA_TOGGLE$9, event => {
EventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_TOGGLE$8, event => {
event.preventDefault();
const button = event.target.closest(SELECTOR_DATA_TOGGLE$9);
const button = event.target.closest(SELECTOR_DATA_TOGGLE$8);
const data = Button.getOrCreateInstance(button);
data.toggle();
});
@@ -895,23 +894,23 @@ EventHandler.on(document, EVENT_CLICK_DATA_API$7, SELECTOR_DATA_TOGGLE$9, event
* Constants
*/
const NAME$h = 'swipe';
const EVENT_KEY$d = '.bs.swipe';
const EVENT_TOUCHSTART = `touchstart${EVENT_KEY$d}`;
const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY$d}`;
const EVENT_TOUCHEND = `touchend${EVENT_KEY$d}`;
const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY$d}`;
const EVENT_POINTERUP = `pointerup${EVENT_KEY$d}`;
const NAME$g = 'swipe';
const EVENT_KEY$c = '.bs.swipe';
const EVENT_TOUCHSTART = `touchstart${EVENT_KEY$c}`;
const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY$c}`;
const EVENT_TOUCHEND = `touchend${EVENT_KEY$c}`;
const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY$c}`;
const EVENT_POINTERUP = `pointerup${EVENT_KEY$c}`;
const POINTER_TYPE_TOUCH = 'touch';
const POINTER_TYPE_PEN = 'pen';
const CLASS_NAME_POINTER_EVENT = 'pointer-event';
const SWIPE_THRESHOLD = 40;
const Default$g = {
const Default$f = {
endCallback: null,
leftCallback: null,
rightCallback: null
};
const DefaultType$g = {
const DefaultType$f = {
endCallback: '(function|null)',
leftCallback: '(function|null)',
rightCallback: '(function|null)'
@@ -936,18 +935,18 @@ class Swipe extends Config {
// Getters
static get Default() {
return Default$g;
return Default$f;
}
static get DefaultType() {
return DefaultType$g;
return DefaultType$f;
}
static get NAME() {
return NAME$h;
return NAME$g;
}
// Public
dispose() {
EventHandler.off(this._element, EVENT_KEY$d);
EventHandler.off(this._element, EVENT_KEY$c);
}
// Private
@@ -1015,10 +1014,10 @@ class Swipe extends Config {
* Constants
*/
const NAME$g = 'carousel';
const DATA_KEY$c = 'bs.carousel';
const EVENT_KEY$c = `.${DATA_KEY$c}`;
const DATA_API_KEY$8 = '.data-api';
const NAME$f = 'carousel';
const DATA_KEY$b = 'bs.carousel';
const EVENT_KEY$b = `.${DATA_KEY$b}`;
const DATA_API_KEY$7 = '.data-api';
const ARROW_LEFT_KEY$2 = 'ArrowLeft';
const ARROW_RIGHT_KEY$2 = 'ArrowRight';
const TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch
@@ -1027,14 +1026,14 @@ const ORDER_NEXT = 'next';
const ORDER_PREV = 'prev';
const DIRECTION_LEFT = 'left';
const DIRECTION_RIGHT = 'right';
const EVENT_SLIDE = `slide${EVENT_KEY$c}`;
const EVENT_SLID = `slid${EVENT_KEY$c}`;
const EVENT_KEYDOWN$1 = `keydown${EVENT_KEY$c}`;
const EVENT_MOUSEENTER$2 = `mouseenter${EVENT_KEY$c}`;
const EVENT_MOUSELEAVE$1 = `mouseleave${EVENT_KEY$c}`;
const EVENT_DRAG_START = `dragstart${EVENT_KEY$c}`;
const EVENT_LOAD_DATA_API$3 = `load${EVENT_KEY$c}${DATA_API_KEY$8}`;
const EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$c}${DATA_API_KEY$8}`;
const EVENT_SLIDE = `slide${EVENT_KEY$b}`;
const EVENT_SLID = `slid${EVENT_KEY$b}`;
const EVENT_KEYDOWN$1 = `keydown${EVENT_KEY$b}`;
const EVENT_MOUSEENTER$2 = `mouseenter${EVENT_KEY$b}`;
const EVENT_MOUSELEAVE$1 = `mouseleave${EVENT_KEY$b}`;
const EVENT_DRAG_START = `dragstart${EVENT_KEY$b}`;
const EVENT_LOAD_DATA_API$3 = `load${EVENT_KEY$b}${DATA_API_KEY$7}`;
const EVENT_CLICK_DATA_API$5 = `click${EVENT_KEY$b}${DATA_API_KEY$7}`;
const CLASS_NAME_CAROUSEL = 'carousel';
const CLASS_NAME_ACTIVE$2 = 'active';
const CLASS_NAME_SLIDE = 'slide';
@@ -1053,7 +1052,7 @@ const KEY_TO_DIRECTION = {
[ARROW_LEFT_KEY$2]: DIRECTION_RIGHT,
[ARROW_RIGHT_KEY$2]: DIRECTION_LEFT
};
const Default$f = {
const Default$e = {
interval: 5000,
keyboard: true,
pause: 'hover',
@@ -1061,7 +1060,7 @@ const Default$f = {
touch: true,
wrap: true
};
const DefaultType$f = {
const DefaultType$e = {
interval: '(number|boolean)',
// TODO:v6 remove boolean support
keyboard: 'boolean',
@@ -1092,13 +1091,13 @@ class Carousel extends BaseComponent {
// Getters
static get Default() {
return Default$f;
return Default$e;
}
static get DefaultType() {
return DefaultType$f;
return DefaultType$e;
}
static get NAME() {
return NAME$g;
return NAME$f;
}
// Public
@@ -1325,7 +1324,7 @@ class Carousel extends BaseComponent {
* Data API implementation
*/
EventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_SLIDE, function (event) {
EventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_SLIDE, function (event) {
const target = SelectorEngine.getElementFromSelector(this);
if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {
return;
@@ -1365,15 +1364,15 @@ EventHandler.on(window, EVENT_LOAD_DATA_API$3, () => {
* Constants
*/
const NAME$f = 'collapse';
const DATA_KEY$b = 'bs.collapse';
const EVENT_KEY$b = `.${DATA_KEY$b}`;
const DATA_API_KEY$7 = '.data-api';
const EVENT_SHOW$7 = `show${EVENT_KEY$b}`;
const EVENT_SHOWN$7 = `shown${EVENT_KEY$b}`;
const EVENT_HIDE$7 = `hide${EVENT_KEY$b}`;
const EVENT_HIDDEN$7 = `hidden${EVENT_KEY$b}`;
const EVENT_CLICK_DATA_API$5 = `click${EVENT_KEY$b}${DATA_API_KEY$7}`;
const NAME$e = 'collapse';
const DATA_KEY$a = 'bs.collapse';
const EVENT_KEY$a = `.${DATA_KEY$a}`;
const DATA_API_KEY$6 = '.data-api';
const EVENT_SHOW$6 = `show${EVENT_KEY$a}`;
const EVENT_SHOWN$6 = `shown${EVENT_KEY$a}`;
const EVENT_HIDE$6 = `hide${EVENT_KEY$a}`;
const EVENT_HIDDEN$6 = `hidden${EVENT_KEY$a}`;
const EVENT_CLICK_DATA_API$4 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`;
const CLASS_NAME_SHOW$6 = 'show';
const CLASS_NAME_COLLAPSE = 'collapse';
const CLASS_NAME_COLLAPSING = 'collapsing';
@@ -1383,12 +1382,12 @@ const CLASS_NAME_HORIZONTAL = 'collapse-horizontal';
const WIDTH = 'width';
const HEIGHT = 'height';
const SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing';
const SELECTOR_DATA_TOGGLE$8 = '[data-bs-toggle="collapse"]';
const Default$e = {
const SELECTOR_DATA_TOGGLE$7 = '[data-bs-toggle="collapse"]';
const Default$d = {
parent: null,
toggle: true
};
const DefaultType$e = {
const DefaultType$d = {
parent: '(null|element)',
toggle: 'boolean'
};
@@ -1402,7 +1401,7 @@ class Collapse extends BaseComponent {
super(element, config);
this._isTransitioning = false;
this._triggerArray = [];
const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$8);
const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$7);
for (const elem of toggleList) {
const selector = SelectorEngine.getSelectorFromElement(elem);
const filterElement = SelectorEngine.find(selector).filter(foundElement => foundElement === this._element);
@@ -1421,13 +1420,13 @@ class Collapse extends BaseComponent {
// Getters
static get Default() {
return Default$e;
return Default$d;
}
static get DefaultType() {
return DefaultType$e;
return DefaultType$d;
}
static get NAME() {
return NAME$f;
return NAME$e;
}
// Public
@@ -1453,7 +1452,7 @@ class Collapse extends BaseComponent {
if (activeChildren.length && activeChildren[0]._isTransitioning) {
return;
}
const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$7);
const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$6);
if (startEvent.defaultPrevented) {
return;
}
@@ -1471,7 +1470,7 @@ class Collapse extends BaseComponent {
this._element.classList.remove(CLASS_NAME_COLLAPSING);
this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$6);
this._element.style[dimension] = '';
EventHandler.trigger(this._element, EVENT_SHOWN$7);
EventHandler.trigger(this._element, EVENT_SHOWN$6);
};
const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1);
const scrollSize = `scroll${capitalizedDimension}`;
@@ -1482,7 +1481,7 @@ class Collapse extends BaseComponent {
if (this._isTransitioning || !this._isShown()) {
return;
}
const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$7);
const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$6);
if (startEvent.defaultPrevented) {
return;
}
@@ -1502,7 +1501,7 @@ class Collapse extends BaseComponent {
this._isTransitioning = false;
this._element.classList.remove(CLASS_NAME_COLLAPSING);
this._element.classList.add(CLASS_NAME_COLLAPSE);
EventHandler.trigger(this._element, EVENT_HIDDEN$7);
EventHandler.trigger(this._element, EVENT_HIDDEN$6);
};
this._element.style[dimension] = '';
this._queueCallback(complete, this._element, true);
@@ -1524,7 +1523,7 @@ class Collapse extends BaseComponent {
if (!this._config.parent) {
return;
}
const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE$8);
const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE$7);
for (const element of children) {
const selected = SelectorEngine.getElementFromSelector(element);
if (selected) {
@@ -1552,7 +1551,7 @@ class Collapse extends BaseComponent {
* Data API implementation
*/
EventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_TOGGLE$8, function (event) {
EventHandler.on(document, EVENT_CLICK_DATA_API$4, SELECTOR_DATA_TOGGLE$7, function (event) {
// preventDefault only for <a> elements (which change the URL) not inside the collapsible element
if (event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') {
event.preventDefault();
@@ -1564,434 +1563,6 @@ EventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_TOGGLE$8, functi
}
});
/**
* --------------------------------------------------------------------------
* Bootstrap datepicker.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
const NAME$e = 'datepicker';
const DATA_KEY$a = 'bs.datepicker';
const EVENT_KEY$a = `.${DATA_KEY$a}`;
const DATA_API_KEY$6 = '.data-api';
const EVENT_CHANGE = `change${EVENT_KEY$a}`;
const EVENT_SHOW$6 = `show${EVENT_KEY$a}`;
const EVENT_SHOWN$6 = `shown${EVENT_KEY$a}`;
const EVENT_HIDE$6 = `hide${EVENT_KEY$a}`;
const EVENT_HIDDEN$6 = `hidden${EVENT_KEY$a}`;
const EVENT_CLICK_DATA_API$4 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`;
const EVENT_FOCUSIN_DATA_API = `focusin${EVENT_KEY$a}${DATA_API_KEY$6}`;
const SELECTOR_DATA_TOGGLE$7 = '[data-bs-toggle="datepicker"]';
const HIDE_DELAY = 100; // ms delay before hiding after selection
const Default$d = {
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$d = {
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$d;
}
static get DefaultType() {
return DefaultType$d;
}
static get NAME() {
return NAME$e;
}
// 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$6);
if (showEvent.defaultPrevented) {
return;
}
this._calendar.show();
this._isShown = true;
EventHandler.trigger(this._element, EVENT_SHOWN$6);
}
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$6);
if (hideEvent.defaultPrevented) {
return;
}
this._calendar.hide();
this._isShown = false;
EventHandler.trigger(this._element, EVENT_HIDDEN$6);
}
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$4, SELECTOR_DATA_TOGGLE$7, 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$7, 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$a}${DATA_API_KEY$6}`, () => {
for (const element of document.querySelectorAll(`${SELECTOR_DATA_TOGGLE$7}[data-bs-inline="true"]`)) {
Datepicker.getOrCreateInstance(element);
}
});
/**
* --------------------------------------------------------------------------
* Bootstrap dialog.js
@@ -5853,5 +5424,5 @@ class Toggler extends BaseComponent {
eventActionOnPlugin(Toggler, EVENT_CLICK, SELECTOR_DATA_TOGGLE, 'toggle');
export { Alert, Button, Carousel, Collapse, Datepicker, Dialog, Dropdown, Offcanvas, OtpInput, Popover, ScrollSpy, Strength, Tab, Toast, Toggler, Tooltip };
export { Alert, Button, Carousel, Collapse, Dialog, Dropdown, Offcanvas, OtpInput, Popover, ScrollSpy, Strength, Tab, Toast, Toggler, Tooltip };
//# sourceMappingURL=bootstrap.esm.js.map
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+73 -502
View File
@@ -1,13 +1,13 @@
/*!
* Bootstrap v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('vanilla-calendar-pro'), require('@floating-ui/dom')) :
typeof define === 'function' && define.amd ? define(['vanilla-calendar-pro', '@floating-ui/dom'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.bootstrap = factory(global.vanillaCalendarPro, global.FloatingUIDOM));
})(this, (function (vanillaCalendarPro, dom) { 'use strict';
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@floating-ui/dom')) :
typeof define === 'function' && define.amd ? define(['@floating-ui/dom'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.bootstrap = factory(global.FloatingUIDOM));
})(this, (function (dom) { 'use strict';
/**
* --------------------------------------------------------------------------
@@ -795,11 +795,11 @@
* Constants
*/
const NAME$j = 'alert';
const DATA_KEY$e = 'bs.alert';
const EVENT_KEY$f = `.${DATA_KEY$e}`;
const EVENT_CLOSE = `close${EVENT_KEY$f}`;
const EVENT_CLOSED = `closed${EVENT_KEY$f}`;
const NAME$i = 'alert';
const DATA_KEY$d = 'bs.alert';
const EVENT_KEY$e = `.${DATA_KEY$d}`;
const EVENT_CLOSE = `close${EVENT_KEY$e}`;
const EVENT_CLOSED = `closed${EVENT_KEY$e}`;
const CLASS_NAME_FADE$4 = 'fade';
const CLASS_NAME_SHOW$7 = 'show';
@@ -810,7 +810,7 @@
class Alert extends BaseComponent {
// Getters
static get NAME() {
return NAME$j;
return NAME$i;
}
// Public
@@ -850,13 +850,13 @@
* Constants
*/
const NAME$i = 'button';
const DATA_KEY$d = 'bs.button';
const EVENT_KEY$e = `.${DATA_KEY$d}`;
const DATA_API_KEY$9 = '.data-api';
const NAME$h = 'button';
const DATA_KEY$c = 'bs.button';
const EVENT_KEY$d = `.${DATA_KEY$c}`;
const DATA_API_KEY$8 = '.data-api';
const CLASS_NAME_ACTIVE$3 = 'active';
const SELECTOR_DATA_TOGGLE$9 = '[data-bs-toggle="button"]';
const EVENT_CLICK_DATA_API$7 = `click${EVENT_KEY$e}${DATA_API_KEY$9}`;
const SELECTOR_DATA_TOGGLE$8 = '[data-bs-toggle="button"]';
const EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$d}${DATA_API_KEY$8}`;
/**
* Class definition
@@ -865,7 +865,7 @@
class Button extends BaseComponent {
// Getters
static get NAME() {
return NAME$i;
return NAME$h;
}
// Public
@@ -879,9 +879,9 @@
* Data API implementation
*/
EventHandler.on(document, EVENT_CLICK_DATA_API$7, SELECTOR_DATA_TOGGLE$9, event => {
EventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_TOGGLE$8, event => {
event.preventDefault();
const button = event.target.closest(SELECTOR_DATA_TOGGLE$9);
const button = event.target.closest(SELECTOR_DATA_TOGGLE$8);
const data = Button.getOrCreateInstance(button);
data.toggle();
});
@@ -898,23 +898,23 @@
* Constants
*/
const NAME$h = 'swipe';
const EVENT_KEY$d = '.bs.swipe';
const EVENT_TOUCHSTART = `touchstart${EVENT_KEY$d}`;
const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY$d}`;
const EVENT_TOUCHEND = `touchend${EVENT_KEY$d}`;
const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY$d}`;
const EVENT_POINTERUP = `pointerup${EVENT_KEY$d}`;
const NAME$g = 'swipe';
const EVENT_KEY$c = '.bs.swipe';
const EVENT_TOUCHSTART = `touchstart${EVENT_KEY$c}`;
const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY$c}`;
const EVENT_TOUCHEND = `touchend${EVENT_KEY$c}`;
const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY$c}`;
const EVENT_POINTERUP = `pointerup${EVENT_KEY$c}`;
const POINTER_TYPE_TOUCH = 'touch';
const POINTER_TYPE_PEN = 'pen';
const CLASS_NAME_POINTER_EVENT = 'pointer-event';
const SWIPE_THRESHOLD = 40;
const Default$g = {
const Default$f = {
endCallback: null,
leftCallback: null,
rightCallback: null
};
const DefaultType$g = {
const DefaultType$f = {
endCallback: '(function|null)',
leftCallback: '(function|null)',
rightCallback: '(function|null)'
@@ -939,18 +939,18 @@
// Getters
static get Default() {
return Default$g;
return Default$f;
}
static get DefaultType() {
return DefaultType$g;
return DefaultType$f;
}
static get NAME() {
return NAME$h;
return NAME$g;
}
// Public
dispose() {
EventHandler.off(this._element, EVENT_KEY$d);
EventHandler.off(this._element, EVENT_KEY$c);
}
// Private
@@ -1018,10 +1018,10 @@
* Constants
*/
const NAME$g = 'carousel';
const DATA_KEY$c = 'bs.carousel';
const EVENT_KEY$c = `.${DATA_KEY$c}`;
const DATA_API_KEY$8 = '.data-api';
const NAME$f = 'carousel';
const DATA_KEY$b = 'bs.carousel';
const EVENT_KEY$b = `.${DATA_KEY$b}`;
const DATA_API_KEY$7 = '.data-api';
const ARROW_LEFT_KEY$2 = 'ArrowLeft';
const ARROW_RIGHT_KEY$2 = 'ArrowRight';
const TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch
@@ -1030,14 +1030,14 @@
const ORDER_PREV = 'prev';
const DIRECTION_LEFT = 'left';
const DIRECTION_RIGHT = 'right';
const EVENT_SLIDE = `slide${EVENT_KEY$c}`;
const EVENT_SLID = `slid${EVENT_KEY$c}`;
const EVENT_KEYDOWN$1 = `keydown${EVENT_KEY$c}`;
const EVENT_MOUSEENTER$2 = `mouseenter${EVENT_KEY$c}`;
const EVENT_MOUSELEAVE$1 = `mouseleave${EVENT_KEY$c}`;
const EVENT_DRAG_START = `dragstart${EVENT_KEY$c}`;
const EVENT_LOAD_DATA_API$3 = `load${EVENT_KEY$c}${DATA_API_KEY$8}`;
const EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$c}${DATA_API_KEY$8}`;
const EVENT_SLIDE = `slide${EVENT_KEY$b}`;
const EVENT_SLID = `slid${EVENT_KEY$b}`;
const EVENT_KEYDOWN$1 = `keydown${EVENT_KEY$b}`;
const EVENT_MOUSEENTER$2 = `mouseenter${EVENT_KEY$b}`;
const EVENT_MOUSELEAVE$1 = `mouseleave${EVENT_KEY$b}`;
const EVENT_DRAG_START = `dragstart${EVENT_KEY$b}`;
const EVENT_LOAD_DATA_API$3 = `load${EVENT_KEY$b}${DATA_API_KEY$7}`;
const EVENT_CLICK_DATA_API$5 = `click${EVENT_KEY$b}${DATA_API_KEY$7}`;
const CLASS_NAME_CAROUSEL = 'carousel';
const CLASS_NAME_ACTIVE$2 = 'active';
const CLASS_NAME_SLIDE = 'slide';
@@ -1056,7 +1056,7 @@
[ARROW_LEFT_KEY$2]: DIRECTION_RIGHT,
[ARROW_RIGHT_KEY$2]: DIRECTION_LEFT
};
const Default$f = {
const Default$e = {
interval: 5000,
keyboard: true,
pause: 'hover',
@@ -1064,7 +1064,7 @@
touch: true,
wrap: true
};
const DefaultType$f = {
const DefaultType$e = {
interval: '(number|boolean)',
// TODO:v6 remove boolean support
keyboard: 'boolean',
@@ -1095,13 +1095,13 @@
// Getters
static get Default() {
return Default$f;
return Default$e;
}
static get DefaultType() {
return DefaultType$f;
return DefaultType$e;
}
static get NAME() {
return NAME$g;
return NAME$f;
}
// Public
@@ -1328,7 +1328,7 @@
* Data API implementation
*/
EventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_SLIDE, function (event) {
EventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_SLIDE, function (event) {
const target = SelectorEngine.getElementFromSelector(this);
if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {
return;
@@ -1368,15 +1368,15 @@
* Constants
*/
const NAME$f = 'collapse';
const DATA_KEY$b = 'bs.collapse';
const EVENT_KEY$b = `.${DATA_KEY$b}`;
const DATA_API_KEY$7 = '.data-api';
const EVENT_SHOW$7 = `show${EVENT_KEY$b}`;
const EVENT_SHOWN$7 = `shown${EVENT_KEY$b}`;
const EVENT_HIDE$7 = `hide${EVENT_KEY$b}`;
const EVENT_HIDDEN$7 = `hidden${EVENT_KEY$b}`;
const EVENT_CLICK_DATA_API$5 = `click${EVENT_KEY$b}${DATA_API_KEY$7}`;
const NAME$e = 'collapse';
const DATA_KEY$a = 'bs.collapse';
const EVENT_KEY$a = `.${DATA_KEY$a}`;
const DATA_API_KEY$6 = '.data-api';
const EVENT_SHOW$6 = `show${EVENT_KEY$a}`;
const EVENT_SHOWN$6 = `shown${EVENT_KEY$a}`;
const EVENT_HIDE$6 = `hide${EVENT_KEY$a}`;
const EVENT_HIDDEN$6 = `hidden${EVENT_KEY$a}`;
const EVENT_CLICK_DATA_API$4 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`;
const CLASS_NAME_SHOW$6 = 'show';
const CLASS_NAME_COLLAPSE = 'collapse';
const CLASS_NAME_COLLAPSING = 'collapsing';
@@ -1386,12 +1386,12 @@
const WIDTH = 'width';
const HEIGHT = 'height';
const SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing';
const SELECTOR_DATA_TOGGLE$8 = '[data-bs-toggle="collapse"]';
const Default$e = {
const SELECTOR_DATA_TOGGLE$7 = '[data-bs-toggle="collapse"]';
const Default$d = {
parent: null,
toggle: true
};
const DefaultType$e = {
const DefaultType$d = {
parent: '(null|element)',
toggle: 'boolean'
};
@@ -1405,7 +1405,7 @@
super(element, config);
this._isTransitioning = false;
this._triggerArray = [];
const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$8);
const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$7);
for (const elem of toggleList) {
const selector = SelectorEngine.getSelectorFromElement(elem);
const filterElement = SelectorEngine.find(selector).filter(foundElement => foundElement === this._element);
@@ -1424,13 +1424,13 @@
// Getters
static get Default() {
return Default$e;
return Default$d;
}
static get DefaultType() {
return DefaultType$e;
return DefaultType$d;
}
static get NAME() {
return NAME$f;
return NAME$e;
}
// Public
@@ -1456,7 +1456,7 @@
if (activeChildren.length && activeChildren[0]._isTransitioning) {
return;
}
const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$7);
const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$6);
if (startEvent.defaultPrevented) {
return;
}
@@ -1474,7 +1474,7 @@
this._element.classList.remove(CLASS_NAME_COLLAPSING);
this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$6);
this._element.style[dimension] = '';
EventHandler.trigger(this._element, EVENT_SHOWN$7);
EventHandler.trigger(this._element, EVENT_SHOWN$6);
};
const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1);
const scrollSize = `scroll${capitalizedDimension}`;
@@ -1485,7 +1485,7 @@
if (this._isTransitioning || !this._isShown()) {
return;
}
const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$7);
const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$6);
if (startEvent.defaultPrevented) {
return;
}
@@ -1505,7 +1505,7 @@
this._isTransitioning = false;
this._element.classList.remove(CLASS_NAME_COLLAPSING);
this._element.classList.add(CLASS_NAME_COLLAPSE);
EventHandler.trigger(this._element, EVENT_HIDDEN$7);
EventHandler.trigger(this._element, EVENT_HIDDEN$6);
};
this._element.style[dimension] = '';
this._queueCallback(complete, this._element, true);
@@ -1527,7 +1527,7 @@
if (!this._config.parent) {
return;
}
const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE$8);
const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE$7);
for (const element of children) {
const selected = SelectorEngine.getElementFromSelector(element);
if (selected) {
@@ -1555,7 +1555,7 @@
* Data API implementation
*/
EventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_TOGGLE$8, function (event) {
EventHandler.on(document, EVENT_CLICK_DATA_API$4, SELECTOR_DATA_TOGGLE$7, function (event) {
// preventDefault only for <a> elements (which change the URL) not inside the collapsible element
if (event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') {
event.preventDefault();
@@ -1567,434 +1567,6 @@
}
});
/**
* --------------------------------------------------------------------------
* Bootstrap datepicker.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
const NAME$e = 'datepicker';
const DATA_KEY$a = 'bs.datepicker';
const EVENT_KEY$a = `.${DATA_KEY$a}`;
const DATA_API_KEY$6 = '.data-api';
const EVENT_CHANGE = `change${EVENT_KEY$a}`;
const EVENT_SHOW$6 = `show${EVENT_KEY$a}`;
const EVENT_SHOWN$6 = `shown${EVENT_KEY$a}`;
const EVENT_HIDE$6 = `hide${EVENT_KEY$a}`;
const EVENT_HIDDEN$6 = `hidden${EVENT_KEY$a}`;
const EVENT_CLICK_DATA_API$4 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`;
const EVENT_FOCUSIN_DATA_API = `focusin${EVENT_KEY$a}${DATA_API_KEY$6}`;
const SELECTOR_DATA_TOGGLE$7 = '[data-bs-toggle="datepicker"]';
const HIDE_DELAY = 100; // ms delay before hiding after selection
const Default$d = {
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$d = {
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$d;
}
static get DefaultType() {
return DefaultType$d;
}
static get NAME() {
return NAME$e;
}
// 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$6);
if (showEvent.defaultPrevented) {
return;
}
this._calendar.show();
this._isShown = true;
EventHandler.trigger(this._element, EVENT_SHOWN$6);
}
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$6);
if (hideEvent.defaultPrevented) {
return;
}
this._calendar.hide();
this._isShown = false;
EventHandler.trigger(this._element, EVENT_HIDDEN$6);
}
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$4, SELECTOR_DATA_TOGGLE$7, 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$7, 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$a}${DATA_API_KEY$6}`, () => {
for (const element of document.querySelectorAll(`${SELECTOR_DATA_TOGGLE$7}[data-bs-inline="true"]`)) {
Datepicker.getOrCreateInstance(element);
}
});
/**
* --------------------------------------------------------------------------
* Bootstrap dialog.js
@@ -5868,7 +5440,6 @@
Button,
Carousel,
Collapse,
Datepicker,
Dialog,
Dropdown,
Offcanvas,
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap alert.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap base-component.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap button.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap carousel.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap collapse.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
-443
View File
@@ -1,443 +0,0 @@
/*!
* Bootstrap datepicker.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('vanilla-calendar-pro'), require('./base-component.js'), require('./dom/event-handler.js'), require('./util/index.js')) :
typeof define === 'function' && define.amd ? define(['vanilla-calendar-pro', './base-component', './dom/event-handler', './util/index'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Datepicker = factory(global["vanilla-calendar-pro"], global.BaseComponent, global.EventHandler, global.Index));
})(this, (function (vanillaCalendarPro, BaseComponent, EventHandler, index_js) { 'use strict';
/**
* --------------------------------------------------------------------------
* Bootstrap datepicker.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
const NAME = 'datepicker';
const DATA_KEY = 'bs.datepicker';
const EVENT_KEY = `.${DATA_KEY}`;
const DATA_API_KEY = '.data-api';
const EVENT_CHANGE = `change${EVENT_KEY}`;
const EVENT_SHOW = `show${EVENT_KEY}`;
const EVENT_SHOWN = `shown${EVENT_KEY}`;
const EVENT_HIDE = `hide${EVENT_KEY}`;
const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
const EVENT_FOCUSIN_DATA_API = `focusin${EVENT_KEY}${DATA_API_KEY}`;
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="datepicker"]';
const HIDE_DELAY = 100; // ms delay before hiding after selection
const Default = {
datepickerTheme: null,
// 'light', 'dark', 'auto' - explicit theme for datepicker popover only
dateMin: null,
dateMax: null,
dateFormat: null,
// Intl.DateTimeFormat options, or function(date, locale) => string
displayElement: null,
// Element to show formatted date (defaults to element for buttons)
displayMonthsCount: 1,
// Number of months to display side-by-side
firstWeekday: 1,
// Monday
inline: false,
// Render calendar inline (no popup)
locale: 'default',
positionElement: null,
// Element to position calendar relative to (defaults to input)
selectedDates: [],
selectionMode: 'single',
// 'single', 'multiple', 'multiple-ranged'
placement: 'left',
// 'left', 'center', 'right', 'auto'
vcpOptions: {} // Pass-through for any VCP option
};
const DefaultType = {
datepickerTheme: '(null|string)',
dateMin: '(null|string|number|object)',
dateMax: '(null|string|number|object)',
dateFormat: '(null|object|function)',
displayElement: '(null|string|element|boolean)',
displayMonthsCount: 'number',
firstWeekday: 'number',
inline: 'boolean',
locale: 'string',
positionElement: '(null|string|element)',
selectedDates: 'array',
selectionMode: 'string',
placement: 'string',
vcpOptions: 'object'
};
/**
* Class definition
*/
class Datepicker extends BaseComponent {
constructor(element, config) {
super(element, config);
this._calendar = null;
this._isShown = false;
this._initCalendar();
}
// Getters
static get Default() {
return Default;
}
static get DefaultType() {
return DefaultType;
}
static get NAME() {
return NAME;
}
// Public
toggle() {
if (this._config.inline) {
return; // Inline calendars are always visible
}
return this._isShown ? this.hide() : this.show();
}
show() {
if (this._config.inline) {
return; // Inline calendars are always visible
}
if (!this._calendar || index_js.isDisabled(this._element) || this._isShown) {
return;
}
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW);
if (showEvent.defaultPrevented) {
return;
}
this._calendar.show();
this._isShown = true;
EventHandler.trigger(this._element, EVENT_SHOWN);
}
hide() {
if (this._config.inline) {
return; // Inline calendars are always visible
}
if (!this._calendar || !this._isShown) {
return;
}
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);
if (hideEvent.defaultPrevented) {
return;
}
this._calendar.hide();
this._isShown = false;
EventHandler.trigger(this._element, EVENT_HIDDEN);
}
dispose() {
if (this._themeObserver) {
this._themeObserver.disconnect();
this._themeObserver = null;
}
if (this._calendar) {
this._calendar.destroy();
}
this._calendar = null;
super.dispose();
}
getSelectedDates() {
const dates = this._calendar?.context?.selectedDates;
return dates ? [...dates] : [];
}
setSelectedDates(dates) {
if (this._calendar) {
this._calendar.set({
selectedDates: dates
});
}
}
// Private
_initCalendar() {
this._isInput = this._element.tagName === 'INPUT';
this._isInline = this._config.inline;
// For inline mode, look for a hidden input child to bind to
if (this._isInline && !this._isInput) {
this._boundInput = this._element.querySelector('input[type="hidden"], input[name]');
}
this._positionElement = this._resolvePositionElement();
this._displayElement = this._resolveDisplayElement();
const calendarOptions = this._buildCalendarOptions();
// Create calendar on the position element (for correct popup positioning)
// but value updates still go to this._element (the input)
this._calendar = new vanillaCalendarPro.Calendar(this._positionElement, calendarOptions);
this._calendar.init();
// Watch for theme changes on ancestor elements (for live theme switching)
this._setupThemeObserver();
// Set initial value if input has a value
if (this._isInput && this._element.value) {
this._parseInputValue();
}
// Populate input/display with preselected dates
this._updateDisplayWithSelectedDates();
}
_updateDisplayWithSelectedDates() {
const {
selectedDates
} = this._config;
if (!selectedDates || selectedDates.length === 0) {
return;
}
const formattedDate = this._formatDateForInput(selectedDates);
if (this._isInput) {
this._element.value = formattedDate;
}
if (this._boundInput) {
this._boundInput.value = selectedDates.join(',');
}
if (this._displayElement) {
this._displayElement.textContent = formattedDate;
}
}
_resolvePositionElement() {
let {
positionElement
} = this._config;
if (typeof positionElement === 'string') {
positionElement = document.querySelector(positionElement);
}
// Use input's parent if in form-adorn
if (!positionElement && this._isInput && !this._isInline) {
const parent = this._element.closest('.form-adorn');
if (parent) {
positionElement = parent;
}
}
return positionElement || this._element;
}
_resolveDisplayElement() {
const {
displayElement
} = this._config;
if (typeof displayElement === 'string') {
return document.querySelector(displayElement);
}
// For buttons/non-inputs (not inline), look for a [data-bs-datepicker-display] child
if (displayElement === true || displayElement === null && !this._isInput && !this._isInline) {
const displayChild = this._element.querySelector('[data-bs-datepicker-display]');
return displayChild || this._element;
}
return displayElement;
}
_getThemeAncestor() {
return this._element.closest('[data-bs-theme]');
}
_getEffectiveTheme() {
// Priority: explicit datepickerTheme config > inherited from ancestor > none
const {
datepickerTheme
} = this._config;
if (datepickerTheme) {
return datepickerTheme;
}
const ancestor = this._getThemeAncestor();
return ancestor?.getAttribute('data-bs-theme') || null;
}
_syncThemeAttribute(element) {
if (!element) {
return;
}
const theme = this._getEffectiveTheme();
if (theme) {
// Copy theme to popover (needed because VCP appends to body, breaking CSS inheritance)
element.setAttribute('data-bs-theme', theme);
} else {
// No theme - remove attribute to allow natural inheritance
element.removeAttribute('data-bs-theme');
}
}
_setupThemeObserver() {
// Watch for theme changes on ancestor elements
const ancestor = this._getThemeAncestor();
if (!ancestor || this._config.datepickerTheme) {
// No ancestor to watch, or explicit datepickerTheme overrides
return;
}
this._themeObserver = new MutationObserver(() => {
this._syncThemeAttribute(this._calendar?.context?.mainElement);
});
this._themeObserver.observe(ancestor, {
attributes: true,
attributeFilter: ['data-bs-theme']
});
}
_buildCalendarOptions() {
// Get theme for VCP - use 'system' for auto-detection if no explicit theme
const theme = this._getEffectiveTheme();
// VCP uses 'system' for auto, Bootstrap uses 'auto'
const vcpTheme = !theme || theme === 'auto' ? 'system' : theme;
const calendarOptions = {
...this._config.vcpOptions,
inputMode: !this._isInline,
positionToInput: this._config.placement,
firstWeekday: this._config.firstWeekday,
locale: this._config.locale,
selectionDatesMode: this._config.selectionMode,
selectedDates: this._config.selectedDates,
displayMonthsCount: this._config.displayMonthsCount,
type: this._config.displayMonthsCount > 1 ? 'multiple' : 'default',
selectedTheme: vcpTheme,
themeAttrDetect: '[data-bs-theme]',
onClickDate: (self, event) => this._handleDateClick(self, event),
onInit: self => {
this._syncThemeAttribute(self.context.mainElement);
},
onShow: () => {
this._isShown = true;
this._syncThemeAttribute(this._calendar.context.mainElement);
},
onHide: () => {
this._isShown = false;
}
};
// Navigate to the month of the first selected date
if (this._config.selectedDates.length > 0) {
const firstDate = this._parseDate(this._config.selectedDates[0]);
calendarOptions.selectedMonth = firstDate.getMonth();
calendarOptions.selectedYear = firstDate.getFullYear();
}
if (this._config.dateMin) {
calendarOptions.dateMin = this._config.dateMin;
}
if (this._config.dateMax) {
calendarOptions.dateMax = this._config.dateMax;
}
return calendarOptions;
}
_handleDateClick(self, event) {
const selectedDates = [...self.context.selectedDates];
if (selectedDates.length > 0) {
const formattedDate = this._formatDateForInput(selectedDates);
if (this._isInput) {
this._element.value = formattedDate;
}
if (this._boundInput) {
this._boundInput.value = selectedDates.join(',');
}
if (this._displayElement) {
this._displayElement.textContent = formattedDate;
}
}
EventHandler.trigger(this._element, EVENT_CHANGE, {
dates: selectedDates,
event
});
this._maybeHideAfterSelection(selectedDates);
}
_maybeHideAfterSelection(selectedDates) {
if (this._isInline) {
return;
}
const shouldHide = this._config.selectionMode === 'single' && selectedDates.length > 0 || this._config.selectionMode === 'multiple-ranged' && selectedDates.length >= 2;
if (shouldHide) {
setTimeout(() => this.hide(), HIDE_DELAY);
}
}
_parseDate(dateStr) {
const [year, month, day] = dateStr.split('-');
return new Date(year, month - 1, day);
}
_formatDate(dateStr) {
const date = this._parseDate(dateStr);
const locale = this._config.locale === 'default' ? undefined : this._config.locale;
const {
dateFormat
} = this._config;
// Custom function formatter
if (typeof dateFormat === 'function') {
return dateFormat(date, locale);
}
// Intl.DateTimeFormat options object
if (dateFormat && typeof dateFormat === 'object') {
return new Intl.DateTimeFormat(locale, dateFormat).format(date);
}
// Default: locale-aware formatting
return date.toLocaleDateString(locale);
}
_formatDateForInput(dates) {
if (dates.length === 0) {
return '';
}
if (dates.length === 1) {
return this._formatDate(dates[0]);
}
// For date ranges, use en-dash; for multiple dates, use comma
const separator = this._config.selectionMode === 'multiple-ranged' ? ' ' : ', ';
return dates.map(d => this._formatDate(d)).join(separator);
}
_parseInputValue() {
// Try to parse the input value as a date
const value = this._element.value.trim();
if (!value) {
return;
}
const date = new Date(value);
if (!Number.isNaN(date.getTime())) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const formatted = `${year}-${month}-${day}`;
this._calendar.set({
selectedDates: [formatted]
});
}
}
}
/**
* Data API implementation
*/
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
// Only handle if not an input (inputs use focus)
// Skip inline datepickers (they're always visible)
if (this.tagName === 'INPUT' || this.dataset.bsInline === 'true') {
return;
}
event.preventDefault();
Datepicker.getOrCreateInstance(this).toggle();
});
EventHandler.on(document, EVENT_FOCUSIN_DATA_API, SELECTOR_DATA_TOGGLE, function () {
// Handle focus for input elements
if (this.tagName !== 'INPUT') {
return;
}
Datepicker.getOrCreateInstance(this).show();
});
// Auto-initialize inline datepickers on DOMContentLoaded
EventHandler.on(document, `DOMContentLoaded${EVENT_KEY}${DATA_API_KEY}`, () => {
for (const element of document.querySelectorAll(`${SELECTOR_DATA_TOGGLE}[data-bs-inline="true"]`)) {
Datepicker.getOrCreateInstance(element);
}
});
return Datepicker;
}));
//# sourceMappingURL=datepicker.js.map
-1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap dialog.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap data.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap event-handler.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap manipulator.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap selector-engine.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap dropdown.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap offcanvas.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap otp-input.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap popover.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap scrollspy.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap strength.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap tab.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap toast.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap toggler.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap tooltip.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap backdrop.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap component-functions.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap config.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap floating-ui.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap focustrap.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap index.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap sanitizer.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap scrollbar.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap swipe.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
+1 -1
View File
@@ -1,6 +1,6 @@
/*!
* Bootstrap template-factory.js v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
-1
View File
@@ -9,7 +9,6 @@ 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'
-2
View File
@@ -9,7 +9,6 @@ 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'
@@ -27,7 +26,6 @@ export default {
Button,
Carousel,
Collapse,
Datepicker,
Dialog,
Dropdown,
Offcanvas,
-482
View File
@@ -1,482 +0,0 @@
/**
* --------------------------------------------------------------------------
* 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
-114
View File
@@ -243,40 +243,6 @@ 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', () => {
@@ -335,86 +301,6 @@ 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', () => {
-34
View File
@@ -106,40 +106,6 @@ 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', () => {
-170
View File
@@ -1,170 +0,0 @@
<!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>
+128 -311
View File
@@ -19,8 +19,7 @@
],
"license": "MIT",
"dependencies": {
"postcss-prefix-custom-properties": "^0.1.0",
"vanilla-calendar-pro": "^3.0.5"
"postcss-prefix-custom-properties": "^0.1.0"
},
"devDependencies": {
"@astrojs/check": "^0.9.5",
@@ -47,6 +46,7 @@
"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,7 +74,6 @@
"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",
@@ -94,7 +93,6 @@
"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"
@@ -6488,6 +6486,122 @@
"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",
@@ -7186,6 +7300,7 @@
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"engines": {
"node": ">=8"
}
@@ -11747,267 +11862,6 @@
"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",
@@ -15945,9 +15799,9 @@
}
},
"node_modules/postcss-sorting": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-9.1.0.tgz",
"integrity": "sha512-Mn8KJ45HNNG6JBpBizXcyf6LqY/qyqetGcou/nprDnFwBFBLGj0j/sNKV2lj2KMOVOwdXu14aEzqJv8CIV6e8g==",
"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": {
@@ -18523,30 +18377,6 @@
"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",
@@ -18675,20 +18505,17 @@
}
},
"node_modules/stylelint-order": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-7.0.1.tgz",
"integrity": "sha512-GWPei1zBVDDjxM+/BmcSCiOcHNd8rSqW6FUZtqQGlTRpD0Z5nSzspzWD8rtKif5KPdzUG68DApKEV/y/I9VbTw==",
"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.5.6",
"postcss-sorting": "^9.1.0"
},
"engines": {
"node": ">=20.19.0"
"postcss": "^8.4.32",
"postcss-sorting": "^8.0.2"
},
"peerDependencies": {
"stylelint": "^16.18.0 || ^17.0.0"
"stylelint": "^14.0.0 || ^15.0.0 || ^16.0.1"
}
},
"node_modules/stylelint-scss": {
@@ -19910,16 +19737,6 @@
"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",
+3 -5
View File
@@ -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": "node build/css-minify.mjs",
"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-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",
"lightningcss": "^1.30.2",
"clean-css-cli": "^5.6.3",
"clipboard": "^2.0.11",
"cross-env": "^10.1.0",
"eslint": "8.57.1",
@@ -177,7 +177,6 @@
"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"
@@ -211,7 +210,6 @@
"volar-service-emmet": "0.0.63"
},
"dependencies": {
"postcss-prefix-custom-properties": "^0.1.0",
"vanilla-calendar-pro": "^3.0.5"
"postcss-prefix-custom-properties": "^0.1.0"
}
}
-426
View File
@@ -1,426 +0,0 @@
// 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
View File
@@ -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,
+17 -15
View File
@@ -11,10 +11,9 @@
@layer colors, theme, config, root, reboot, layout, content, forms, components, custom, helpers, utilities;
:root {
// scss-docs-start root-theme-variables
--black: #{$black};
--white: #{$white};
color-scheme: light dark;
// scss-docs-start root-theme-variables
// Generate semantic theme colors
@each $color-name, $color-map in $new-theme-colors {
@each $key, $value in $color-map {
@@ -33,13 +32,15 @@
@each $color, $value in $theme-borders {
--border-#{$color}: #{$value};
}
// scss-docs-end root-theme-variables
color-scheme: light dark;
--black: #{$black};
--white: #{$white};
// scss-docs-end root-theme-variables
}
:root,
[data-bs-theme="light"] {
color-scheme: light;
// Note: Custom variable values only support SassScript inside `#{}`.
@@ -53,6 +54,12 @@
--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
@@ -84,9 +91,13 @@
--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};
@@ -134,20 +145,12 @@
--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};
@@ -161,7 +164,6 @@
--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
}
}
-1
View File
@@ -17,7 +17,6 @@
@forward "breadcrumb";
@forward "card";
@forward "carousel";
@forward "datepicker";
@forward "dialog";
@forward "dropdown";
@forward "list-group";
-111
View File
@@ -1,111 +0,0 @@
@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};
}
}
+1 -28
View File
@@ -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,31 +243,4 @@
&.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;
}
}
}
+5 -5
View File
@@ -112,8 +112,6 @@ $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),
@@ -132,6 +130,11 @@ $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);
@@ -141,8 +144,5 @@ $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)";
}
}
}
+2 -2
View File
@@ -52,8 +52,8 @@ $otp-input-gap: .5rem !default;
border-color: var(--form-valid-border-color);
&:focus {
--focus-ring-color: rgba(var(--success-rgb), .25);
border-color: var(--form-valid-border-color);
--focus-ring-color: rgba(var(--success-rgb), .25);
}
}
@@ -62,8 +62,8 @@ $otp-input-gap: .5rem !default;
border-color: var(--form-invalid-border-color);
&:focus {
--focus-ring-color: rgba(var(--danger-rgb), .25);
border-color: var(--form-invalid-border-color);
--focus-ring-color: rgba(var(--danger-rgb), .25);
}
}
}
-1
View File
@@ -9,5 +9,4 @@
@forward "input-group";
@forward "strength";
@forward "otp-input";
@forward "form-adorn";
@forward "validation";
+3 -3
View File
@@ -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)} {
-2
View File
@@ -75,7 +75,6 @@
- title: Floating labels
- title: OTP input
- title: Password strength
- title: Form adorn
- title: Layout
- title: Validation
@@ -93,7 +92,6 @@
- title: Carousel
- title: Close button
- title: Collapse
- title: Datepicker
- title: Dialog
- title: Dropdown
- title: List group
-10
View File
@@ -35,10 +35,6 @@
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"
@@ -66,9 +62,6 @@
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"
@@ -135,9 +128,6 @@
<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"
@@ -1,283 +0,0 @@
---
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)
})
```
+5 -3
View File
@@ -18,11 +18,13 @@ 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.
## Root CSS variables
## CSS variables
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:
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 dont use CSS variables, you still have all the power of Sass. **This is still in-progress and will take time to fully implement.**
<ScssDocs name="root-border-var" file="scss/_root.scss" />
For example, consider these `:root` CSS variables for common `<body>` styles:
<ScssDocs name="root-body-variables" file="scss/_root.scss" />
In practice, those variables are then applied in Reboot like so:
-108
View File
@@ -1,108 +0,0 @@
---
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" />
+1 -1
View File
@@ -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
}