react-datetime-picker
Version:
A date range picker for your React app.
541 lines (540 loc) • 24.2 kB
JavaScript
'use client';
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useEffect, useRef, useState } from 'react';
import { getYear, getMonthHuman, getDate, getHours, getMinutes, getSeconds, getHoursMinutesSeconds, } from '@wojtekmaj/date-utils';
import Divider from './Divider.js';
import DayInput from 'react-date-picker/dist/esm/DateInput/DayInput';
import MonthInput from 'react-date-picker/dist/esm/DateInput/MonthInput';
import MonthSelect from 'react-date-picker/dist/esm/DateInput/MonthSelect';
import YearInput from 'react-date-picker/dist/esm/DateInput/YearInput';
import Hour12Input from 'react-time-picker/dist/esm/TimeInput/Hour12Input';
import Hour24Input from 'react-time-picker/dist/esm/TimeInput/Hour24Input';
import MinuteInput from 'react-time-picker/dist/esm/TimeInput/MinuteInput';
import SecondInput from 'react-time-picker/dist/esm/TimeInput/SecondInput';
import AmPm from 'react-time-picker/dist/esm/TimeInput/AmPm';
import NativeInput from './DateTimeInput/NativeInput.js';
import { getFormatter, getNumberFormatter, formatDate } from './shared/dateFormatter.js';
import { convert12to24, convert24to12 } from './shared/dates.js';
import { between, getAmPmLabels } from './shared/utils.js';
var getFormatterOptionsCache = {};
var defaultMinDate = new Date();
defaultMinDate.setFullYear(1, 0, 1);
defaultMinDate.setHours(0, 0, 0, 0);
var defaultMaxDate = new Date(8.64e15);
var allViews = ['hour', 'minute', 'second'];
function toDate(value) {
if (value instanceof Date) {
return value;
}
return new Date(value);
}
function isSameDate(date, year, month, day) {
return (year === getYear(date).toString() &&
month === getMonthHuman(date).toString() &&
day === getDate(date).toString());
}
function getValue(value, index) {
var rawValue = Array.isArray(value) ? value[index] : value;
if (!rawValue) {
return null;
}
var valueDate = toDate(rawValue);
if (isNaN(valueDate.getTime())) {
throw new Error("Invalid date: ".concat(value));
}
return valueDate;
}
function getDetailValue(_a, index) {
var value = _a.value, minDate = _a.minDate, maxDate = _a.maxDate;
var valuePiece = getValue(value, index);
if (!valuePiece) {
return null;
}
return between(valuePiece, minDate, maxDate);
}
var getDetailValueFrom = function (args) { return getDetailValue(args, 0); };
function isInternalInput(element) {
return element.dataset.input === 'true';
}
function findInput(element, property) {
var nextElement = element;
do {
nextElement = nextElement[property];
} while (nextElement && !isInternalInput(nextElement));
return nextElement;
}
function focus(element) {
if (element) {
element.focus();
}
}
function renderCustomInputs(placeholder, elementFunctions, allowMultipleInstances) {
var usedFunctions = [];
var pattern = new RegExp(Object.keys(elementFunctions)
.map(function (el) { return "".concat(el, "+"); })
.join('|'), 'g');
var matches = placeholder.match(pattern);
return placeholder.split(pattern).reduce(function (arr, element, index) {
var divider = element && (
// eslint-disable-next-line react/no-array-index-key
_jsx(Divider, { children: element }, "separator_".concat(index)));
arr.push(divider);
var currentMatch = matches && matches[index];
if (currentMatch) {
var renderFunction = elementFunctions[currentMatch] ||
elementFunctions[Object.keys(elementFunctions).find(function (elementFunction) {
return currentMatch.match(elementFunction);
})];
if (!renderFunction) {
return arr;
}
if (!allowMultipleInstances && usedFunctions.includes(renderFunction)) {
arr.push(currentMatch);
}
else {
arr.push(renderFunction(currentMatch, index));
usedFunctions.push(renderFunction);
}
}
return arr;
}, []);
}
var formatNumber = getNumberFormatter({ useGrouping: false });
export default function DateTimeInput(_a) {
var amPmAriaLabel = _a.amPmAriaLabel, autoFocus = _a.autoFocus, className = _a.className, dayAriaLabel = _a.dayAriaLabel, dayPlaceholder = _a.dayPlaceholder, disabled = _a.disabled, format = _a.format, hourAriaLabel = _a.hourAriaLabel, hourPlaceholder = _a.hourPlaceholder, isWidgetOpenProps = _a.isWidgetOpen, locale = _a.locale, maxDate = _a.maxDate, _b = _a.maxDetail, maxDetail = _b === void 0 ? 'minute' : _b, minDate = _a.minDate, minuteAriaLabel = _a.minuteAriaLabel, minutePlaceholder = _a.minutePlaceholder, monthAriaLabel = _a.monthAriaLabel, monthPlaceholder = _a.monthPlaceholder, _c = _a.name, name = _c === void 0 ? 'datetime' : _c, nativeInputAriaLabel = _a.nativeInputAriaLabel, onChangeProps = _a.onChange, onInvalidChange = _a.onInvalidChange, required = _a.required, secondAriaLabel = _a.secondAriaLabel, secondPlaceholder = _a.secondPlaceholder, showLeadingZeros = _a.showLeadingZeros, valueProps = _a.value, yearAriaLabel = _a.yearAriaLabel, yearPlaceholder = _a.yearPlaceholder;
var _d = useState(null), amPm = _d[0], setAmPm = _d[1];
var _e = useState(null), year = _e[0], setYear = _e[1];
var _f = useState(null), month = _f[0], setMonth = _f[1];
var _g = useState(null), day = _g[0], setDay = _g[1];
var _h = useState(null), hour = _h[0], setHour = _h[1];
var _j = useState(null), minute = _j[0], setMinute = _j[1];
var _k = useState(null), second = _k[0], setSecond = _k[1];
var _l = useState(null), value = _l[0], setValue = _l[1];
var amPmInput = useRef(null);
var yearInput = useRef(null);
var monthInput = useRef(null);
var monthSelect = useRef(null);
var dayInput = useRef(null);
var hour12Input = useRef(null);
var hour24Input = useRef(null);
var minuteInput = useRef(null);
var secondInput = useRef(null);
var _m = useState(isWidgetOpenProps), isWidgetOpen = _m[0], setIsWidgetOpenOpen = _m[1];
var lastPressedKey = useRef(undefined);
useEffect(function () {
setIsWidgetOpenOpen(isWidgetOpenProps);
}, [isWidgetOpenProps]);
useEffect(function () {
var nextValue = getDetailValueFrom({
value: valueProps,
minDate: minDate,
maxDate: maxDate,
});
if (nextValue) {
setAmPm(convert24to12(getHours(nextValue))[1]);
setYear(getYear(nextValue).toString());
setMonth(getMonthHuman(nextValue).toString());
setDay(getDate(nextValue).toString());
setHour(getHours(nextValue).toString());
setMinute(getMinutes(nextValue).toString());
setSecond(getSeconds(nextValue).toString());
setValue(toDate(nextValue));
}
else {
setAmPm(null);
setYear(null);
setMonth(null);
setDay(null);
setHour(null);
setMinute(null);
setSecond(null);
setValue(null);
}
}, [
valueProps,
minDate,
maxDate,
// Toggling widget visibility resets values
isWidgetOpen,
]);
var valueType = maxDetail;
var formatTime = (function () {
var level = allViews.indexOf(maxDetail);
var formatterOptions = getFormatterOptionsCache[level] ||
(function () {
var options = { hour: 'numeric' };
if (level >= 1) {
options.minute = 'numeric';
}
if (level >= 2) {
options.second = 'numeric';
}
getFormatterOptionsCache[level] = options;
return options;
})();
return getFormatter(formatterOptions);
})();
var datePlaceholder = (function () {
var year = 2017;
var monthIndex = 11;
var day = 11;
var date = new Date(year, monthIndex, day);
var formattedDate = formatDate(locale, date);
var datePieces = ['year', 'month', 'day'];
var datePieceReplacements = ['y', 'M', 'd'];
function formatDatePiece(name, dateToFormat) {
var formatterOptions = getFormatterOptionsCache[name] ||
(function () {
var _a;
var options = (_a = {}, _a[name] = 'numeric', _a);
getFormatterOptionsCache[name] = options;
return options;
})();
return getFormatter(formatterOptions)(locale, dateToFormat).match(/\d{1,}/);
}
var placeholder = formattedDate;
datePieces.forEach(function (datePiece, index) {
var match = formatDatePiece(datePiece, date);
if (match) {
var formattedDatePiece = match[0];
var datePieceReplacement = datePieceReplacements[index];
placeholder = placeholder.replace(formattedDatePiece, datePieceReplacement);
}
});
// See: https://github.com/wojtekmaj/react-date-picker/issues/396
placeholder = placeholder.replace('17', 'y');
return placeholder;
})();
var timePlaceholder = (function () {
var hour24 = 21;
var hour12 = 9;
var minute = 13;
var second = 14;
var date = new Date(2017, 0, 1, hour24, minute, second);
return formatTime(locale, date)
.replace(formatNumber(locale, hour12), 'h')
.replace(formatNumber(locale, hour24), 'H')
.replace(formatNumber(locale, minute), 'mm')
.replace(formatNumber(locale, second), 'ss')
.replace(new RegExp(getAmPmLabels(locale).join('|')), 'a');
})();
var placeholder = format || "".concat(datePlaceholder, "\u00A0").concat(timePlaceholder);
var dateDivider = (function () {
var dividers = datePlaceholder.match(/[^0-9a-z]/i);
return dividers ? dividers[0] : null;
})();
var timeDivider = (function () {
var dividers = timePlaceholder.match(/[^0-9a-z]/i);
return dividers ? dividers[0] : null;
})();
var maxTime = (function () {
if (!maxDate) {
return undefined;
}
if (!isSameDate(maxDate, year, month, day)) {
return undefined;
}
return getHoursMinutesSeconds(maxDate || defaultMaxDate);
})();
var minTime = (function () {
if (!minDate) {
return undefined;
}
if (!isSameDate(minDate, year, month, day)) {
return undefined;
}
return getHoursMinutesSeconds(minDate || defaultMinDate);
})();
function onClick(event) {
if (event.target === event.currentTarget) {
// Wrapper was directly clicked
var firstInput = event.target.children[1];
focus(firstInput);
}
}
function onKeyDown(event) {
lastPressedKey.current = event.key;
switch (event.key) {
case 'ArrowLeft':
case 'ArrowRight':
case dateDivider:
case timeDivider: {
event.preventDefault();
var input = event.target;
var property = event.key === 'ArrowLeft' ? 'previousElementSibling' : 'nextElementSibling';
var nextInput = findInput(input, property);
focus(nextInput);
break;
}
default:
}
}
function onKeyUp(event) {
var key = event.key, input = event.target;
var isLastPressedKey = lastPressedKey.current === key;
if (!isLastPressedKey) {
return;
}
var isNumberKey = !isNaN(Number(key));
if (!isNumberKey) {
return;
}
var max = input.getAttribute('max');
if (!max) {
return;
}
var value = input.value;
/**
* Given 1, the smallest possible number the user could type by adding another digit is 10.
* 10 would be a valid value given max = 12, so we won't jump to the next input.
* However, given 2, smallers possible number would be 20, and thus keeping the focus in
* this field doesn't make sense.
*/
if (Number(value) * 10 > Number(max) || value.length >= max.length) {
var property = 'nextElementSibling';
var nextInput = findInput(input, property);
focus(nextInput);
}
}
/**
* Called after internal onChange. Checks input validity. If all fields are valid,
* calls props.onChange.
*/
function onChangeExternal() {
if (!onChangeProps) {
return;
}
function filterBoolean(value) {
return Boolean(value);
}
var formElements = [
amPmInput.current,
dayInput.current,
monthInput.current,
monthSelect.current,
yearInput.current,
hour12Input.current,
hour24Input.current,
minuteInput.current,
secondInput.current,
].filter(filterBoolean);
var formElementsWithoutSelect = formElements.slice(1);
var values = {};
formElements.forEach(function (formElement) {
values[formElement.name] =
formElement.type === 'number'
? 'valueAsNumber' in formElement
? formElement.valueAsNumber
: Number(formElement.value)
: formElement.value;
});
var isEveryValueEmpty = formElementsWithoutSelect.every(function (formElement) { return !formElement.value; });
if (isEveryValueEmpty) {
onChangeProps(null, false);
return;
}
var isEveryValueFilled = formElements.every(function (formElement) { return formElement.value; });
var isEveryValueValid = formElements.every(function (formElement) { return formElement.validity.valid; });
if (isEveryValueFilled && isEveryValueValid) {
var year_1 = Number(values.year || new Date().getFullYear());
var monthIndex = Number(values.month || 1) - 1;
var day_1 = Number(values.day || 1);
var hour_1 = Number(values.hour24 ||
(values.hour12 && values.amPm && convert12to24(values.hour12, values.amPm)) ||
0);
var minute_1 = Number(values.minute || 0);
var second_1 = Number(values.second || 0);
var proposedValue = new Date();
proposedValue.setFullYear(year_1, monthIndex, day_1);
proposedValue.setHours(hour_1, minute_1, second_1, 0);
onChangeProps(proposedValue, false);
return;
}
if (!onInvalidChange) {
return;
}
onInvalidChange();
}
/**
* Called when non-native date input is changed.
*/
function onChange(event) {
var _a = event.target, name = _a.name, value = _a.value;
switch (name) {
case 'amPm':
setAmPm(value);
break;
case 'year':
setYear(value);
break;
case 'month':
setMonth(value);
break;
case 'day':
setDay(value);
break;
case 'hour12':
setHour(value ? convert12to24(value, amPm || 'am').toString() : '');
break;
case 'hour24':
setHour(value);
break;
case 'minute':
setMinute(value);
break;
case 'second':
setSecond(value);
break;
}
onChangeExternal();
}
/**
* Called when native date input is changed.
*/
function onChangeNative(event) {
var value = event.target.value;
if (!onChangeProps) {
return;
}
var processedValue = (function () {
if (!value) {
return null;
}
var _a = value.split('T'), valueDate = _a[0], valueTime = _a[1];
var _b = valueDate.split('-'), yearString = _b[0], monthString = _b[1], dayString = _b[2];
var year = Number(yearString);
var monthIndex = Number(monthString) - 1 || 0;
var day = Number(dayString) || 1;
var _c = valueTime.split(':'), hourString = _c[0], minuteString = _c[1], secondString = _c[2];
var hour = Number(hourString) || 0;
var minute = Number(minuteString) || 0;
var second = Number(secondString) || 0;
var proposedValue = new Date();
proposedValue.setFullYear(year, monthIndex, day);
proposedValue.setHours(hour, minute, second, 0);
return proposedValue;
})();
onChangeProps(processedValue, false);
}
var commonInputProps = {
className: className,
disabled: disabled,
maxDate: maxDate || defaultMaxDate,
minDate: minDate || defaultMinDate,
onChange: onChange,
onKeyDown: onKeyDown,
onKeyUp: onKeyUp,
// This is only for showing validity when editing
required: Boolean(required || isWidgetOpen),
};
var commonTimeInputProps = {
maxTime: maxTime,
minTime: minTime,
};
function renderDay(currentMatch, index) {
if (currentMatch && currentMatch.length > 2) {
throw new Error("Unsupported token: ".concat(currentMatch));
}
var showLeadingZerosFromFormat = currentMatch && currentMatch.length === 2;
return (_jsx(DayInput, __assign({}, commonInputProps, { ariaLabel: dayAriaLabel,
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus: index === 0 && autoFocus, inputRef: dayInput, month: month, placeholder: dayPlaceholder, showLeadingZeros: showLeadingZerosFromFormat || showLeadingZeros, value: day, year: year }), "day"));
}
function renderMonth(currentMatch, index) {
if (currentMatch && currentMatch.length > 4) {
throw new Error("Unsupported token: ".concat(currentMatch));
}
if (currentMatch.length > 2) {
return (_jsx(MonthSelect, __assign({}, commonInputProps, { ariaLabel: monthAriaLabel,
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus: index === 0 && autoFocus, inputRef: monthSelect, locale: locale, placeholder: monthPlaceholder, short: currentMatch.length === 3, value: month, year: year }), "month"));
}
var showLeadingZerosFromFormat = currentMatch && currentMatch.length === 2;
return (_jsx(MonthInput, __assign({}, commonInputProps, { ariaLabel: monthAriaLabel,
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus: index === 0 && autoFocus, inputRef: monthInput, placeholder: monthPlaceholder, showLeadingZeros: showLeadingZerosFromFormat || showLeadingZeros, value: month, year: year }), "month"));
}
function renderYear(currentMatch, index) {
return (_jsx(YearInput, __assign({}, commonInputProps, { ariaLabel: yearAriaLabel,
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus: index === 0 && autoFocus, inputRef: yearInput, placeholder: yearPlaceholder, value: year, valueType: "day" }), "year"));
}
function renderHour12(currentMatch, index) {
if (currentMatch && currentMatch.length > 2) {
throw new Error("Unsupported token: ".concat(currentMatch));
}
var showLeadingZeros = currentMatch ? currentMatch.length === 2 : false;
return (_jsx(Hour12Input, __assign({}, commonInputProps, commonTimeInputProps, { amPm: amPm, ariaLabel: hourAriaLabel,
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus: index === 0 && autoFocus, inputRef: hour12Input, placeholder: hourPlaceholder, showLeadingZeros: showLeadingZeros, value: hour }), "hour12"));
}
function renderHour24(currentMatch, index) {
if (currentMatch && currentMatch.length > 2) {
throw new Error("Unsupported token: ".concat(currentMatch));
}
var showLeadingZeros = currentMatch ? currentMatch.length === 2 : false;
return (_jsx(Hour24Input, __assign({}, commonInputProps, commonTimeInputProps, { ariaLabel: hourAriaLabel,
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus: index === 0 && autoFocus, inputRef: hour24Input, placeholder: hourPlaceholder, showLeadingZeros: showLeadingZeros, value: hour }), "hour24"));
}
function renderHour(currentMatch, index) {
if (/h/.test(currentMatch)) {
return renderHour12(currentMatch, index);
}
return renderHour24(currentMatch, index);
}
function renderMinute(currentMatch, index) {
if (currentMatch && currentMatch.length > 2) {
throw new Error("Unsupported token: ".concat(currentMatch));
}
var showLeadingZeros = currentMatch ? currentMatch.length === 2 : false;
return (_jsx(MinuteInput, __assign({}, commonInputProps, commonTimeInputProps, { ariaLabel: minuteAriaLabel,
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus: index === 0 && autoFocus, hour: hour, inputRef: minuteInput, placeholder: minutePlaceholder, showLeadingZeros: showLeadingZeros, value: minute }), "minute"));
}
function renderSecond(currentMatch, index) {
if (currentMatch && currentMatch.length > 2) {
throw new Error("Unsupported token: ".concat(currentMatch));
}
var showLeadingZeros = currentMatch ? currentMatch.length === 2 : true;
return (_jsx(SecondInput, __assign({}, commonInputProps, commonTimeInputProps, { ariaLabel: secondAriaLabel,
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus: index === 0 && autoFocus, hour: hour, inputRef: secondInput, minute: minute, placeholder: secondPlaceholder, showLeadingZeros: showLeadingZeros, value: second }), "second"));
}
function renderAmPm(currentMatch, index) {
return (_jsx(AmPm, __assign({}, commonInputProps, commonTimeInputProps, { ariaLabel: amPmAriaLabel,
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus: index === 0 && autoFocus, inputRef: amPmInput, locale: locale, onChange: onChange, value: amPm }), "ampm"));
}
function renderCustomInputsInternal() {
var elementFunctions = {
d: renderDay,
M: renderMonth,
y: renderYear,
h: renderHour,
H: renderHour,
m: renderMinute,
s: renderSecond,
a: renderAmPm,
};
var allowMultipleInstances = typeof format !== 'undefined';
return renderCustomInputs(placeholder, elementFunctions, allowMultipleInstances);
}
function renderNativeInput() {
return (_jsx(NativeInput, { ariaLabel: nativeInputAriaLabel, disabled: disabled, maxDate: maxDate || defaultMaxDate, minDate: minDate || defaultMinDate, name: name, onChange: onChangeNative, required: required, value: value, valueType: valueType }, "datetime"));
}
return (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
_jsxs("div", { className: className, onClick: onClick, children: [renderNativeInput(), renderCustomInputsInternal()] }));
}