UNPKG

@kubit-ui-web/react-components

Version:

Kubit React Components is a customizable, accessible library of React web components, designed to enhance your application's user experience

360 lines 16.3 kB
import { useEffect, useMemo, useRef, useState, } from 'react'; import { useInput } from '../../../hooks/useInput/useInput'; import { useUtilsProvider } from '../../../provider/utils/provider'; import { formatDateToUTC } from '../../../utils/date/formatDate'; import { formatDateToNative } from '../../../utils/syntheticComponents/syntheticDate/helper/formatDateToNative'; import { syntheticDate } from '../../../utils/syntheticComponents/syntheticDate/syntheticDate'; import { useInternalValidations } from '../../input/hooks/useInternalValidations'; import { ERROR_EXECUTION } from '../../input/types/input'; import { InternalErrorType } from '../../input/types/internalErrors'; import { verifyDate } from '../utils/verifyDate'; export const useInputDate = ({ ref, errorExecution, internalErrorExecution, keyValidation, max, min, maxLength, minLength, maxDate = new Date(), mask, maskType, disabled, error, currentValue, today = '', type, truncate, informationAssociated, disabledCopyAndPaste, onWrapperBlur, onBlur, onChange, onFocus, onKeyDown, onError, onInternalErrors, onPaste, ...props }) => { // Ghost prototype const { setDate } = useMemo(() => { if (typeof window !== 'undefined' && typeof document !== 'undefined') { return syntheticDate(props.name); } return { setDate: () => null }; }, []); // Auxiliar methods const buildRangeDates = (date) => { const orderDates = date.sort((a, b) => new Date(a).getTime() - new Date(b).getTime()); return { startDate: orderDates[0], endDate: orderDates[1], }; }; const getDate = (date) => { if (date) { const formatedDate = formatDate(date, props.format); return formatedDate; } return ''; }; const orderStringDates = (date) => { const isValidDate = verifyDate(props.format, date, props.minDate, maxDate, props.dateSeparator, props.hasRange, today); const datesFormatted = [ transformDate(date.split(' ')[0], props.format), transformDate(date.split(' ')[1], props.format), ]; return isValidDate ? [...datesFormatted].sort((a, b) => new Date(a).getTime() - new Date(b).getTime()) : date; }; // used when selecting date in calendar or transforming dates in internal validation // updates date and dateFormatted states // set the showcalendar state to false const handleChangeDate = (newDate, hasError = false) => { if (!hasError) { if (props.hasRange && newDate.length === 2) { props.onSelectedDateChange?.(buildRangeDates([newDate[0], newDate[1]])); setDateFormatted(newDate); if (newDate[1]) { setCalendarOpen(false); } } else { props.onSelectedDateChange?.(newDate[0]); setDateFormatted([newDate[0]]); setCalendarOpen(false); } props.onCalendarOpen?.(false); } onError?.(hasError); }; // validate formatted dates when date range const handleChangeInternalValidateDateRange = (dateRange) => { const isValidDateRange = verifyDate(props.format, dateRange, props.minDate, maxDate, props.dateSeparator, true, today); // Regex to split dates with dateSeparator ("to") const regexSplit = /[\sA-Za-z]+/; const dateRangeFormatted = dateRange.split(regexSplit); if (dateRangeFormatted && dateRangeFormatted?.length >= 2) { if (isValidDateRange) { removeInternalError(InternalErrorType.INVALID_DATE_RANGE); } else { addInternalError(InternalErrorType.INVALID_DATE_RANGE); } } handleChangeDate([ transformDate(dateRangeFormatted[0], props.format), transformDate(dateRangeFormatted[1], props.format), ], !isValidDateRange); }; // validate formatted dates when single date const handleChangeInternalValidateSingleDate = (date) => { const isValidDate = verifyDate(props.format, date, props.minDate, maxDate, props.dateSeparator, props.hasRange, today); if (isValidDate) { removeInternalError(InternalErrorType.INVALID_DATE); } else { addInternalError(InternalErrorType.INVALID_DATE); } handleChangeDate([transformDate(date, props.format)], !isValidDate); }; const createDateFormatted = (initialDate, initialSecondDate) => { if (initialDate) { if (initialSecondDate) { return [initialDate, initialSecondDate]; } return [initialDate]; } return []; }; // states, constants and refs const [calendarOpen, setCalendarOpen] = useState(false); const [dateFormatted, setDateFormatted] = useState(createDateFormatted(props.initialDate, props.initialSecondDate)); const firstRender = useRef(true); const { transformDate, formatDate } = useUtilsProvider(); const { internalErrors, addInternalError, removeInternalError } = useInternalValidations(type, undefined, onInternalErrors); // Methods // Popover control calendar const handleOpenCalendar = (open) => { setCalendarOpen(open); props.onCalendarOpen?.(open); }; const handleClickInput = event => { if (calendarOpen) { setCalendarOpen(false); props.onCalendarOpen?.(false); } props.onClick?.(event); }; const onBlurDate = event => { if (event.currentTarget.contains(event.relatedTarget)) { return; } const inputValue = inputRef?.current?.value ?? ''; if (errorExecution === ERROR_EXECUTION.ON_BLUR) { if (props.hasRange) { handleChangeInternalValidateDateRange(inputValue); } else { handleChangeInternalValidateSingleDate(inputValue); } } const hasError = internalErrors.length > 0; props.onDateError?.(hasError); let adaptEvent; const dateValue = inputValue; if (props.hasRange) { const hasSeparator = props.dateSeparator && dateValue.includes(props.dateSeparator); const secondDateExists = dateValue.length > props.format.length; const dates = hasSeparator ? dateValue.split(props.dateSeparator) : dateValue.split(' '); const firstDate = formatDateToNative(dates[0].trim(), props.format, true); const secondDate = secondDateExists ? formatDateToNative(dates[1].trim(), props.format, true) : ''; const firstValueAsNumber = firstDate.length && formatDateToUTC(firstDate).getTime(); const secondValueAsNumber = secondDate.length && formatDateToUTC(secondDate).getTime(); const firstValueAsDate = firstDate.length && formatDateToUTC(firstDate); const secondValueAsDate = secondDate.length && formatDateToUTC(secondDate); adaptEvent = { target: { value: `${firstDate} ${secondDate}`, valueAsNumber: `${firstValueAsNumber} ${secondValueAsNumber}`.trim(), valueAsDate: `${firstValueAsDate} - ${secondValueAsDate}`, }, }; } else { adaptEvent = setDate(dateValue, props.format); } onWrapperBlur?.(adaptEvent); }; // validate formatted dates const handleChangeInternalValidate = event => { const dateValue = event?.target?.value; // reset the internal errors for new verify if (internalErrors.includes(InternalErrorType.INVALID_DATE)) { removeInternalError(InternalErrorType.INVALID_DATE); } else if (internalErrors.includes(InternalErrorType.INVALID_DATE_RANGE)) { removeInternalError(InternalErrorType.INVALID_DATE_RANGE); } // date range if (dateValue?.length === props.format?.length * 2 + 1) { if (errorExecution === ERROR_EXECUTION.ON_CHANGE) { handleChangeInternalValidateDateRange(dateValue); } const orderedRangeDates = orderStringDates(dateValue); // if the first date is invalid give an error const hasError = internalErrors.length > 0; // add ordered dates in ref value if (inputRef?.current) { inputRef.current.value = !hasError && typeof orderedRangeDates !== 'string' ? `${formatDate(orderedRangeDates[0], props.format)} ${formatDate(orderedRangeDates[1], props.format)}` : dateValue; } // add date separator in ref value inputRef?.current?.setRangeText(` ${props.dateSeparator}`, props.format.length, props.format.length, 'end'); // single date } else if (dateValue?.length === props.format?.length && errorExecution === ERROR_EXECUTION.ON_CHANGE) { handleChangeInternalValidateSingleDate(dateValue); } let adaptEvent; if (props.hasRange) { const hasSeparator = props.dateSeparator && dateValue.includes(props.dateSeparator); const secondDateExists = dateValue.length > props.format.length; const dates = hasSeparator ? dateValue.split(props.dateSeparator) : dateValue.split(' '); const firstDate = formatDateToNative(dates[0].trim(), props.format, true); const secondDate = secondDateExists ? formatDateToNative(dates[1].trim(), props.format, true) : ''; const firstValueAsNumber = firstDate.length && formatDateToUTC(firstDate).getTime(); const secondValueAsNumber = secondDate.length && formatDateToUTC(secondDate).getTime(); const firstValueAsDate = firstDate.length && formatDateToUTC(firstDate); const secondValueAsDate = secondDate.length && formatDateToUTC(secondDate); adaptEvent = { target: { value: `${firstDate} ${secondDate}`, valueAsNumber: `${firstValueAsNumber} ${secondValueAsNumber}`.trim(), valueAsDate: `${firstValueAsDate} - ${secondValueAsDate}`, }, }; } else { adaptEvent = setDate(dateValue, props.format); } onChange?.(adaptEvent); }; const handlePickCalendarDate = (newPickDate) => { // clean errors setted by internal validation, when date is selected from calendar if (internalErrors.includes(InternalErrorType.INVALID_DATE)) { removeInternalError(InternalErrorType.INVALID_DATE); } else if (internalErrors.includes(InternalErrorType.INVALID_DATE_RANGE)) { removeInternalError(InternalErrorType.INVALID_DATE_RANGE); } const orderedDates = [...newPickDate].sort((a, b) => new Date(a).getTime() - new Date(b).getTime()); const formattedDates = orderedDates.map(date => formatDate(date, props.format)); const hasSecondaDate = formattedDates.length === 2; const dateValue = hasSecondaDate ? formattedDates.join(' ') : formattedDates[0]; handleChangeDate(newPickDate); if (props.hasRange) { handleSetValue(`${getDate(orderedDates[0])} ${getDate(orderedDates[1])}`); } else if (dateValue?.length === props.format?.length) { if (inputRef?.current) { // Inner input value should be set before the rerendering // This avoid calling onChange event when input.value is different from the current value when picking a date inputRef.current.value = dateValue; // When selecting a date from the calendar, set the cursor to the end of the input eventKeyPressRef.current = { key: '', cursor: { start: dateValue.length, end: dateValue.length, }, }; } handleSetValue(dateValue); } let adaptEvent; if (hasSecondaDate) { const firstNativeDate = formatDateToNative(getDate(orderedDates[0]), props.format, true); const secondNativeDate = formatDateToNative(getDate(orderedDates[1]), props.format, true); const firstValueAsNumber = formatDateToUTC(orderedDates[0]).getTime(); const secondValueAsNumber = formatDateToUTC(orderedDates[1]).getTime(); adaptEvent = { target: { value: `${firstNativeDate} ${secondNativeDate}`, valueAsNumber: `${firstValueAsNumber} ${secondValueAsNumber}`, valueAsDate: `${orderedDates[0]} - ${orderedDates[1]}`, }, }; } else { const dateSelected = getDate(orderedDates[0]); adaptEvent = setDate(dateSelected, props.format); } onChange?.(adaptEvent); }; useEffect(() => { // On close calendar input will recive focus automatically // Also avoid focus when rendering the component if (!calendarOpen && !firstRender.current) { inputRef?.current?.focus(); } firstRender.current = false; }, [calendarOpen]); const { value, state, eventKeyPressRef, inputRef, handleChangeInternal, handleBlurInternal, handleFocusInternal, handleKeyDownInternal, handleSetValue, handlePasteInternal, } = useInput({ ref, keyValidation, internalErrorExecution, max, min, maxLength, minLength, mask, maskType, disabled, error: error || internalErrors.length > 0, currentValue, type, truncate, informationAssociated, disabledCopyAndPaste, onBlur, onFocus, onKeyDown, onError, onInternalErrors, onChange: handleChangeInternalValidate, onPaste, }); // update initial dates when they have value useEffect(() => { setDateFormatted(createDateFormatted(props.initialDate && formatDateToUTC(props.initialDate), props.initialSecondDate && formatDateToUTC(props.initialSecondDate))); if (props.hasRange) { const newValue = props.initialDate || props.initialSecondDate ? `${getDate(props.initialDate)} ${props.dateSeparator} ${getDate(props.initialSecondDate)}` : ''; handleSetValue(newValue); } else { handleSetValue(getDate(props.initialDate)); } }, [props.initialDate, props.initialSecondDate]); useEffect(() => { if (String(value).length === props.format?.length * 2 + 1) { // to set date separator when date is selected from calendar inputRef?.current?.setRangeText(` ${props.dateSeparator}`, props.format.length, props.format.length, 'end'); if (inputRef?.current) { handleSetValue(inputRef?.current?.value); } } //Set "Today, " when today date is selected or typed on single date if (String(value) === formatDate(new Date(), props.format) && String(value)?.length === props.format?.length && !props.hasRange) { handleSetValue(`${today}${value}`); } }, [value]); return { dateFormatted, calendarOpen, handleClickInput, handlePickCalendarDate, handleOpenCalendar, handleChangeInternalValidate, value, state, inputRef, handleChangeInternal, handleBlurInternal, handleWrapperBlur: onBlurDate, handleFocusInternal, handleKeyDownInternal, handleSetValue, handlePasteInternal, }; }; //# sourceMappingURL=useInputDate.js.map