UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

635 lines (634 loc) 26.8 kB
"use strict"; "use client"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; require("core-js/modules/es.string.replace.js"); require("core-js/modules/web.dom-collections.iterator.js"); var _react = _interopRequireWildcard(require("react")); var _isValid = _interopRequireDefault(require("date-fns/isValid")); var _parseISO = _interopRequireDefault(require("date-fns/parseISO")); var _classnames = _interopRequireDefault(require("classnames")); var _TextMask = _interopRequireDefault(require("../input-masked/TextMask")); var _Button = _interopRequireDefault(require("../button/Button")); var _Input = _interopRequireWildcard(require("../input/Input")); var _componentHelper = require("../../shared/component-helper"); var _helpers = require("../../shared/helpers"); var _DatePickerCalc = require("./DatePickerCalc"); var _DatePickerContext = _interopRequireDefault(require("./DatePickerContext")); var _shared = require("../../shared"); var _usePartialDates = _interopRequireDefault(require("./hooks/usePartialDates")); var _useInputDates = _interopRequireDefault(require("./hooks/useInputDates")); var _span; const _excluded = ["isRange", "maskOrder", "separatorRegExp", "id", "title", "submitAttributes", "maskPlaceholder", "onFocus", "onBlur", "onChange", "onSubmit", "selectedDateTitle", "showInput", "inputElement", "lang", "disabled", "skeleton", "opened", "size", "status", "statusState", "statusProps"], _excluded2 = ["is_valid", "is_valid_start_date", "is_valid_end_date"], _excluded3 = ["className", "value"]; function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } 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); } function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; } function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } const defaultProps = { maskOrder: 'dd/mm/yyyy', maskPlaceholder: 'dd/mm/åååå', separatorRegExp: /[-/ ]/g, statusState: 'error', opened: false }; function DatePickerInput(externalProps) { const props = _objectSpread(_objectSpread({}, defaultProps), externalProps); const { isRange, maskOrder, separatorRegExp, id, title, submitAttributes, maskPlaceholder, onFocus, onBlur, onChange, onSubmit, selectedDateTitle, showInput, inputElement, lang, disabled, skeleton, opened, size, status, statusState, statusProps } = props, attributes = _objectWithoutProperties(props, _excluded); 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, correctInvalidDate } } = (0, _react.useContext)(_DatePickerContext.default); const { inputDates, updateInputDates } = (0, _useInputDates.default)({ startDate, endDate }); const translation = (0, _shared.useTranslation)().DatePicker; const hasHadValidDate = (0, _isValid.default)(startDate) || (0, _isValid.default)(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; } acc.push(cur); if (separators.length > 0) { acc.push(separators.shift()); } return acc; }, []); }, [maskOrder, separatorRegExp]); const pasteHandler = (0, _react.useCallback)(async event => { if (!focusMode.current) { return; } const success = (event.clipboardData || typeof window !== 'undefined' && window['clipboardData']).getData('text'); if (!success) { return; } event.preventDefault(); try { const separators = ['.', '/']; const possibleFormats = ['yyyy-MM-dd']; possibleFormats.forEach(date => { separators.forEach(sep => { possibleFormats.push(date.replace(/-/g, sep)); }); }); possibleFormats.forEach(date => { possibleFormats.push(date.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)(state => { updateDates({ hoverDate: null }, dates => { if (isDateFullyFilledOutRef.current || hasHadValidDate) { const { startDate, endDate, event } = _objectSpread(_objectSpread({}, state), dates); callOnChangeHandler(_objectSpread({ startDate, endDate, event }, invalidDatesRef.current)); } }); }, [updateDates, callOnChangeHandler, hasHadValidDate]); const callOnChange = (0, _react.useCallback)(_ref => { let { startDate, endDate, event } = _ref; const state = {}; if (typeof startDate !== 'undefined' && (0, _isValid.default)(startDate)) { state['startDate'] = startDate; } if (!isRange) { endDate = startDate; } if (typeof endDate !== 'undefined' && (0, _isValid.default)(endDate)) { state['endDate'] = endDate; } updateDates(state, dates => { if (typeof startDate !== 'undefined' && (0, _isValid.default)(startDate) || typeof endDate !== 'undefined' && (0, _isValid.default)(endDate)) { callOnChangeHandler(_objectSpread(_objectSpread({ event }, dates), invalidDatesRef.current)); } }); }, [updateDates, callOnChangeHandler, isRange]); const callOnType = (0, _react.useCallback)(_ref2 => { let { event } = _ref2; 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(_objectSpread({ partialStartDate: startDate }, isRange && { partialEndDate: endDate })); const parsedStartDate = (0, _parseISO.default)(startDate); const parsedEndDate = (0, _parseISO.default)(endDate); const isStartDateValid = (0, _isValid.default)(parsedStartDate); const isEndDateValid = (0, _isValid.default)(parsedEndDate); const _getReturnObject = getReturnObject(_objectSpread(_objectSpread({ startDate: isStartDateValid ? parsedStartDate : null, endDate: isEndDateValid ? parsedEndDate : null, event }, partialDatesRef.current), invalidDatesRef.current)), { is_valid, is_valid_start_date, is_valid_end_date } = _getReturnObject, returnObject = _objectWithoutProperties(_getReturnObject, _excluded2); const typedDates = _objectSpread(_objectSpread(_objectSpread({}, !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 ? void 0 : onType(_objectSpread(_objectSpread({ 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, _isValid.default)(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 = _objectSpread(_objectSpread({}, invalidDatesRef.current), mode === 'start' ? { invalidStartDate: null } : { invalidEndDate: null }); callOnChange({ [`${mode}Date`]: date, event }); } else { updateDates({ [`${mode}Date`]: null }); updateInputDates({ [`${mode}${type}`]: value }); invalidDatesRef.current = _objectSpread(_objectSpread({}, 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 ? void 0 : onFocus(_objectSpread(_objectSpread({}, 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 ? void 0 : onBlur(_objectSpread(_objectSpread({}, event), getReturnObject(_objectSpread({ event }, partialDatesRef.current)))); }, [onBlur, getReturnObject, partialDatesRef]); const onKeyDownHandler = (0, _react.useCallback)(async event => { const keyCode = event.key; const target = event.target; if (correctInvalidDate && target.selectionStart !== target.selectionEnd) { 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 = /[0-9]/g.test(keyCode); const refListArray = refList.current; const index = refListArray.findIndex(_ref3 => { let { current } = _ref3; return 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}`](_objectSpread(_objectSpread({ 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 }); } } } }, [correctInvalidDate, 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(_objectSpread(_objectSpread({}, 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) => { 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 = _objectSpread(_objectSpread({}, element), {}, { onInput: onInputHandler, onKeyDown: onKeyDownHandler, onPaste: pasteHandler, onFocus: e => { focusMode.current = mode; onFocusHandler(e); }, onBlur: onBlurHandler, placeholderChar }); } const DateField = inputElement && _react.default.isValidElement(inputElement) ? inputElement.type : InputElement; const inputSizeClassName = size && `dnb-date-picker__input--${size}`; switch (state) { case 'd': refList.current.push(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': refList.current.push(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': refList.current.push(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, pasteHandler, onBlurHandler, onFocusHandler, 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) { let position = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; let options = arguments.length > 2 ? arguments[2] : undefined; 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(_ref4) { let { className, value } = _ref4, props = _objectWithoutProperties(_ref4, _excluded3); 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