diff --git a/.changeset/datepicker-vanilla-calendar-extensions.md b/.changeset/datepicker-vanilla-calendar-extensions.md new file mode 100644 index 000000000..d8e8414fa --- /dev/null +++ b/.changeset/datepicker-vanilla-calendar-extensions.md @@ -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. diff --git a/core/js/src/datepicker.ts b/core/js/src/datepicker.ts index 082850e55..af8d6380c 100644 --- a/core/js/src/datepicker.ts +++ b/core/js/src/datepicker.ts @@ -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 | 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, diff --git a/core/js/tests/unit/datepicker.spec.ts b/core/js/tests/unit/datepicker.spec.ts index 9ab21b6d3..4216ea81f 100644 --- a/core/js/tests/unit/datepicker.spec.ts +++ b/core/js/tests/unit/datepicker.spec.ts @@ -58,6 +58,28 @@ describe('Datepicker', () => { expect(instance._isInput).toBe(true) }) + it('should register the extensions the library exports', () => { + fixtureEl.innerHTML = '' + + 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 = '' + + 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 = '' diff --git a/core/package.json b/core/package.json index 8add9cc8e..e1a5a4d75 100644 --- a/core/package.json +++ b/core/package.json @@ -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" } } diff --git a/docs/content/ui/plugins/date-picker.mdx b/docs/content/ui/plugins/date-picker.mdx index 24f107b17..150bd2c37 100644 --- a/docs/content/ui/plugins/date-picker.mdx +++ b/docs/content/ui/plugins/date-picker.mdx @@ -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 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5d87b3c0..0d91c2786 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: