In every frontend/SPA project – at some point – we need a datepicker. Usually we pick a framework-compatible npm package like svelte-datepicker, ag-datepicker, and so on. In my latest Svelte project I used mymth/vanillajs-datepicker that works pretty weel, a lot of options, flexible and you can customize the look via custom css.
A downside of this, is that in my experience we need to try to avoid as much as possibile external dependencies, something you learn when you maintain a project for a long time, and things starts to be unsupported. At that point you have a big problem. It’s more a business decision, than a technical decision.
So what if it is possibile to have a datepicker without external dependencies? I didn’t know, but now almost all browser support <input type="date />
!
You can find all the technical details in the MDN docs.
Here an example in Svelte and TypeScript, using a custom action. Add this in a file native-date-picker.ts
.
You can find a working Svelte REPL here (Javascript).
PS1: note the ActionReturn
that allows Visual Studio Code (or any Svelte tooling) to know which events or attributes the action enriches the DOM element.
PS2: I added an example about how to open “manually” the datepicker, but the native datepicker has a button integrated, so it should not be needed, but I found interesting the showPicker()
method.
A limitation of the native datepicker is that you cannot force to show to the user a specific date format, but the browser language localization is used.
I’m not aware of any trick to bypass this – pretty annoying – limitation.
import type { ActionReturn } from 'svelte/action'; export type DatepickerOptions = { maxDate?: Date; minDate?: Date; } export type DatePickerContext = { date?: Date, options?: DatepickerOptions } interface Attributes { 'on:changeDate': (e: DatePickerChangeEvent) => void } export type DatePickerChangeEventPayload = { date: Date | undefined }; export type DatePickerChangeEvent = CustomEvent<DatePickerChangeEventPayload>; /** ISO8601 as explained in https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date */ const asNativeDateString = (date: Date) => { const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${date.getFullYear()}-${month}-${day}`; }; /** * Action for native DatePicker, see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date. * * You can manually open the picker via `<HTMLInputElement>.showPicker();` * @event changeDate * @example * <script lang="ts"> * let ctrl: HTMLInputElement; * * function onChangeDate(ev: DatePickerChangeEvent) { * const date = ev.detail.date; * console.log(date); * } * * function openDatePicker() { * ctrl.showPicker(); * } * </script> * * <input type="date" use:nativeDatePicker="{{ date: null, options: { minDate: new Date(1970, 0, 1) } }}" bind:this="{ctrl}" on:changeDate="{onChangeDate}" /> * <button type="button" on:click="{openDatePicker}"></button> */ export function nativeDatePicker(node: HTMLElement, data: DatePickerContext): ActionReturn<DatePickerContext, Attributes> { const input = node as HTMLInputElement; if (!(node instanceof HTMLInputElement) && input.type !== 'date') { throw new Error('Unsupported type'); } function init(data: DatePickerContext) { input.value = data.date ? asNativeDateString(data.date) : ''; input.max = data.options?.maxDate ? asNativeDateString(data.options.maxDate) : ''; input.min = data.options?.minDate ? asNativeDateString(data.options.minDate) : ''; } function handleChange(_: Event) { const date = input.valueAsDate ?? undefined; node.dispatchEvent(new CustomEvent<DatePickerChangeEventPayload>('changeDate', { detail: { date: date } })); } init(data); node.addEventListener('change', handleChange); return { update(newData: DatePickerContext) { init(newData); }, destroy() { node.removeEventListener('change', handleChange); }, }; }
And then a Svelte component using it:
<script lang="ts"> let ctrl: HTMLInputElement; function onChangeDate(ev: DatePickerChangeEvent) { const date = ev.detail.date; console.log(date); } function openDatePicker() { ctrl.showPicker(); } </script> <input type="date" use:nativeDatePicker="{{ date: null, options: { minDate: new Date(1970, 0, 1) } }}" bind:this="{ctrl}" on:changeDate="{onChangeDate}" /> <button type="button" on:click="{openDatePicker}"></button>