@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
666 lines (665 loc) • 24.8 kB
JavaScript
;
"use client";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _push = _interopRequireDefault(require("core-js-pure/stable/instance/push.js"));
var _react = _interopRequireWildcard(require("react"));
var _dateFns = require("date-fns");
var _classnames = _interopRequireDefault(require("classnames"));
var _TextMask = _interopRequireDefault(require("../input-masked/TextMask.js"));
var _Button = _interopRequireDefault(require("../button/Button.js"));
var _Input = _interopRequireWildcard(require("../input/Input.js"));
var _componentHelper = require("../../shared/component-helper.js");
var _helpers = require("../../shared/helpers.js");
var _DatePickerCalc = require("./DatePickerCalc.js");
var _DatePickerContext = _interopRequireDefault(require("./DatePickerContext.js"));
var _index = require("../../shared/index.js");
var _usePartialDates = _interopRequireDefault(require("./hooks/usePartialDates.js"));
var _useInputDates = _interopRequireDefault(require("./hooks/useInputDates.js"));
var _DateFormatUtils = require("../date-format/DateFormatUtils.js");
var _span;
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
const defaultProps = {
separatorRegExp: /[-/ ]/g,
statusState: 'error',
opened: false
};
function DatePickerInput(externalProps) {
const props = {
...defaultProps,
...externalProps
};
const {
maskOrder: defaultMaskOrder,
maskPlaceholder: defaultMaskPlaceholder
} = (0, _index.useTranslation)().DatePicker;
const {
isRange,
maskOrder = defaultMaskOrder,
separatorRegExp,
id,
title,
submitAttributes,
maskPlaceholder = defaultMaskPlaceholder,
onFocus,
onBlur,
onChange,
onSubmit,
selectedDateTitle,
showInput,
inputElement,
lang,
disabled,
skeleton,
opened,
size,
status,
statusState,
statusProps,
...attributes
} = props;
const [focusState, setFocusState] = (0, _react.useState)('virgin');
const {
partialDatesRef,
setPartialDates
} = (0, _usePartialDates.default)();
const invalidDatesRef = (0, _react.useRef)({
invalidStartDate: null,
invalidEndDate: null
});
const isDateFullyFilledOutRef = (0, _react.useRef)(false);
const {
updateDates,
callOnChangeHandler,
getReturnObject,
startDate,
endDate,
props: {
onType,
label
}
} = (0, _react.useContext)(_DatePickerContext.default);
const {
inputDates,
updateInputDates
} = (0, _useInputDates.default)({
startDate,
endDate
});
const translation = (0, _index.useTranslation)().DatePicker;
const {
locale
} = (0, _react.useContext)(_index.Context);
const hasHadValidDate = (0, _dateFns.isValid)(startDate) || (0, _dateFns.isValid)(endDate);
const modeDate = (0, _react.useMemo)(() => ({
startDate,
endDate
}), [startDate, endDate]);
const inputRefs = (0, _react.useRef)({
startDayRef: {
current: undefined
},
startMonthRef: {
current: undefined
},
startYearRef: {
current: undefined
},
endDayRef: {
current: undefined
},
endMonthRef: {
current: undefined
},
endYearRef: {
current: undefined
}
});
const dateRefs = (0, _react.useRef)({
startDay: '',
startMonth: '',
startYear: '',
endDay: '',
endMonth: '',
endYear: ''
});
syncDateRefs(dateRefs, inputDates);
const temporaryDates = (0, _react.useRef)({
startDate: undefined,
endDate: undefined
});
const refList = (0, _react.useRef)();
const focusMode = (0, _react.useRef)();
const maskList = (0, _react.useMemo)(() => {
const separators = maskOrder.match(separatorRegExp);
return maskOrder.split(separatorRegExp).reduce((acc, cur) => {
if (!cur) {
return acc;
}
(0, _push.default)(acc).call(acc, cur);
if (separators.length > 0) {
(0, _push.default)(acc).call(acc, separators.shift());
}
return acc;
}, []);
}, [maskOrder, separatorRegExp]);
const copyHandler = (0, _react.useCallback)((event, mode) => {
const date = mode === 'end' ? endDate : startDate;
if ((0, _dateFns.isValid)(date)) {
event.preventDefault();
const valueToCopy = (0, _DateFormatUtils.formatDate)(date, {
locale
});
event.clipboardData.setData('text/plain', valueToCopy);
}
}, [endDate, locale, startDate]);
const pasteHandler = (0, _react.useCallback)(async event => {
if (!focusMode.current) {
return;
}
const success = (event.clipboardData || typeof window !== 'undefined' && window['clipboardData']).getData('text/plain');
if (!success) {
return;
}
event.preventDefault();
try {
const separators = ['.', '/'];
const possibleFormats = ['yyyy-MM-dd'];
const baseFormats = [...possibleFormats];
baseFormats.forEach(date => {
separators.forEach(sep => {
const format = date.replace(/-/g, sep);
(0, _push.default)(possibleFormats).call(possibleFormats, format);
(0, _push.default)(possibleFormats).call(possibleFormats, format.split('').reverse().join(''));
});
});
let date;
let index = 0;
for (index; index < possibleFormats.length; ++index) {
date = (0, _DatePickerCalc.convertStringToDate)(success, {
dateFormat: possibleFormats[index]
});
if (date) {
break;
}
}
const mode = focusMode.current === 'start' ? 'startDate' : 'endDate';
if (date) {
updateDates({
[mode]: date
});
}
} catch (error) {
(0, _componentHelper.warn)(error);
}
}, [updateDates]);
const callOnChangeAsInvalid = (0, _react.useCallback)(params => {
if (isDateFullyFilledOutRef.current || hasHadValidDate) {
const datesFromContext = {
startDate,
endDate
};
const {
startDate: derivedStartDate,
endDate: derivedEndDate,
event
} = {
...datesFromContext,
...params
};
callOnChangeHandler({
startDate: derivedStartDate,
endDate: derivedEndDate,
event,
...invalidDatesRef.current
});
}
}, [callOnChangeHandler, hasHadValidDate, startDate, endDate]);
const callOnChange = (0, _react.useCallback)(({
startDate,
endDate,
event
}) => {
const state = {};
if (typeof startDate !== 'undefined' && (0, _dateFns.isValid)(startDate)) {
state['startDate'] = startDate;
}
if (!isRange) {
endDate = startDate;
}
if (typeof endDate !== 'undefined' && (0, _dateFns.isValid)(endDate)) {
state['endDate'] = endDate;
}
updateDates(state, dates => {
if (typeof startDate !== 'undefined' && (0, _dateFns.isValid)(startDate) || typeof endDate !== 'undefined' && (0, _dateFns.isValid)(endDate)) {
callOnChangeHandler({
event,
...dates,
...invalidDatesRef.current
});
}
});
}, [updateDates, callOnChangeHandler, isRange]);
const callOnType = (0, _react.useCallback)(({
event
}) => {
const getDates = () => ['start', 'end'].reduce((acc, mode) => {
acc[`${mode}Date`] = [dateRefs.current[`${mode}Year`] || inputDates[`${mode}Year`] || 'yyyy', dateRefs.current[`${mode}Month`] || inputDates[`${mode}Month`] || 'mm', dateRefs.current[`${mode}Day`] || inputDates[`${mode}Day`] || 'dd'].join('-');
return acc;
}, {
startDate: undefined,
endDate: undefined
});
const {
startDate,
endDate
} = getDates();
setPartialDates({
partialStartDate: startDate,
...(isRange && {
partialEndDate: endDate
})
});
const parsedStartDate = (0, _dateFns.parseISO)(startDate);
const parsedEndDate = (0, _dateFns.parseISO)(endDate);
const isStartDateValid = (0, _dateFns.isValid)(parsedStartDate);
const isEndDateValid = (0, _dateFns.isValid)(parsedEndDate);
const {
is_valid,
is_valid_start_date,
is_valid_end_date,
...returnObject
} = getReturnObject({
startDate: isStartDateValid ? parsedStartDate : null,
endDate: isEndDateValid ? parsedEndDate : null,
event,
...partialDatesRef.current,
...invalidDatesRef.current
});
const typedDates = {
...(!isRange && is_valid === false && {
date: startDate
}),
...(isRange && is_valid_start_date === false && {
start_date: startDate
}),
...(isRange && is_valid_end_date === false && {
end_date: endDate
})
};
onType === null || onType === void 0 || onType({
is_valid,
is_valid_start_date,
is_valid_end_date,
...returnObject,
...typedDates
});
}, [setPartialDates, isRange, getReturnObject, partialDatesRef, onType, inputDates]);
const setDate = (0, _react.useCallback)((event, mode, type) => {
event.persist();
const value = event.target.value;
dateRefs.current[`${mode}${type}`] = value;
if (modeDate[`${mode}Date`]) {
temporaryDates.current[`${mode}Date`] = modeDate[`${mode}Date`];
}
const fallback = temporaryDates.current[`${mode}Date`];
const year = dateRefs.current[`${mode}Year`] || fallback && fallback.getFullYear();
const month = dateRefs.current[`${mode}Month`] || fallback && fallback.getMonth() + 1;
const day = dateRefs.current[`${mode}Day`] || fallback && fallback.getDate();
const date = new Date(parseFloat(String(year)), parseFloat(String(month)) - 1, parseFloat(String(day)));
const isValidDate = !/[^0-9]/.test(String(day)) && !/[^0-9]/.test(String(month)) && !/[^0-9]/.test(String(year)) && (0, _dateFns.isValid)(date) && date.getDate() === parseFloat(String(day)) && date.getMonth() + 1 === parseFloat(String(month)) && date.getFullYear() === parseFloat(String(year));
const dateString = `${year}-${month}-${day}`;
isDateFullyFilledOutRef.current = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(dateString);
if (isValidDate) {
invalidDatesRef.current = {
...invalidDatesRef.current,
...(mode === 'start' ? {
invalidStartDate: null
} : {
invalidEndDate: null
})
};
callOnChange({
[`${mode}Date`]: date,
event
});
} else {
updateDates({
[`${mode}Date`]: null
});
updateInputDates({
[`${mode}${type}`]: value
});
invalidDatesRef.current = {
...invalidDatesRef.current,
...(mode === 'start' ? {
invalidStartDate: dateString
} : {
invalidEndDate: dateString
})
};
callOnChangeAsInvalid({
[`${mode}Date`]: null,
event
});
}
callOnType({
event
});
}, [updateDates, callOnChange, callOnChangeAsInvalid, callOnType, modeDate, dateRefs, temporaryDates, updateInputDates]);
const dateSetters = (0, _react.useMemo)(() => ({
set_startDay: event => {
setDate(event, 'start', 'Day');
},
set_startMonth: event => {
setDate(event, 'start', 'Month');
},
set_startYear: event => {
setDate(event, 'start', 'Year');
},
set_endDay: event => {
setDate(event, 'end', 'Day');
},
set_endMonth: event => {
setDate(event, 'end', 'Month');
},
set_endYear: event => {
setDate(event, 'end', 'Year');
}
}), [setDate]);
const onFocusHandler = (0, _react.useCallback)(event => {
setFocusState('focus');
onFocus === null || onFocus === void 0 || onFocus({
...event,
...getReturnObject({
event
})
});
if (isNaN(parseFloat(event.target.value))) {
setCursorPosition(event.target);
}
}, [getReturnObject, onFocus]);
const onBlurHandler = (0, _react.useCallback)(event => {
focusMode.current = null;
setFocusState('blur');
onBlur === null || onBlur === void 0 || onBlur({
...event,
...getReturnObject({
event,
...partialDatesRef.current
})
});
}, [onBlur, getReturnObject, partialDatesRef]);
const onKeyDownHandler = (0, _react.useCallback)(async event => {
const keyCode = event.key;
const target = event.target;
const isNumberKey = /[0-9]/g.test(keyCode);
if (target.selectionStart !== target.selectionEnd && !isNumberKey) {
setCursorPosition(target);
}
const size = parseFloat(target.getAttribute('size'));
const firstSelectionStart = target.selectionStart;
const firstSelectionEnd = target.selectionEnd;
await wait(_helpers.IS_IOS ? 10 : 1);
const secondSelectionStart = target.selectionStart;
const isValid = isNumberKey;
const refListArray = refList.current;
const index = refListArray.findIndex(({
current
}) => current === target);
const isLastChar = secondSelectionStart === size;
const isFirstChar = firstSelectionStart === size;
const isMovingForward = keyCode !== 'ArrowLeft' && keyCode !== 'Backspace' && isValid && isLastChar;
const isExplicitForward = (keyCode === 'ArrowRight' || keyCode === 'Enter') && isFirstChar;
const hasNextField = index < refListArray.length - 1;
if (hasNextField && (isMovingForward || isExplicitForward)) {
var _refListArray;
if (!refListArray[index + 1].current) {
return;
}
const nextSibling = (_refListArray = refListArray[index + 1]) === null || _refListArray === void 0 ? void 0 : _refListArray.current;
if (nextSibling) {
setCursorPosition(nextSibling, 0, {
withoutDelay: true
});
}
if (parseFloat(keyCode) <= 9 && firstSelectionStart === target.size) {
const name = (0, _componentHelper.toCapitalized)(nextSibling.getAttribute('class').match(/__input--(day|month|year)($|\s)/)[1]);
const mode = nextSibling.getAttribute('id').match(/-(start|end)-(day|month|year)/)[1];
dateSetters[`set_${mode}${name}`]({
persist: () => null,
...event,
target: {
value: keyCode + nextSibling.value.slice(1)
}
});
setCursorPosition(nextSibling, 1);
}
} else if (index > 0 && firstSelectionStart === firstSelectionEnd) {
const isMovingBackward = keyCode === 'ArrowLeft' && firstSelectionStart === 0;
const isPressingBackspace = keyCode === 'Backspace' && firstSelectionStart <= 1;
if (isMovingBackward || isPressingBackspace) {
var _refListArray2;
const prevSibling = (_refListArray2 = refListArray[index - 1]) === null || _refListArray2 === void 0 ? void 0 : _refListArray2.current;
if (prevSibling) {
const endPos = prevSibling.value.length;
setCursorPosition(prevSibling, endPos, {
withoutDelay: true
});
}
}
}
}, [dateSetters]);
const onInputHandler = (0, _react.useCallback)(event => {
const target = event.currentTarget;
if (_helpers.IS_ANDROID && event.nativeEvent.inputType === 'deleteContentBackward' && target.selectionStart === 0 && target.selectionEnd === 0) {
onKeyDownHandler({
...event,
key: 'Backspace'
});
}
}, [onKeyDownHandler]);
const getPlaceholderChar = (0, _react.useCallback)(value => {
const index = maskOrder.indexOf(value);
return maskPlaceholder[index];
}, [maskOrder, maskPlaceholder]);
const generateDateList = (0, _react.useCallback)((element, mode) => {
return maskList.map((value, i) => {
var _context, _context2, _context3;
const state = value.slice(0, 1);
const placeholderChar = getPlaceholderChar(value);
const {
day,
month,
year
} = translation;
const isRangeLabel = isRange ? `${translation[mode]} ` : '';
if (!separatorRegExp.test(value)) {
if (!inputElement) {
element = {
...element,
onInput: onInputHandler,
onKeyDown: onKeyDownHandler,
onFocus: e => {
focusMode.current = mode;
onFocusHandler(e);
},
onBlur: onBlurHandler,
onPaste: pasteHandler,
onCopy: event => {
copyHandler(event, mode);
},
placeholderChar
};
}
const DateField = inputElement && _react.default.isValidElement(inputElement) ? inputElement.type : InputElement;
const inputSizeClassName = size && `dnb-date-picker__input--${size}`;
switch (state) {
case 'd':
(0, _push.default)(_context = refList.current).call(_context, inputRefs.current[`${mode}DayRef`]);
return _react.default.createElement(_react.default.Fragment, {
key: 'dd' + i
}, _react.default.createElement(DateField, _extends({}, element, {
id: `${id}-${mode}-day`,
key: 'di' + i,
className: (0, _classnames.default)("dnb-date-picker__input dnb-date-picker__input--day", element.className, inputSizeClassName),
size: 2,
mask: [/[0-9]/, /[0-9]/],
inputRef: inputRefs.current[`${mode}DayRef`],
onChange: dateSetters[`set_${mode}Day`],
value: inputDates[`${mode}Day`] || '',
"aria-labelledby": `${id}-${mode}-day-label`
})), _react.default.createElement("label", {
key: 'dl' + i,
hidden: true,
id: `${id}-${mode}-day-label`,
htmlFor: `${id}-${mode}-day`
}, isRangeLabel + day));
case 'm':
(0, _push.default)(_context2 = refList.current).call(_context2, inputRefs.current[`${mode}MonthRef`]);
return _react.default.createElement(_react.default.Fragment, {
key: 'mm' + i
}, _react.default.createElement(DateField, _extends({}, element, {
id: `${id}-${mode}-month`,
key: 'mi' + i,
className: (0, _classnames.default)("dnb-date-picker__input dnb-date-picker__input--month", element.className, inputSizeClassName),
size: 2,
mask: [/[0-9]/, /[0-9]/],
inputRef: inputRefs.current[`${mode}MonthRef`],
onChange: dateSetters[`set_${mode}Month`],
value: inputDates[`${mode}Month`] || '',
"aria-labelledby": `${id}-${mode}-month-label`
})), _react.default.createElement("label", {
key: 'ml' + i,
hidden: true,
id: `${id}-${mode}-month-label`,
htmlFor: `${id}-${mode}-month`
}, isRangeLabel + month));
case 'y':
(0, _push.default)(_context3 = refList.current).call(_context3, inputRefs.current[`${mode}YearRef`]);
return _react.default.createElement(_react.default.Fragment, {
key: 'yy' + i
}, _react.default.createElement(DateField, _extends({}, element, {
id: `${id}-${mode}-year`,
key: 'yi' + i,
className: (0, _classnames.default)("dnb-date-picker__input dnb-date-picker__input--year", element.className, inputSizeClassName),
size: 4,
mask: [/[0-9]/, /[0-9]/, /[0-9]/, /[0-9]/],
inputRef: inputRefs.current[`${mode}YearRef`],
onChange: dateSetters[`set_${mode}Year`],
value: inputDates[`${mode}Year`] || '',
"aria-labelledby": `${id}-${mode}-year-label`
})), _react.default.createElement("label", {
key: 'yl' + i,
hidden: true,
id: `${id}-${mode}-year-label`,
htmlFor: `${id}-${mode}-year`
}, isRangeLabel + year));
}
}
return _react.default.createElement("span", {
key: 's' + i,
className: "dnb-date-picker--separator",
"aria-hidden": true
}, placeholderChar);
});
}, [maskList, getPlaceholderChar, translation, isRange, separatorRegExp, inputElement, size, onInputHandler, onKeyDownHandler, onBlurHandler, pasteHandler, onFocusHandler, copyHandler, id, dateSetters, inputDates]);
const renderInputElement = (0, _react.useCallback)(element => {
refList.current = [];
const startDateList = generateDateList(element, 'start');
const endDateList = generateDateList(element, 'end');
return _react.default.createElement("span", {
id: `${id}-input`,
className: "dnb-date-picker__input__wrapper"
}, startDateList, isRange && (_span || (_span = _react.default.createElement("span", {
className: "dnb-date-picker--separator",
"aria-hidden": true
}, ' – '))), isRange && endDateList);
}, [id, isRange, generateDateList]);
const ariaLabel = (0, _react.useMemo)(() => selectedDateTitle ? `${selectedDateTitle}, ${translation.openPickerText}` : translation.openPickerText, [selectedDateTitle, translation]);
(0, _componentHelper.validateDOMAttributes)(props, attributes);
(0, _componentHelper.validateDOMAttributes)(null, submitAttributes);
const SubmitElement = (0, _react.useMemo)(() => showInput ? _Input.SubmitButton : _Button.default, [showInput]);
if (!showInput) {
submitAttributes.innerRef = submitAttributes.ref;
submitAttributes.ref = null;
}
return _react.default.createElement("fieldset", {
className: "dnb-date-picker__fieldset",
lang: lang
}, label && _react.default.createElement("legend", {
className: "dnb-sr-only"
}, label), _react.default.createElement(_Input.default, _extends({
id: `${id}__input`,
input_state: disabled ? 'disabled' : focusState,
input_element: inputElement && typeof inputElement !== 'string' ? typeof inputElement === 'function' ? inputElement(props) : inputElement : renderInputElement,
disabled: disabled || skeleton,
skeleton: skeleton,
size: size,
status: !opened ? status : null,
status_state: statusState
}, statusProps, {
submit_element: _react.default.createElement(SubmitElement, _extends({
id: id,
disabled: disabled,
skeleton: skeleton,
className: (0, _classnames.default)(showInput && 'dnb-button--input-button', opened && 'dnb-button--active'),
"aria-label": ariaLabel,
title: title,
size: size,
status: status,
status_state: statusState,
type: "button",
icon: "calendar",
variant: "secondary",
on_submit: onSubmit,
on_click: onSubmit
}, submitAttributes, statusProps)),
lang: lang
}, attributes)));
}
var _default = exports.default = DatePickerInput;
function setCursorPosition(target, position = 0, options) {
target.focus();
const select = () => {
target.setSelectionRange(position, position);
};
if (!(options !== null && options !== void 0 && options.withoutDelay) && process.env.NODE_ENV !== 'test') {
setTimeout(select, 0);
} else {
select();
}
}
function InputElement({
className,
value,
...props
}) {
return _react.default.createElement(_TextMask.default, _extends({
guide: true,
inputMode: "numeric",
showMask: true,
keepCharPositions: false,
autoComplete: "off",
autoCapitalize: "none",
spellCheck: false,
autoCorrect: "off",
className: (0, _classnames.default)(className, /\d+/.test(String(value)) && 'dnb-date-picker__input--highlight'),
value: value
}, props));
}
function syncDateRefs(dateRefs, inputDates) {
for (const date in dateRefs.current) {
const dateRefValue = dateRefs.current[date];
const inputDateValue = inputDates[date];
if (dateRefValue !== inputDateValue) {
dateRefs.current[date] = inputDateValue;
}
}
}
const wait = duration => new Promise(r => setTimeout(r, duration));
//# sourceMappingURL=DatePickerInput.js.map