@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
377 lines (376 loc) • 12.3 kB
JavaScript
"use client";
import _extends from "@babel/runtime-corejs3/helpers/esm/extends";
import React, { useCallback, useContext, useEffect, useMemo, useReducer, useRef } from 'react';
import StringField from "../String/index.js";
import CompositionField from "../Composition/index.js";
import SelectionField from "../Selection/index.js";
import SharedContext from "../../../../shared/Context.js";
import { parseISO, isValid, isAfter } from 'date-fns';
import useTranslation from "../../hooks/useTranslation.js";
import { formatDate } from "../../../../components/date-format/DateFormatUtils.js";
import { useFieldProps } from "../../hooks/index.js";
import { useIterateItemNo } from "../../Iterate/ItemNo/useIterateItemNo.js";
export const DEFAULT_DATE_FORMAT = 'yyyy-MM-dd';
function DateOfBirth(props) {
const [, forceUpdate] = useReducer(() => ({}), {});
const {
errorDateOfBirth,
errorDateOfBirthFuture,
errorRequired,
label,
dayLabel,
monthLabel,
yearLabel,
dayPlaceholder,
monthPlaceholder,
yearPlaceholder
} = useTranslation().DateOfBirth;
const {
locale
} = useContext(SharedContext);
const {
dateFormat = DEFAULT_DATE_FORMAT,
labelSuffix,
required
} = props;
const dayRef = useRef(props === null || props === void 0 ? void 0 : props.emptyValue);
const monthRef = useRef(props === null || props === void 0 ? void 0 : props.emptyValue);
const yearRef = useRef(props === null || props === void 0 ? void 0 : props.emptyValue);
const errorMessages = useMemo(() => {
return {
'Field.errorRequired': errorRequired,
'Field.errorPattern': errorDateOfBirth,
...props.errorMessages
};
}, [errorDateOfBirth, errorRequired, props.errorMessages]);
const provideAdditionalArgs = useCallback(value => {
const [year, month, day] = splitValue(value, dateFormat);
if (year && month && day) {
return {
year,
month,
day
};
}
}, [dateFormat]);
const dateOfBirthValidator = useCallback(value => {
const [year, month, day] = splitValue(value, dateFormat);
if (year && month && day) {
const isoValue = `${year}-${month}-${day}`;
const dateValue = parseISO(isoValue);
if (!isValid(dateValue)) {
return Error(errorDateOfBirth);
}
if (isAfter(dateValue, new Date())) {
return Error(errorDateOfBirthFuture);
}
}
}, [errorDateOfBirth, errorDateOfBirthFuture, dateFormat]);
const {
onBlurValidator: propOnBlurValidator,
onChangeValidator,
value: propValue,
space,
...otherProps
} = props;
const onBlurValidator = useMemo(() => {
if (propOnBlurValidator === false) {
return undefined;
}
if (typeof propOnBlurValidator === 'function') {
return (value, args) => {
const coreResult = dateOfBirthValidator(value);
if (coreResult instanceof Error) {
return coreResult;
}
return propOnBlurValidator(value, args);
};
}
return dateOfBirthValidator;
}, [propOnBlurValidator, dateOfBirthValidator]);
const preparedProps = useMemo(() => ({
...otherProps,
value: propValue,
errorMessages,
onBlurValidator,
onChangeValidator,
exportValidators: {
dateOfBirthValidator
},
provideAdditionalArgs
}), [otherProps, propValue, errorMessages, onBlurValidator, onChangeValidator, dateOfBirthValidator, provideAdditionalArgs]);
const {
emptyValue,
label: labelProp,
width = 'large',
help,
labelSrOnly,
labelSize,
labelDescription,
labelDescriptionInline,
error,
disabled,
htmlAttributes,
handleChange,
onDayChange,
onMonthChange,
onYearChange,
setHasFocus,
value: fieldValue
} = useFieldProps(preparedProps);
const labelWithItemNo = useIterateItemNo({
label: labelProp !== null && labelProp !== void 0 ? labelProp : label,
labelSuffix,
required
});
const prepareEventValues = useCallback(({
day = dayRef.current || emptyValue,
month = monthRef.current || emptyValue,
year = yearRef.current || emptyValue
} = {}) => {
return {
year,
month,
day
};
}, [emptyValue]);
const callOnChange = useCallback(data => {
const eventValues = prepareEventValues(data);
handleChange(joinValue([eventValues.year, eventValues.month, eventValues.day], dateFormat), eventValues);
}, [prepareEventValues, handleChange, dateFormat]);
const callOnBlurOrFocus = useCallback(hasFocus => {
setHasFocus(hasFocus, undefined, prepareEventValues());
}, [prepareEventValues, setHasFocus]);
useEffect(() => {
if (fieldValue) {
const [year, month, day] = splitValue(fieldValue, dateFormat);
const currentValues = joinValue([yearRef.current, monthRef.current, dayRef.current], dateFormat);
const shouldUpdate = !dayRef.current && !monthRef.current && !yearRef.current || fieldValue !== currentValues;
if (shouldUpdate) {
dayRef.current = day;
monthRef.current = month;
yearRef.current = year;
forceUpdate();
}
}
}, [fieldValue, dateFormat]);
const handleDayChange = useCallback(value => {
const day = dayRef.current = value || emptyValue;
forceUpdate();
callOnChange({
day,
month: monthRef.current,
year: yearRef.current
});
onDayChange === null || onDayChange === void 0 || onDayChange(day);
}, [emptyValue, callOnChange, onDayChange]);
const handleMonthChange = useCallback(value => {
const month = monthRef.current = value || emptyValue;
forceUpdate();
callOnChange({
day: dayRef.current,
month,
year: yearRef.current
});
onMonthChange === null || onMonthChange === void 0 || onMonthChange(month);
}, [emptyValue, callOnChange, onMonthChange]);
const handleYearChange = useCallback(value => {
const year = yearRef.current = value || emptyValue;
forceUpdate();
callOnChange({
day: dayRef.current,
month: monthRef.current,
year
});
onYearChange === null || onYearChange === void 0 || onYearChange(year);
}, [emptyValue, callOnChange, onYearChange]);
const normalizeDay = useCallback(value => {
if (!value) {
return value;
}
const trimmed = value.trim();
if (/^[1-9]$/.test(trimmed)) {
return trimmed.padStart(2, '0');
}
return trimmed;
}, []);
const normalizeYear = useCallback(value => {
if (!value) {
return value;
}
const trimmed = value.trim();
if (/^\d{1,2}$/.test(trimmed)) {
const padded = trimmed.padStart(2, '0');
const currentYear = new Date().getFullYear();
const currentCentury = Math.floor(currentYear / 100) * 100;
let normalized = currentCentury + parseInt(padded, 10);
if (normalized > currentYear) {
normalized -= 100;
}
return String(normalized);
}
return trimmed;
}, []);
const handleOnBlur = useCallback(() => {
callOnBlurOrFocus(false);
}, [callOnBlurOrFocus]);
const handleDayBlur = useCallback(() => {
const normalized = normalizeDay(dayRef.current);
if (normalized && normalized !== dayRef.current) {
handleDayChange(normalized);
}
handleOnBlur();
}, [handleDayChange, handleOnBlur, normalizeDay]);
const handleYearBlur = useCallback(() => {
const normalized = normalizeYear(yearRef.current);
if (normalized && normalized !== yearRef.current) {
handleYearChange(normalized);
}
handleOnBlur();
}, [handleOnBlur, handleYearChange, normalizeYear]);
const handleOnFocus = useCallback(() => {
callOnBlurOrFocus(true);
}, [callOnBlurOrFocus]);
const compositionFieldProps = {
className: 'dnb-forms-field-date-of-birth',
error,
label: labelWithItemNo,
labelSrOnly,
labelSize,
labelDescription,
labelDescriptionInline,
space
};
const months = useMemo(() => {
return [...Array(12)].map((_, i) => {
const nr = String(i + 1);
const value = nr.padStart(2, '0');
const title = capitalizeFirstLetter(formatDate(new Date(0, i, 1), {
locale,
options: {
month: 'long'
}
}));
return {
value,
title,
search_content: [title, nr, value]
};
});
}, [locale]);
const onBlurAutocomplete = useCallback(({
value
}) => {
const nr = parseFloat(value);
if (!isNaN(nr)) {
var _months$find;
const monthValue = (_months$find = months.find(m => parseFloat(m.value) === nr)) === null || _months$find === void 0 ? void 0 : _months$find.value;
const month = monthValue || emptyValue;
monthRef.current = month;
forceUpdate();
callOnChange({
month
});
} else {
var _months$find2;
const monthValue = (_months$find2 = months.find(m => m.title === value)) === null || _months$find2 === void 0 ? void 0 : _months$find2.value;
if (monthValue) {
monthRef.current = monthValue;
forceUpdate();
callOnChange({
month: monthValue
});
}
}
}, [callOnChange, emptyValue, months]);
return React.createElement(CompositionField, _extends({
width: width,
help: help
}, compositionFieldProps), React.createElement(StringField, {
value: dayRef.current,
autoComplete: "bday-day",
labelDescription: dayLabel,
labelSize: labelSize,
width: "3.5rem",
inputMode: "numeric",
mask: [/[0-9]/, /[0-9]/],
placeholder: dayPlaceholder,
onChange: handleDayChange,
onFocus: handleOnFocus,
onBlur: handleDayBlur,
disabled: disabled,
htmlAttributes: htmlAttributes
}), React.createElement(SelectionField, {
className: "dnb-forms-field-date-of-birth__month",
value: monthRef.current,
variant: "autocomplete",
labelDescription: monthLabel,
labelSize: labelSize,
width: "stretch",
placeholder: "",
autocompleteProps: {
openOnFocus: true,
inputIcon: '',
placeholder: monthPlaceholder,
autoComplete: 'bday-month',
independentWidth: true,
disableReorder: true,
onBlur: onBlurAutocomplete
},
data: months,
onChange: handleMonthChange,
onFocus: handleOnFocus,
onBlur: handleOnBlur,
disabled: disabled,
htmlAttributes: htmlAttributes
}), React.createElement(StringField, {
value: yearRef.current,
autoComplete: "bday-year",
labelDescription: yearLabel,
width: "stretch",
inputMode: "numeric",
mask: [/[0-9]/, /[0-9]/, /[0-9]/, /[0-9]/],
placeholder: yearPlaceholder,
onChange: handleYearChange,
onFocus: handleOnFocus,
onBlur: handleYearBlur,
disabled: disabled,
htmlAttributes: htmlAttributes
}));
}
DateOfBirth._supportsSpacingProps = undefined;
export default DateOfBirth;
function capitalizeFirstLetter(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
}
function joinValue(array, dateFormat = DEFAULT_DATE_FORMAT) {
const [year, month, day] = array;
if (!year || !month || !day) {
return undefined;
}
return dateFormat.replace('yyyy', year).replace('MM', month).replace('dd', day);
}
function splitValue(value, dateFormat = DEFAULT_DATE_FORMAT) {
if (typeof value !== 'string' || !value) {
return [undefined, undefined, undefined];
}
const formatPattern = dateFormat.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/yyyy/g, '(\\d{4})').replace(/MM/g, '(\\d{2})').replace(/dd/g, '(\\d{2})');
const regex = new RegExp(`^${formatPattern}$`);
const match = value.match(regex);
if (!match) {
return [undefined, undefined, undefined];
}
const yearIndex = dateFormat.indexOf('yyyy');
const monthIndex = dateFormat.indexOf('MM');
const dayIndex = dateFormat.indexOf('dd');
const sortedIndices = [yearIndex, monthIndex, dayIndex].sort((a, b) => a - b);
const result = sortedIndices.map((originalIndex, sortedPosition) => {
const matchGroupIndex = sortedPosition + 1;
return match[matchGroupIndex];
});
const year = result[sortedIndices.indexOf(yearIndex)];
const month = result[sortedIndices.indexOf(monthIndex)];
const day = result[sortedIndices.indexOf(dayIndex)];
return [year, month, day];
}
//# sourceMappingURL=DateOfBirth.js.map