UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

377 lines (376 loc) 12.3 kB
"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