@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
JavaScript
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