UNPKG

svelte-flatpickr-plus

Version:

Flatpickr is a lightweight and powerful datetime picker. Svelte Flatpickr Plus is a wrapper for Flatpickr with some extra features.

328 lines (310 loc) 17.2 kB
import flatpickr from 'flatpickr_plus'; import yearDropdownPlugin from 'flatpickr_plus/dist/plugins/yearDropdown'; import monthSelectPlugin from 'flatpickr_plus/dist/plugins/monthSelect'; import 'flatpickr_plus/dist/flatpickr.css'; import 'flatpickr_plus/dist/plugins/monthSelect/style.css'; /** * Represents a date option that can be either a Date object, a string, or a number. * @typedef {Date | string | number} DateOption */ /** * Represents a date range limit with 'from' and 'to' properties. * @template D * @typedef {Object} DateRangeLimit * @property {D} from - The start of the date range limit. * @property {D} to - The end of the date range limit. */ /** * Represents a date limit that can be a single date, a date range limit, or a function that checks dates. * @template D * @typedef {D | DateRangeLimit<D> | ((date: Date) => boolean)} DateLimit */ /** * A hook function that receives an array of dates, the current date string, the Flatpickr instance, and optional data. * @typedef {(dates: Date[], currentDateString: string, self: any, data?: any) => void} Hook */ /** * Represents a key for various hooks used in Flatpickr. * @typedef {"onChange" | "onClose" | "onDayCreate" | "onDestroy" | "onKeyDown" | "onMonthChange" | "onOpen" | "onParseConfig" | "onReady" | "onValueUpdate" | "onYearChange" | "onPreCalendarPosition"} HookKey */ /** * Represent a Event name for flatpikr attribute can listen * @typedef {"onchange" | "onclose" | "ondaycreate" | "ondestroy" | "onkeydown" | "onmonthchange" | "onopen" | "onparseconfig" | "onready" | "onvalueupdate" | "onyearchange" | "onprecalendarposition"} EventKey * @property {function} [onchange] - Fires when the selected dates have changed, either when a date is picked or cleared, by the user or programmatically. * @property {function} [onclose] - Fires when the calendar is closed. * @property {function} [ondaycreate] - Fires for every day cell in the calendar, where the fourth argument is the HTML element of the cell. See https://chmln.github.io/flatpickr/events/#ondaycreate * @property {function} [ondestroy] - Fires before the calendar instance is destroyed. * @property {function} [onkeydown] - Fires when valid keyboard input for the calendar is detected. * @property {function} [onmonthchange] - Fires after the month has changed. * @property {function} [onopen] - Fires after the calendar is opened. * @property {function} [onparseconfig] - Fires after the configuration for the calendar is parsed. * @property {function} [onready] - Fires once the calendar instance is ready. * @property {function} [onvalueupdate] - Like onChange, but fires immediately after any date changes. * @property {function} [onyearchange] - Fires after the year has changed. * @property {function} [onprecalendarposition] - Fires before the calendar position is calculated. */ /** * @typedef {Object} FlatpickrOptions * @property {boolean} [allowInput] - Allows the user to enter a date directly into the input field. By default, direct entry is disabled. * @property {boolean} [allowInvalidPreload] - Allow preloading of an invalid date. * @property {string} [altFormat] - Exactly the same as date format, but for the altInput field. * @property {boolean} [altInput] - Show the user a readable date (as per altFormat), but return something totally different to the server. * @property {string} [altInputClass] - This class will be added to the input element created by the altInput option. Note that altInput already inherits classes from the original input. * @property {boolean} [animate] - Whether to enable animations, such as month transitions. * @property {HTMLElement} [appendTo] - Instead of body, appends the calendar to the specified node instead. * @property {string} [ariaDateFormat] - Defines how the date will be formatted in the aria-label for calendar days, using the same tokens as dateFormat. If you change this, you should choose a value that will make sense if a screen reader reads it out loud. Defaults to "F j, Y". * @property {boolean} [autoFillDefaultTime] - Whether the default time should be auto-filled when the input is empty and gains or loses focus. Defaults to true. * @property {boolean} [clickOpens] - Whether clicking on the input should open the picker. Set it to false if you only want to open the calendar programmatically. * @property {boolean} [closeOnSelect] - Whether the calendar should close after date selection. * @property {string} [conjunction] - If "mode" is "multiple", this string will be used to join selected dates together for the date input value. * @property {string} [dateFormat] - A string of characters that are used to define how the date will be displayed in the input box. See https://chmln.github.io/flatpickr/formatting * @property {DateOption | DateOption[]} [defaultDate] - The initial selected date(s). * @property {number} [defaultHour] - Initial value of the hour element, when no date is selected. * @property {number} [defaultMinute] - Initial value of the minute element, when no date is selected. * @property {number} [defaultSeconds] - Initial value of the seconds element, when no date is selected. * @property {DateLimit<DateOption>[]} [disable] - Disables certain dates, preventing them from being selected. See https://chmln.github.io/flatpickr/examples/#disabling-specific-dates * @property {boolean} [disableMobile] - Set this to true to always use the non-native picker on mobile devices. By default, Flatpickr utilizes native datetime widgets unless certain options (e.g., disable) are used. * @property {DateLimit<DateOption>[]} [enable] - Disables all dates except these specified. See https://chmln.github.io/flatpickr/examples/#disabling-all-dates-except-select-few * @property {boolean} [enableSeconds] - Enables seconds selection in the time picker. * @property {boolean} [enableTime] - Enables the time picker. * @property {(e: Error) => void} [errorHandler] - Allows using a custom error handling function. * @property {(date: Date, format: string, locale: any) => string} [formatDate] - Allows using a custom date formatting function instead of the built-in. Generally unnecessary. * @property {(date: Date) => string | number} [getWeek] - If "weekNumbers" are enabled, this is the function that outputs the week number for a given date, optionally along with other text. * @property {number} [hourIncrement] - Adjusts the step for the hour input (including scrolling). * @property {HTMLElement[]} [ignoredFocusElements] - By default, clicking anywhere outside of the calendar/input will close the calendar. Clicking on elements specified in this option will not close the calendar. * @property {boolean} [inline] - Displays the calendar inline. * @property {boolean} [isMonthPicker] - Whether the calendar is a month picker. * @property {any | Partial<any>} [locale] - The locale, either as a string (e.g., "ru", "en") or as an object. See https://chmln.github.io/flatpickr/localization/ * @property {DateOption} [maxDate] - The maximum date that a user can pick (inclusive). * @property {DateOption} [maxTime] - The maximum time that a user can pick (inclusive). * @property {DateOption} [minDate] - The minimum date that a user can pick (inclusive). * @property {DateOption} [minTime] - The minimum time that a user can pick (inclusive). * @property {number} [minuteIncrement] - Adjusts the step for the minute input (including scrolling). Defaults to 5. * @property {"single" | "multiple" | "range" | "time"} [mode] - Date selection mode, defaults to "single". * @property {"dropdown" | "static"} [monthSelectorType] - How the month selector in the calendar should be shown. * @property {string} [nextArrow] - HTML for the right arrow icon, used to switch months. * @property {boolean} [noCalendar] - Hides the day selection in the calendar. Use it along with "enableTime" to create a time picker. * @property {DateOption} [now] - Specifies the current date and time. * @property {Hook | Hook[]} [onChange] - Fires when the selected dates have changed, either when a date is picked or cleared, by the user or programmatically. * @property {Hook | Hook[]} [onClose] - Fires when the calendar is closed. * @property {Hook | Hook[]} [onDayCreate] - Fires for every day cell in the calendar, where the fourth argument is the HTML element of the cell. See https://chmln.github.io/flatpickr/events/#ondaycreate * @property {Hook | Hook[]} [onDestroy] - Fires before the calendar instance is destroyed. * @property {Hook | Hook[]} [onKeyDown] - Fires when valid keyboard input for the calendar is detected. * @property {Hook | Hook[]} [onMonthChange] - Fires after the month has changed. * @property {Hook | Hook[]} [onOpen] - Fires after the calendar is opened. * @property {Hook | Hook[]} [onParseConfig] - Fires after the configuration for the calendar is parsed. * @property {Hook | Hook[]} [onReady] - Fires once the calendar instance is ready. * @property {Hook | Hook[]} [onValueUpdate] - Like onChange, but fires immediately after any date changes. * @property {Hook | Hook[]} [onYearChange] - Fires after the year has changed. * @property {Hook | Hook[]} [onPreCalendarPosition] - Fires before the calendar position is calculated. * @property {(date: string, format: string) => Date} [parseDate] - A custom datestring parser. * @property {Plugin[]} [plugins] - Plugins. See https://chmln.github.io/flatpickr/plugins/ * @property {"auto" | "above" | "below" | "auto left" | "auto center" | "auto right" | "above left" | "above center" | "above right" | "below left" | "below center" | "below right" | ((self: any, customElement: HTMLElement | undefined) => void)} [position] - How the calendar should be positioned with regards to the input. Defaults to "auto". * @property {Element} [positionElement] - The element off of which the calendar will be positioned. Defaults to the date input. * @property {string} [prevArrow] - HTML for the left arrow icon, used to switch months. * @property {boolean} [shorthandCurrentMonth] - Whether to display the current month name in shorthand mode, e.g., "Sep" instead of "September". * @property {boolean} [shorthand] - Whether to display the current date in shorthand mode. * @property {boolean} [static] - Creates a wrapper to position the calendar. Use this if the input is inside a scrollable element. * @property {number} [showMonths] - Sets the number of months to show. * @property {boolean} [time_24hr] - Displays time picker in 24-hour mode without AM/PM selection when enabled. * @property {boolean} [weekNumbers] - Display week numbers left of the calendar. * @property {boolean} [wrap] - See https://chmln.github.io/flatpickr/examples/#flatpickr-external-elements * @property {boolean} [useLocaleYear] - Handling year as locale year. * @property {boolean} [resetMoveDefault] - Handling reset and move to calendar to default date. * @property {boolean} [resetToDefault] - Handling reset and selected a default date. */ const endDayOfNextYear = new Date(new Date().getFullYear() + 1, 11, 31); /** @type {FlatpickrOptions} */ const defaultOptions = { allowInput: false, allowInvalidPreload: false, altFormat: "F j, Y", altInput: false, altInputClass: "", ariaDateFormat: "F j, Y", autoFillDefaultTime: true, clickOpens: true, closeOnSelect: true, conjunction: ", ", dateFormat: "Y-m-d", defaultHour: 12, defaultMinute: 0, defaultSeconds: 0, disable: [], disableMobile: true, enableSeconds: false, enableTime: false, hourIncrement: 1, ignoredFocusElements: [], inline: false, isMonthPicker: false, locale: "default", maxDate: endDayOfNextYear, minuteIncrement: 5, mode: "single", monthSelectorType: "dropdown", noCalendar: false, now: new Date(), onChange: [], onClose: [], onDayCreate: [], onDestroy: [], onKeyDown: [], onMonthChange: [], onOpen: [], onParseConfig: [], onReady: [], onValueUpdate: [], onYearChange: [], onPreCalendarPosition: [], plugins: [], position: "auto", positionElement: undefined, shorthandCurrentMonth: false, shorthand: false, showMonths: 1, static: false, time_24hr: false, weekNumbers: false, wrap: false, useLocaleYear: false, resetMoveDefault: true, resetToDefault: true, }; /** @type {FlatpickrOptions} */ const defaultMonthOptions = { ...defaultOptions, altFormat: 'F Y', ariaDateFormat: 'F Y', dateFormat: 'F Y' } /** @type {HookKey[]} */ const hooks = [ 'onChange', 'onMonthChange', 'onYearChange', 'onReady', 'onOpen', 'onDayCreate', 'onClose', 'onValueUpdate' ]; /** * @param {FlatpickrOptions} opts * @returns {FlatpickrOptions} */ function modifyHooks(opts = {}) { opts = Object.assign({}, opts); for (const hook of hooks) { if (!Array.isArray(opts[hook])) opts[hook] = [opts[hook]]; } return opts; } /** * @param {Event|CustomEvent} event * @param {*} fp * @param {FlatpickrOptions} opts */ function resetFlatpickr(event, fp, opts) { fp.clear(); if (opts.defaultDate && opts.resetToDefault) event.preventDefault(); } /** * * @param {HTMLInputElement} node * @param {FlatpickrOptions} opts * @param {[*]} plugins * @returns */ function attachFlatpickr(node, opts, plugins = opts.noCalendar ? [] : [yearDropdownPlugin()]) { node.setAttribute('autocomplete', 'off'); if (!opts.allowInput) { node.setAttribute('readonly', 'true'); } /** @type {import('flatpickr_plus').flatpickr.Instance} */ const fp = flatpickr(node, { ...opts, onOpen: [ async function (selectedDates, dateStr, instance) { if (!opts.allowInput) { if (instance.altInput) { const dayElement = await instance.days; if (dayElement) { if (dayElement.querySelector('.today')) { /** @type {HTMLElement} */ (dayElement.querySelector('.today'))?.focus(); } else { /** @type {HTMLElement} */ (dayElement.querySelector('.selected'))?.focus(); } } } else { const dayElement = await instance.days; if (dayElement) { if (dayElement.querySelector('.today')) { /** @type {HTMLElement} */ (dayElement.querySelector('.today'))?.focus(); } else { /** @type {HTMLElement} */ (dayElement.querySelector('.selected'))?.focus(); } return } instance.hourElement?.focus() } } }, ...opts.onOpen ], onClose: [ function (selectedDates, dateStr, instance) { if (instance.altInput) { instance.altInput.blur(); } else { instance.input.blur(); } }, ...opts.onClose ], plugins: plugins }); if (opts.wrap) node.querySelector('input').form?.addEventListener('reset', (ev) => resetFlatpickr(ev, fp, opts)); else node.form?.addEventListener('reset', (ev) => resetFlatpickr(ev, fp, opts)); return fp; } /** @type {import('./types.js').FlatpickrAction} */ export default function (node, options = defaultOptions) { if (options.isMonthPicker) { options = { ...defaultMonthOptions, ...options } } else { options = { ...defaultOptions, ...options } } const opts = modifyHooks(options); const monthPlugins = [ monthSelectPlugin({ shorthand: options.shorthand, //defaults to false dateFormat: options.dateFormat, //defaults to "F Y" altFormat: options.altFormat //defaults to "F Y" }), yearDropdownPlugin(), ] const instance = attachFlatpickr(node, opts, options.isMonthPicker ? monthPlugins : []); $effect(() => { if (opts.defaultDate) { const event = new Event('input'); node.dispatchEvent(event); } return () => { instance.destroy(); instance._input?.form?.removeEventListener('reset', (ev) => resetFlatpickr(ev, instance, opts)); }; }); }