UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

635 lines (634 loc) 22.1 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 _SegmentedField = _interopRequireDefault(require("../input-masked/segmented-field/SegmentedField.js")); var _Button = _interopRequireDefault(require("../button/Button.js")); var _Input = _interopRequireWildcard(require("../input/Input.js")); var _componentHelper = require("../../shared/component-helper.js"); var _DatePickerCalc = require("./DatePickerCalc.js"); var _DatePickerContext = _interopRequireDefault(require("./DatePickerContext.js")); var _index = require("../../shared/index.js"); var _useInputDates = _interopRequireDefault(require("./hooks/useInputDates.js")); var _DateFormatUtils = require("../date-format/DateFormatUtils.js"); var _jsxRuntime = require("react/jsx-runtime"); 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 }; } const defaultProps = { separatorRegExp: /[-/ ]/g, statusState: 'error', open: false }; function DatePickerInput(externalProps) { const props = { ...defaultProps, ...externalProps }; const { maskOrder: defaultMaskOrder, maskPlaceholder: defaultMaskPlaceholder } = (0, _index.useTranslation)().DatePicker; const { isRange, maskOrder, separatorRegExp, id, title, submitAttributes, maskPlaceholder, onFocus, onBlur, onChange, onSubmit, selectedDateTitle, showInput, inputElement, lang, disabled, skeleton, open, size, status, statusState, statusProps, _omitInputShellClass, ...attributes } = props; const [focusState, setFocusState] = (0, _react.useState)('virgin'); 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 resolvedMaskOrder = maskOrder || (locale === 'en-US' ? 'mm/dd/yyyy' : defaultMaskOrder); const resolvedMaskPlaceholder = maskPlaceholder || (locale === 'en-US' ? 'mm/dd/yyyy' : defaultMaskPlaceholder); const hasHadValidDate = (0, _dateFns.isValid)(startDate) || (0, _dateFns.isValid)(endDate); const modeDate = (0, _react.useMemo)(() => ({ startDate, endDate }), [startDate, endDate]); const dateRefs = (0, _react.useRef)({ startDay: '', startMonth: '', startYear: '', endDay: '', endMonth: '', endYear: '' }); syncDateRefs(dateRefs, inputDates); const lastMaskValuesRef = (0, _react.useRef)({ start: { day: '', month: '', year: '' }, end: { day: '', month: '', year: '' } }); const temporaryDates = (0, _react.useRef)({ startDate: undefined, endDate: undefined }); const focusMode = (0, _react.useRef)(undefined); const orderedParts = (0, _react.useMemo)(() => { return resolvedMaskOrder.split(separatorRegExp).filter(Boolean).map(p => p.toLowerCase().startsWith('d') ? 'day' : p.toLowerCase().startsWith('m') ? 'month' : 'year'); }, [resolvedMaskOrder, separatorRegExp]); const delimiter = (0, _react.useMemo)(() => { var _resolvedMaskPlacehol; return (_resolvedMaskPlacehol = resolvedMaskPlaceholder.match(/[./-]/)) === null || _resolvedMaskPlacehol === void 0 ? void 0 : _resolvedMaskPlacehol[0]; }, [resolvedMaskPlaceholder]); const getValues = (0, _react.useCallback)(mode => { return { day: String(dateRefs.current[`${mode}Day`] || ''), month: String(dateRefs.current[`${mode}Month`] || ''), year: String(dateRefs.current[`${mode}Year`] || '') }; }, []); const copyHandler = (0, _react.useCallback)((event, mode) => { const date = mode === 'end' ? endDate : startDate; if ((0, _dateFns.isValid)(date)) { event.preventDefault(); const valueToCopy = getCopyValue(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; } if (!hasFullDateStructure(success)) { return; } try { const separators = ['.', '/']; const possibleFormats = [resolvedMaskOrder.replace(/\//g, '-')]; 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], strictDateFormat: true }); if (date) { break; } } if (!date) { return; } event.preventDefault(); const mode = focusMode.current === 'start' ? 'startDate' : 'endDate'; { updateDates({ [mode]: date }); const mm = String(date.getMonth() + 1).padStart(2, '0'); const dd = String(date.getDate()).padStart(2, '0'); const yyyy = String(date.getFullYear()).padStart(4, '0'); const m = focusMode.current === 'start' ? 'start' : 'end'; dateRefs.current[`${m}Day`] = dd; dateRefs.current[`${m}Month`] = mm; dateRefs.current[`${m}Year`] = yyyy; updateInputDates({ [`${m}Day`]: dd, [`${m}Month`]: mm, [`${m}Year`]: yyyy }); } } catch (error) { (0, _componentHelper.warn)(error); } }, [resolvedMaskOrder, updateDates, updateInputDates]); const buildInputs = (0, _react.useCallback)(mode => { const phChars = translation.placeholderCharacters || { day: 'd', month: 'm', year: 'y' }; const byPart = part => { const len = part === 'year' ? 4 : 2; const placeholder = String(phChars[part] || (part === 'year' ? 'y' : part[0])).repeat(len); const labelBase = translation[part]; const label = isRange ? `${translation[mode]} ${labelBase}` : labelBase; const cls = `dnb-date-picker__input dnb-date-picker__input--${part}`; const mask = new Array(len).fill(/[0-9]/); const spinButton = part === 'day' ? { min: 1, max: 31, getInitialValue: () => new Date().getDate() } : part === 'month' ? { min: 1, max: 12, getInitialValue: () => new Date().getMonth() + 1 } : { min: 0, max: 9999, getInitialValue: () => new Date().getFullYear() }; return { id: part, label, placeholder, mask, spinButton, className: cls, inputMode: 'numeric', onPaste: pasteHandler, onCopy: e => copyHandler(e, mode) }; }; return orderedParts.map(p => byPart(p)); }, [isRange, orderedParts, pasteHandler, copyHandler, translation]); 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 getInputPart = (mode, part, fallback) => { const refValue = dateRefs.current[`${mode}${part}`]; const inputValue = inputDates[`${mode}${part}`]; if (refValue === '') { if (inputValue !== null && typeof inputValue !== 'undefined' && inputValue !== '') { return ''; } return fallback; } if (refValue !== null && typeof refValue !== 'undefined') { return refValue; } if (inputValue !== null && typeof inputValue !== 'undefined') { return inputValue; } return fallback; }; const getDates = () => ['start', 'end'].reduce((acc, mode) => { acc[`${mode}Date`] = [getInputPart(mode, 'Year', 'yyyy'), getInputPart(mode, 'Month', 'mm'), getInputPart(mode, 'Day', 'dd')].join('-'); return acc; }, { startDate: undefined, endDate: undefined }); const { startDate, endDate } = getDates(); 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 { isValid, isValidStartDate, isValidEndDate, ...returnObject } = getReturnObject({ startDate: isStartDateValid ? parsedStartDate : null, endDate: isEndDateValid ? parsedEndDate : null, event, ...invalidDatesRef.current }); const typedDates = { ...(!isRange && isValid === false && { date: startDate }), ...(isRange && isValidStartDate === false && { startDate: startDate }), ...(isRange && isValidEndDate === false && { endDate: endDate }) }; onType === null || onType === void 0 || onType({ isValid, isValidStartDate, isValidEndDate, ...returnObject, ...typedDates }); }, [isRange, getReturnObject, onType, inputDates]); const setDate = (0, _react.useCallback)((event, mode, type) => { 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 [yStr, mStr, dStr] = [String(dateRefs.current[`${mode}Year`] || ''), String(dateRefs.current[`${mode}Month`] || ''), String(dateRefs.current[`${mode}Day`] || '')]; const hasAnyTypedValue = Boolean(yStr || mStr || dStr); const fullyTyped = /^\d{4}$/.test(yStr) && /^\d{2}$/.test(mStr) && /^\d{2}$/.test(dStr); const y = Number(yStr); const m = Number(mStr); const d = Number(dStr); const dt = new Date(y, m - 1, d); const isValidDate = fullyTyped && dt.getFullYear() === y && dt.getMonth() + 1 === m && dt.getDate() === d; isDateFullyFilledOutRef.current = fullyTyped; if (isValidDate) { invalidDatesRef.current = { ...invalidDatesRef.current, ...(mode === 'start' ? { invalidStartDate: null } : { invalidEndDate: null }) }; callOnChange({ [`${mode}Date`]: date, event }); } else { updateDates({ [`${mode}Date`]: null }); updateInputDates({ [`${mode}Day`]: dateRefs.current[`${mode}Day`] || null, [`${mode}Month`]: dateRefs.current[`${mode}Month`] || null, [`${mode}Year`]: dateRefs.current[`${mode}Year`] || null }); const dateString = hasAnyTypedValue ? `${y}-${m}-${d}` : null; 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 onMultiChange = (0, _react.useCallback)((mode, values) => { const prev = getValues(mode); let changed = null; if (values.day !== prev.day) { changed = 'day'; } if (values.month !== prev.month) { changed = 'month'; } if (values.year !== prev.year) { changed = 'year'; } if (!changed) { return; } dateRefs.current[`${mode}Day`] = values.day; dateRefs.current[`${mode}Month`] = values.month; dateRefs.current[`${mode}Year`] = values.year; lastMaskValuesRef.current[mode] = { day: values.day, month: values.month, year: values.year }; const Type = changed === 'day' ? 'Day' : changed === 'month' ? 'Month' : 'Year'; const synthetic = { persist: () => null, target: { value: values[changed] } }; setDate(synthetic, mode, Type); }, [setDate, getValues]); const onMultiChangeStart = (0, _react.useCallback)(values => { onMultiChange('start', values); }, [onMultiChange]); const onMultiChangeEnd = (0, _react.useCallback)(values => { onMultiChange('end', values); }, [onMultiChange]); const getDerivedDatesFromInputs = (0, _react.useCallback)(() => { const deriveDate = mode => { const year = String(dateRefs.current[`${mode}Year`] || ''); const month = String(dateRefs.current[`${mode}Month`] || ''); const day = String(dateRefs.current[`${mode}Day`] || ''); const hasAnyValue = Boolean(year || month || day); if (!hasAnyValue) { return mode === 'start' ? startDate : endDate; } const isComplete = /^\d{4}$/.test(year) && /^\d{2}$/.test(month) && /^\d{2}$/.test(day); if (!isComplete) { return null; } const parsedDate = new Date(Number(year), Number(month) - 1, Number(day)); const isValidDate = parsedDate.getFullYear() === Number(year) && parsedDate.getMonth() + 1 === Number(month) && parsedDate.getDate() === Number(day); return isValidDate ? parsedDate : null; }; return { startDate: deriveDate('start'), endDate: deriveDate('end') }; }, [endDate, startDate]); const scopeRef = (0, _react.useRef)(null); const renderInputElement = (0, _react.useCallback)(() => { return (0, _jsxRuntime.jsxs)("span", { id: `${id}-input`, className: "dnb-date-picker__input__wrapper", ref: scopeRef, children: [(0, _jsxRuntime.jsx)(_SegmentedField.default, { id: `${id}-start`, _omitInputShellClass: true, size: size, status: !open ? status : null, statusState: statusState, inputs: buildInputs('start'), values: getValues('start'), delimiter: delimiter, disabled: disabled || skeleton, onChange: onMultiChangeStart, scopeRef: scopeRef, onFocus: () => { focusMode.current = 'start'; setFocusState('focus'); onFocus === null || onFocus === void 0 || onFocus({ ...getReturnObject({ event: null, ...getDerivedDatesFromInputs(), ...invalidDatesRef.current }) }); }, onBlur: () => { focusMode.current = null; setFocusState('blur'); onBlur === null || onBlur === void 0 || onBlur({ ...getReturnObject({ event: null, ...getDerivedDatesFromInputs(), ...invalidDatesRef.current }) }); }, ...attributes }), isRange && (_span || (_span = (0, _jsxRuntime.jsx)("span", { className: "dnb-date-picker--separator", "aria-hidden": true, children: ' – ' }))), isRange && (0, _jsxRuntime.jsx)(_SegmentedField.default, { id: `${id}-end`, _omitInputShellClass: true, size: size, status: !open ? status : null, statusState: statusState, inputs: buildInputs('end'), values: getValues('end'), delimiter: delimiter, disabled: disabled || skeleton, onChange: onMultiChangeEnd, scopeRef: scopeRef, onFocus: () => { focusMode.current = 'end'; setFocusState('focus'); onFocus === null || onFocus === void 0 || onFocus({ ...getReturnObject({ event: null, ...getDerivedDatesFromInputs(), ...invalidDatesRef.current }) }); }, onBlur: () => { focusMode.current = null; setFocusState('blur'); onBlur === null || onBlur === void 0 || onBlur({ ...getReturnObject({ event: null, ...getDerivedDatesFromInputs(), ...invalidDatesRef.current }) }); }, ...attributes })] }); }, [id, size, buildInputs, getValues, delimiter, disabled, skeleton, open, status, statusState, attributes, isRange, onFocus, onBlur, getReturnObject, getDerivedDatesFromInputs, onMultiChangeStart, onMultiChangeEnd]); 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]); return (0, _jsxRuntime.jsxs)("fieldset", { className: "dnb-date-picker__fieldset", lang: lang, children: [label && (0, _jsxRuntime.jsx)("legend", { className: "dnb-sr-only", children: label }), (0, _jsxRuntime.jsx)(_Input.default, { id: `${id}__input`, inputState: disabled ? 'disabled' : focusState, inputElement: inputElement && typeof inputElement !== 'string' ? typeof inputElement === 'function' ? inputElement(props) : inputElement : renderInputElement, disabled: disabled || skeleton, skeleton: skeleton, size: size, status: !open ? status : null, statusState: statusState, ...statusProps, submitElement: (0, _jsxRuntime.jsx)(SubmitElement, { id: id, disabled: disabled, skeleton: skeleton, className: showInput ? 'dnb-button--input-button' : "", selected: open, "aria-label": ariaLabel, title: title, size: size, status: status, statusState: statusState, type: "button", icon: "calendar", variant: "secondary", onSubmit: onSubmit, onClick: onSubmit, ...submitAttributes, ...statusProps }), lang: lang, _omitInputShellClass: _omitInputShellClass, ...attributes })] }); } var _default = exports.default = DatePickerInput; function hasFullDateStructure(value) { const trimmed = String(value).trim(); if (!trimmed) { return false; } return /^(\d{1,4})[./-](\d{1,2})[./-](\d{1,4})$/.test(trimmed); } function getCopyValue(date, locale) { if (locale === 'en-US') { return new Intl.DateTimeFormat(locale, { month: '2-digit', day: '2-digit', year: 'numeric' }).format(date); } return (0, _DateFormatUtils.formatDate)(date, { locale }); } 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; } } } //# sourceMappingURL=DatePickerInput.js.map