UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

666 lines (665 loc) 24.8 kB
"use strict"; "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