UNPKG

vanillajs-datepicker

Version:

A vanilla JavaScript remake of bootstrap-datepicker for Bulma and other CSS frameworks

210 lines (195 loc) 5.85 kB
import {isInRange} from '../lib/utils.js'; import {isActiveElement} from '../lib/dom.js'; import {addDays, addMonths, addYears} from '../lib/date.js'; import { goToPrevOrNext, switchView, clearSelection, goToOrSelectToday, unfocus, } from './functions.js'; // Find the closest date that doesn't meet the condition for unavailable date // Returns undefined if no available date is found // addFn: function to calculate the next date // - args: time value, amount // increase: amount to pass to addFn // testFn: function to test the unavailability of the date // - args: time value; return: true if unavailable function findNextAvailableOne(date, addFn, increase, testFn, min, max) { if (!isInRange(date, min, max)) { return; } if (testFn(date)) { const newDate = addFn(date, increase); return findNextAvailableOne(newDate, addFn, increase, testFn, min, max); } return date; } // direction: -1 (left/up), 1 (right/down) // vertical: true for up/down, false for left/right function moveByArrowKey(datepicker, direction, vertical) { const picker = datepicker.picker; const currentView = picker.currentView; const step = currentView.step || 1; let viewDate = picker.viewDate; let addFn; switch (currentView.id) { case 0: viewDate = addDays(viewDate, vertical ? direction * 7 : direction); addFn = addDays; break; case 1: viewDate = addMonths(viewDate, vertical ? direction * 4 : direction); addFn = addMonths; break; default: viewDate = addYears(viewDate, direction * (vertical ? 4 : 1) * step); addFn = addYears; } viewDate = findNextAvailableOne( viewDate, addFn, direction < 0 ? -step : step, (date) => currentView.disabled.includes(date), currentView.minDate, currentView.maxDate ); if (viewDate !== undefined) { picker.changeFocus(viewDate).render(); } } export function onKeydown(datepicker, ev) { const {config, picker, editMode} = datepicker; const active = picker.active; const {key, altKey, shiftKey} = ev; const ctrlOrMetaKey = ev.ctrlKey || ev.metaKey; const cancelEvent = () => { ev.preventDefault(); ev.stopPropagation(); }; // tab/enter keys should not be taken by shortcut keys if (key === 'Tab') { unfocus(datepicker); return; } if (key === 'Enter') { if (!active) { datepicker.update(); } else if (editMode) { datepicker.exitEditMode({update: true, autohide: config.autohide}); } else { const currentView = picker.currentView; if (currentView.isMinView) { datepicker.setDate(picker.viewDate); } else { picker.changeView(currentView.id - 1).render(); cancelEvent(); } } return; } const shortcutKeys = config.shortcutKeys; const keyInfo = {key, ctrlOrMetaKey, altKey, shiftKey}; const shortcut = Object.keys(shortcutKeys).find((item) => { const keyDef = shortcutKeys[item]; return !Object.keys(keyDef).find(prop => keyDef[prop] !== keyInfo[prop]); }); if (shortcut) { let action; if (shortcut === 'toggle') { action = shortcut; } else if (editMode) { if (shortcut === 'exitEditMode') { action = shortcut; } } else if (active) { if (shortcut === 'hide') { action = shortcut; } else if (shortcut === 'prevButton') { action = [goToPrevOrNext, [datepicker, -1]]; } else if (shortcut === 'nextButton') { action = [goToPrevOrNext, [datepicker, 1]]; } else if (shortcut === 'viewSwitch') { action = [switchView, [datepicker]]; } else if (config.clearButton && shortcut === 'clearButton') { action = [clearSelection, [datepicker]]; } else if (config.todayButton && shortcut === 'todayButton') { action = [goToOrSelectToday, [datepicker]]; } } else if (shortcut === 'show') { action = shortcut; } if (action) { if (Array.isArray(action)) { action[0].apply(null, action[1]); } else { datepicker[action](); } cancelEvent(); return; } } // perform as a regular <input> when picker in hidden or in edit mode if (!active || editMode) { return; } const handleArrowKeyPress = (direction, vertical) => { if (shiftKey || ctrlOrMetaKey || altKey) { datepicker.enterEditMode(); } else { moveByArrowKey(datepicker, direction, vertical); ev.preventDefault(); } }; if (key === 'ArrowLeft') { handleArrowKeyPress(-1, false); } else if (key === 'ArrowRight') { handleArrowKeyPress(1, false); } else if (key === 'ArrowUp') { handleArrowKeyPress(-1, true); } else if (key === 'ArrowDown') { handleArrowKeyPress(1, true); } else if ( key === 'Backspace' || key === 'Delete' || (key.length === 1 && !ctrlOrMetaKey) ) { datepicker.enterEditMode(); } } export function onFocus(datepicker) { if (datepicker.config.showOnFocus && !datepicker._showing) { datepicker.show(); } } // for the prevention for entering edit mode while getting focus on click export function onMousedown(datepicker, ev) { const el = ev.target; if (datepicker.picker.active || datepicker.config.showOnClick) { el._active = isActiveElement(el); el._clicking = setTimeout(() => { delete el._active; delete el._clicking; }, 2000); } } export function onClickInput(datepicker, ev) { const el = ev.target; if (!el._clicking) { return; } clearTimeout(el._clicking); delete el._clicking; if (el._active) { datepicker.enterEditMode(); } delete el._active; if (datepicker.config.showOnClick) { datepicker.show(); } } export function onPaste(datepicker, ev) { if (ev.clipboardData.types.includes('text/plain')) { datepicker.enterEditMode(); } }