@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
635 lines (634 loc) • 22.1 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 _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