mirror of
https://github.com/tabler/tabler.git
synced 2026-10-12 05:06:55 +04:00
Fix datepicker types requiring vanilla-calendar-pro to be installed (#3220)
Co-authored-by: codecalm <codecalm@gmail.com>
This commit is contained in:
1 parent
0a9edcb400
commit
6199b80b28
5 files changed
+52
-14
No files matched your search
@@ -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 EventHandler from './bootstrap/dom/event-handler'
|
||||
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})$/
|
||||
|
||||
// The date, weekday, month-count and placement options are handed straight to
|
||||
// Vanilla Calendar Pro, so they use its own literal unions rather than the wider
|
||||
// `string` / `number`. That keeps a bad value a compile error here instead of a
|
||||
// silent no-op inside the calendar.
|
||||
// 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
|
||||
// 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 = {
|
||||
/** 'light', 'dark' or 'auto' for the popup only; null inherits from the nearest `[data-bs-theme]` */
|
||||
datepickerTheme: string | null
|
||||
@@ -66,7 +92,7 @@ type ComponentConfig = {
|
||||
selectionMode: DateMode
|
||||
placement: PositionToInput
|
||||
/** pass-through for any Vanilla Calendar Pro option */
|
||||
vcpOptions: Options
|
||||
vcpOptions: object
|
||||
}
|
||||
|
||||
type ComponentConfigInput = Partial<ComponentConfig> & Record<string, unknown>
|
||||
@@ -126,6 +152,7 @@ const afterPluginTimers = (callback: () => void): void => {
|
||||
class Datepicker extends BaseComponent {
|
||||
declare _element: HTMLElement & { value: string }
|
||||
declare _config: ComponentConfig
|
||||
/** @internal */
|
||||
_calendar: Calendar | null = null
|
||||
_isShown = false
|
||||
// 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. */
|
||||
get calendar(): Calendar | null {
|
||||
get calendar(): CalendarInstance | null {
|
||||
return this._calendar
|
||||
}
|
||||
|
||||
@@ -528,6 +555,7 @@ class Datepicker extends BaseComponent {
|
||||
EventHandler.trigger(this._element, EVENT_HIDDEN)
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_buildCalendarOptions(): Options {
|
||||
// The plugin uses 'system' for auto-detection, Bootstrap and Tabler use 'auto'
|
||||
const theme = this._getEffectiveTheme()
|
||||
@@ -563,16 +591,17 @@ class Datepicker extends BaseComponent {
|
||||
}
|
||||
|
||||
if (this._config.dateMin) {
|
||||
calendarOptions.dateMin = this._config.dateMin
|
||||
calendarOptions.dateMin = this._config.dateMin as Options['dateMin']
|
||||
}
|
||||
|
||||
if (this._config.dateMax) {
|
||||
calendarOptions.dateMax = this._config.dateMax
|
||||
calendarOptions.dateMax = this._config.dateMax as Options['dateMax']
|
||||
}
|
||||
|
||||
return calendarOptions
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_handleDateClick(self: Calendar, event: MouseEvent): void {
|
||||
const selectedDates = [...self.context.selectedDates]
|
||||
this._selectedDates = selectedDates
|
||||
@@ -672,6 +701,7 @@ class Datepicker extends BaseComponent {
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_monthOf(value: string | undefined): Pick<Options, 'selectedMonth' | 'selectedYear'> {
|
||||
if (!value || !DATE_PATTERN.test(value)) {
|
||||
return {}
|
||||
|
||||
Vendored
+3
-1
@@ -3,7 +3,9 @@ import type { JQueryStaticLike } from './bootstrap/types'
|
||||
// Plugins that are loaded separately, not bundled: IMask (https://imask.js.org),
|
||||
// SortableJS (https://sortablejs.github.io/Sortable/) and Vanilla Calendar Pro
|
||||
// (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 {
|
||||
interface Window {
|
||||
VanillaCalendarPro?: typeof import('vanilla-calendar-pro')
|
||||
|
||||
@@ -364,8 +364,8 @@ describe('Datepicker', () => {
|
||||
instance.setSelectedDates(['2024-06-20'])
|
||||
expect(input().value).not.toBe('')
|
||||
|
||||
instance.calendar!.context.selectedDates = []
|
||||
instance._handleDateClick(instance.calendar!, new MouseEvent('click'))
|
||||
instance._calendar!.context.selectedDates = []
|
||||
instance._handleDateClick(instance._calendar!, new MouseEvent('click'))
|
||||
|
||||
expect(input().value).toBe('')
|
||||
})
|
||||
@@ -373,8 +373,8 @@ describe('Datepicker', () => {
|
||||
it('does not run the hide timer after dispose', async () => {
|
||||
fixtureEl.innerHTML = '<input type="text">'
|
||||
const instance = new Datepicker(input())
|
||||
instance.calendar!.context.selectedDates = ['2024-06-20']
|
||||
instance._handleDateClick(instance.calendar!, new MouseEvent('click'))
|
||||
instance._calendar!.context.selectedDates = ['2024-06-20']
|
||||
instance._handleDateClick(instance._calendar!, new MouseEvent('click'))
|
||||
|
||||
instance.dispose()
|
||||
await new Promise((resolve) => setTimeout(resolve, 150))
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"noEmit": false,
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"stripInternal": true,
|
||||
"rootDir": "js",
|
||||
"outDir": "dist/types"
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user