flowbite-datepicker-1.3.0
Version:
A Tailwind CSS powered datepicker built with vanilla JavaScript and Flowbite
207 lines (196 loc) • 5.61 kB
JavaScript
import {isInRange} from '../lib/utils.js';
import {addDays, addMonths, addYears, startOfYearPeriod} from '../lib/date.js';
import {goToPrevOrNext, switchView, 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 unavailablity of the date
// - args: time value; retun: 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, ev, direction, vertical) {
const picker = datepicker.picker;
const currentView = picker.currentView;
const step = currentView.step || 1;
let viewDate = picker.viewDate;
let addFn;
let testFn;
switch (currentView.id) {
case 0:
if (vertical) {
viewDate = addDays(viewDate, direction * 7);
} else if (ev.ctrlKey || ev.metaKey) {
viewDate = addYears(viewDate, direction);
} else {
viewDate = addDays(viewDate, direction);
}
addFn = addDays;
testFn = (date) => currentView.disabled.includes(date);
break;
case 1:
viewDate = addMonths(viewDate, vertical ? direction * 4 : direction);
addFn = addMonths;
testFn = (date) => {
const dt = new Date(date);
const {year, disabled} = currentView;
return dt.getFullYear() === year && disabled.includes(dt.getMonth());
};
break;
default:
viewDate = addYears(viewDate, direction * (vertical ? 4 : 1) * step);
addFn = addYears;
testFn = date => currentView.disabled.includes(startOfYearPeriod(date, step));
}
viewDate = findNextAvailableOne(
viewDate,
addFn,
direction < 0 ? -step : step,
testFn,
currentView.minDate,
currentView.maxDate
);
if (viewDate !== undefined) {
picker.changeFocus(viewDate).render();
}
}
export function onKeydown(datepicker, ev) {
if (ev.key === 'Tab') {
unfocus(datepicker);
return;
}
const picker = datepicker.picker;
const {id, isMinView} = picker.currentView;
if (!picker.active) {
switch (ev.key) {
case 'ArrowDown':
case 'Escape':
picker.show();
break;
case 'Enter':
datepicker.update();
break;
default:
return;
}
} else if (datepicker.editMode) {
switch (ev.key) {
case 'Escape':
picker.hide();
break;
case 'Enter':
datepicker.exitEditMode({update: true, autohide: datepicker.config.autohide});
break;
default:
return;
}
} else {
switch (ev.key) {
case 'Escape':
picker.hide();
break;
case 'ArrowLeft':
if (ev.ctrlKey || ev.metaKey) {
goToPrevOrNext(datepicker, -1);
} else if (ev.shiftKey) {
datepicker.enterEditMode();
return;
} else {
moveByArrowKey(datepicker, ev, -1, false);
}
break;
case 'ArrowRight':
if (ev.ctrlKey || ev.metaKey) {
goToPrevOrNext(datepicker, 1);
} else if (ev.shiftKey) {
datepicker.enterEditMode();
return;
} else {
moveByArrowKey(datepicker, ev, 1, false);
}
break;
case 'ArrowUp':
if (ev.ctrlKey || ev.metaKey) {
switchView(datepicker);
} else if (ev.shiftKey) {
datepicker.enterEditMode();
return;
} else {
moveByArrowKey(datepicker, ev, -1, true);
}
break;
case 'ArrowDown':
if (ev.shiftKey && !ev.ctrlKey && !ev.metaKey) {
datepicker.enterEditMode();
return;
}
moveByArrowKey(datepicker, ev, 1, true);
break;
case 'Enter':
if (isMinView) {
datepicker.setDate(picker.viewDate);
} else {
picker.changeView(id - 1).render();
}
break;
case 'Backspace':
case 'Delete':
datepicker.enterEditMode();
return;
default:
if (ev.key.length === 1 && !ev.ctrlKey && !ev.metaKey) {
datepicker.enterEditMode();
}
return;
}
}
ev.preventDefault();
ev.stopPropagation();
}
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 = el === document.activeElement;
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();
}
}