Fix datepicker types requiring vanilla-calendar-pro to be installed (#3220)

Co-authored-by: codecalm <codecalm@gmail.com>
This commit is contained in:
Bartosz-Doandcodecalm authored and GitHub committed 2026-09-25 15:34:42 +02:00
1 parent 0a9edcb400
commit 6199b80b28
5 files changed
+52 -14

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
"@tabler/core": patch
---
Fixed published types requiring the optional `vanilla-calendar-pro` peer dependency to be installed.
+39 -9
View File
@@ -5,7 +5,7 @@
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
import type { Calendar, DateAny, DateMode, DatesArr, MonthsCount, Options, PositionToInput, Range, WeekDayID } from 'vanilla-calendar-pro' import type { Calendar, Options, Range } from 'vanilla-calendar-pro'
import BaseComponent from './bootstrap/base-component' import BaseComponent from './bootstrap/base-component'
import EventHandler from './bootstrap/dom/event-handler' import EventHandler from './bootstrap/dom/event-handler'
import SelectorEngine from './bootstrap/dom/selector-engine' import SelectorEngine from './bootstrap/dom/selector-engine'
@@ -40,10 +40,36 @@ const HIDE_DELAY = 100 // ms delay before hiding after selection
const DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/ const DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/
// The date, weekday, month-count and placement options are handed straight to // The public API is typed with local copies of the Vanilla Calendar Pro types.
// Vanilla Calendar Pro, so they use its own literal unions rather than the wider // `vanilla-calendar-pro` is an optional peer dependency, so the published
// `string` / `number`. That keeps a bad value a compile error here instead of a // `dist/types` must not import it: a project that never loads the datepicker
// silent no-op inside the calendar. // would otherwise fail to type-check with TS2307. The unions match the
// plugin's own, so a bad value is still a compile error. Members that need the
// plugin's full types carry an internal JSDoc tag and are left out of
// `dist/types` by `stripInternal` (the tag must not appear in plain comments,
// or TypeScript strips the declaration that follows).
type DateAny = Date | number | `${number}-${string}-${string}` | 'today'
type DatesArr = Array<Date | number | string>
type DateMode = 'single' | 'multiple' | 'multiple-ranged'
type MonthsCount = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12
type PositionToInput = 'auto' | 'left' | 'center' | 'right' | ['bottom' | 'top', 'left' | 'center' | 'right']
type WeekDayID = 0 | 1 | 2 | 3 | 4 | 5 | 6
/**
* The part of a Vanilla Calendar Pro instance the `calendar` getter promises.
* Cast it to `Calendar` from `vanilla-calendar-pro` for the full type.
*/
interface CalendarInstance {
// Loosely typed on purpose: the plugin's context is large and version-specific.
context: Record<string, any>
init(): unknown
update(resetOptions?: object): unknown
set(options: object, resetOptions?: object): unknown
show(): unknown
hide(): unknown
destroy(): unknown
}
type ComponentConfig = { type ComponentConfig = {
/** 'light', 'dark' or 'auto' for the popup only; null inherits from the nearest `[data-bs-theme]` */ /** 'light', 'dark' or 'auto' for the popup only; null inherits from the nearest `[data-bs-theme]` */
datepickerTheme: string | null datepickerTheme: string | null
@@ -66,7 +92,7 @@ type ComponentConfig = {
selectionMode: DateMode selectionMode: DateMode
placement: PositionToInput placement: PositionToInput
/** pass-through for any Vanilla Calendar Pro option */ /** pass-through for any Vanilla Calendar Pro option */
vcpOptions: Options vcpOptions: object
} }
type ComponentConfigInput = Partial<ComponentConfig> & Record<string, unknown> type ComponentConfigInput = Partial<ComponentConfig> & Record<string, unknown>
@@ -126,6 +152,7 @@ const afterPluginTimers = (callback: () => void): void => {
class Datepicker extends BaseComponent { class Datepicker extends BaseComponent {
declare _element: HTMLElement & { value: string } declare _element: HTMLElement & { value: string }
declare _config: ComponentConfig declare _config: ComponentConfig
/** @internal */
_calendar: Calendar | null = null _calendar: Calendar | null = null
_isShown = false _isShown = false
// The plugin opens and closes the popup on its own too (its click and focus // The plugin opens and closes the popup on its own too (its click and focus
@@ -172,7 +199,7 @@ class Datepicker extends BaseComponent {
} }
/** The Vanilla Calendar Pro instance, for options the component does not expose. */ /** The Vanilla Calendar Pro instance, for options the component does not expose. */
get calendar(): Calendar | null { get calendar(): CalendarInstance | null {
return this._calendar return this._calendar
} }
@@ -528,6 +555,7 @@ class Datepicker extends BaseComponent {
EventHandler.trigger(this._element, EVENT_HIDDEN) EventHandler.trigger(this._element, EVENT_HIDDEN)
} }
/** @internal */
_buildCalendarOptions(): Options { _buildCalendarOptions(): Options {
// The plugin uses 'system' for auto-detection, Bootstrap and Tabler use 'auto' // The plugin uses 'system' for auto-detection, Bootstrap and Tabler use 'auto'
const theme = this._getEffectiveTheme() const theme = this._getEffectiveTheme()
@@ -563,16 +591,17 @@ class Datepicker extends BaseComponent {
} }
if (this._config.dateMin) { if (this._config.dateMin) {
calendarOptions.dateMin = this._config.dateMin calendarOptions.dateMin = this._config.dateMin as Options['dateMin']
} }
if (this._config.dateMax) { if (this._config.dateMax) {
calendarOptions.dateMax = this._config.dateMax calendarOptions.dateMax = this._config.dateMax as Options['dateMax']
} }
return calendarOptions return calendarOptions
} }
/** @internal */
_handleDateClick(self: Calendar, event: MouseEvent): void { _handleDateClick(self: Calendar, event: MouseEvent): void {
const selectedDates = [...self.context.selectedDates] const selectedDates = [...self.context.selectedDates]
this._selectedDates = selectedDates this._selectedDates = selectedDates
@@ -672,6 +701,7 @@ class Datepicker extends BaseComponent {
return `${year}-${month}-${day}` return `${year}-${month}-${day}`
} }
/** @internal */
_monthOf(value: string | undefined): Pick<Options, 'selectedMonth' | 'selectedYear'> { _monthOf(value: string | undefined): Pick<Options, 'selectedMonth' | 'selectedYear'> {
if (!value || !DATE_PATTERN.test(value)) { if (!value || !DATE_PATTERN.test(value)) {
return {} return {}
+3 -1
View File
@@ -3,7 +3,9 @@ import type { JQueryStaticLike } from './bootstrap/types'
// Plugins that are loaded separately, not bundled: IMask (https://imask.js.org), // Plugins that are loaded separately, not bundled: IMask (https://imask.js.org),
// SortableJS (https://sortablejs.github.io/Sortable/) and Vanilla Calendar Pro // SortableJS (https://sortablejs.github.io/Sortable/) and Vanilla Calendar Pro
// (https://vanilla-calendar.pro). The instance types live in the component // (https://vanilla-calendar.pro). The instance types live in the component
// modules, so they are part of the published `dist/types`. // modules, so they are part of the published `dist/types`. This file itself is
// not emitted there (tsc does not copy `.d.ts` sources), so the real
// `vanilla-calendar-pro` import below is only seen inside this repo.
declare global { declare global {
interface Window { interface Window {
VanillaCalendarPro?: typeof import('vanilla-calendar-pro') VanillaCalendarPro?: typeof import('vanilla-calendar-pro')
+4 -4
View File
@@ -364,8 +364,8 @@ describe('Datepicker', () => {
instance.setSelectedDates(['2024-06-20']) instance.setSelectedDates(['2024-06-20'])
expect(input().value).not.toBe('') expect(input().value).not.toBe('')
instance.calendar!.context.selectedDates = [] instance._calendar!.context.selectedDates = []
instance._handleDateClick(instance.calendar!, new MouseEvent('click')) instance._handleDateClick(instance._calendar!, new MouseEvent('click'))
expect(input().value).toBe('') expect(input().value).toBe('')
}) })
@@ -373,8 +373,8 @@ describe('Datepicker', () => {
it('does not run the hide timer after dispose', async () => { it('does not run the hide timer after dispose', async () => {
fixtureEl.innerHTML = '<input type="text">' fixtureEl.innerHTML = '<input type="text">'
const instance = new Datepicker(input()) const instance = new Datepicker(input())
instance.calendar!.context.selectedDates = ['2024-06-20'] instance._calendar!.context.selectedDates = ['2024-06-20']
instance._handleDateClick(instance.calendar!, new MouseEvent('click')) instance._handleDateClick(instance._calendar!, new MouseEvent('click'))
instance.dispose() instance.dispose()
await new Promise((resolve) => setTimeout(resolve, 150)) await new Promise((resolve) => setTimeout(resolve, 150))
+1
View File
@@ -4,6 +4,7 @@
"noEmit": false, "noEmit": false,
"declaration": true, "declaration": true,
"emitDeclarationOnly": true, "emitDeclarationOnly": true,
"stripInternal": true,
"rootDir": "js", "rootDir": "js",
"outDir": "dist/types" "outDir": "dist/types"
}, },