Fix Datepicker throwing on Vanilla Calendar Pro 3.4 by registering its extensions (#3231)

This commit is contained in:
Paweł Kuna authored and GitHub committed 2026-09-28 22:14:08 +02:00
1 parent dee65f2f5c
commit 4f471ddccf
6 files changed
+66 -8

No files matched your search

@@ -0,0 +1,6 @@
---
"@tabler/core": patch
"@tabler/docs": patch
---
Fixed `Datepicker` failing with `data-bs-display-months-count` on Vanilla Calendar Pro 3.4 by registering its extensions.
+30 -2
View File
@@ -5,7 +5,7 @@
* --------------------------------------------------------------------------
*/
import type { Calendar, Options, Range } from 'vanilla-calendar-pro'
import type { Calendar, CalendarExtension, Options, Range } from 'vanilla-calendar-pro'
import BaseComponent from './bootstrap/base-component'
import EventHandler from './bootstrap/dom/event-handler'
import SelectorEngine from './bootstrap/dom/selector-engine'
@@ -40,6 +40,13 @@ const HIDE_DELAY = 100 // ms delay before hiding after selection
const DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/
// Vanilla Calendar Pro 3.4 moved several months, the time picker, week
// numbers, popups and animation into extensions that must be registered when
// the calendar is created, or its constructor throws. The library is loaded
// as a whole on `window`, so every extension it exports is registered;
// 3.3 exports none and gets no `extensions` option.
const EXTENSION_NAMES = ['annotations', 'months', 'motion', 'time', 'weeks'] as const
// The public API is typed with local copies of the Vanilla Calendar Pro types.
// `vanilla-calendar-pro` is an optional peer dependency, so the published
// `dist/types` must not import it: a project that never loads the datepicker
@@ -555,14 +562,35 @@ class Datepicker extends BaseComponent {
EventHandler.trigger(this._element, EVENT_HIDDEN)
}
// The extensions the loaded library exports, after the ones the caller
// registered in `vcpOptions` themselves.
/** @internal */
_calendarExtensions(own: readonly CalendarExtension[] = []): CalendarExtension[] {
const library = window.VanillaCalendarPro as Record<string, unknown> | undefined
const extensions = [...own]
for (const name of EXTENSION_NAMES) {
const extension = library?.[name] as CalendarExtension | undefined
if (extension && typeof extension === 'object' && !extensions.includes(extension)) {
extensions.push(extension)
}
}
return extensions
}
/** @internal */
_buildCalendarOptions(): Options {
// The plugin uses 'system' for auto-detection, Bootstrap and Tabler use 'auto'
const theme = this._getEffectiveTheme()
const vcpTheme = !theme || theme === 'auto' ? 'system' : theme
const vcpOptions = this._config.vcpOptions as Options
const extensions = this._calendarExtensions(vcpOptions.extensions)
const calendarOptions: Options = {
...this._config.vcpOptions,
...vcpOptions,
...(extensions.length > 0 ? { extensions } : {}),
inputMode: !this._isInline,
positionToInput: this._config.placement,
firstWeekday: this._config.firstWeekday,
+22
View File
@@ -58,6 +58,28 @@ describe('Datepicker', () => {
expect(instance._isInput).toBe(true)
})
it('should register the extensions the library exports', () => {
fixtureEl.innerHTML = '<input type="text" data-bs-toggle="datepicker" data-bs-display-months-count="2">'
const instance = new Datepicker(input())
const calendar = instance.calendar as VanillaCalendarPro.Calendar
expect(calendar.extensions).toContain(VanillaCalendarPro.months)
expect(calendar.extensions).toContain(VanillaCalendarPro.time)
expect(calendar.type).toBe('multiple')
expect(calendar.displayMonthsCount).toBe(2)
})
it('should keep the extensions passed in vcpOptions once', () => {
fixtureEl.innerHTML = '<input type="text" data-bs-toggle="datepicker">'
const instance = new Datepicker(input(), { vcpOptions: { extensions: [VanillaCalendarPro.months] } })
const extensions = (instance.calendar as VanillaCalendarPro.Calendar).extensions
expect(extensions.filter((extension) => extension === VanillaCalendarPro.months)).toHaveLength(1)
expect(extensions).toContain(VanillaCalendarPro.motion)
})
it('should stay inert without the plugin', () => {
delete window.VanillaCalendarPro
fixtureEl.innerHTML = '<input type="text" data-bs-toggle="datepicker">'
+1 -1
View File
@@ -287,7 +287,7 @@
"tom-select": "^2.6.2",
"typed.js": "^3.0.0",
"typescript": "^7.0.2",
"vanilla-calendar-pro": "^3.3.2",
"vanilla-calendar-pro": "^3.4.0",
"vitest": "4.1.11"
}
}
+2
View File
@@ -52,6 +52,8 @@ Or include it from a CDN, before `tabler.js`:
You do not need the library's own stylesheet. Tabler styles the calendar in `tabler.css`.
Vanilla Calendar Pro 3.4 splits several months, the time picker, week numbers, popups and animation into extensions. Tabler registers every extension the loaded library exports, so options such as `data-bs-display-months-count="2"` work without importing them yourself.
## Usage
### Basic input
+5 -5
View File
@@ -214,8 +214,8 @@ importers:
specifier: ^7.0.2
version: 7.0.2
vanilla-calendar-pro:
specifier: ^3.3.2
version: 3.3.2
specifier: ^3.4.0
version: 3.4.0
vitest:
specifier: 4.1.11
version: 4.1.11(@types/node@26.4.0)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-istanbul@4.1.11)(esbuild@0.28.1)(sass@1.103.1)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)
@@ -4601,8 +4601,8 @@ packages:
validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
vanilla-calendar-pro@3.3.2:
resolution: {integrity: sha512-RaLpDtvQFeKi0ajOpGBedLNrr0YoiYAF+0sppk4XZV/U57AuihJIhw8h2whUvIG1ZSR3LrdSXk3lHLkCPGlHvg==}
vanilla-calendar-pro@3.4.0:
resolution: {integrity: sha512-HLWjzHOWvMIrjI77tXK5bYzp0s5TeBSrZC247STjcTAdWI7kqEwWT6Xvu20vqdwzF498H1Da8l4WpdgVIGtc1Q==}
vfile-location@5.0.3:
resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
@@ -9590,7 +9590,7 @@ snapshots:
spdx-correct: 3.2.0
spdx-expression-parse: 3.0.1
vanilla-calendar-pro@3.3.2: {}
vanilla-calendar-pro@3.4.0: {}
vfile-location@5.0.3:
dependencies: