@i-novus/ui-core
Version:
Базовые UI компоненты
116 lines (115 loc) • 6.24 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import { forwardRef, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import classNames from 'classnames';
import { CalendarIcon, useConfigProvider, useForwardedRef, useMemoFunction } from '../../../core';
import { Calendar } from '../../../data-display/Calendar/Calendar';
import { Popover } from '../../../data-display/Popover';
import { InputMask } from '../../InputMask';
import { EChangeType } from '../../../data-display/Calendar/types';
import { datePicker } from '../../config/mask';
import { getValidDayjs, getDateAccuracy, cutDateByAccuracy } from '../../dayjs-utils';
import { DATE_FORMAT, OUTPUT_FORMAT, TODAY } from '../../const';
import { useDatePickerInputEvents } from '../hooks/date-picker-input-events';
import { FocusTrap } from '../../../core/components/focus-trap';
import { useHandleInputCompleteHook } from './hooks/handle-input-complete.hook';
dayjs.extend(customParseFormat);
// eslint-disable-next-line sonarjs/cognitive-complexity
export const DatePicker = forwardRef((props, ref) => {
const { className, popupClassName, value = null, onChange, onBlur, onFocus, error = false, disabled = false, readOnly = false, placement = 'bottom', parentId = 'popover', minDate: min, maxDate: max, prefix, dateFormat = DATE_FORMAT, timeFormat, style, suffixIcon, visible = true, getContainer, maskConfig, calendarView = 'month', ...restProps } = props;
const inputRef = useForwardedRef(ref);
const dateAccuracyRef = useRef('day');
dateAccuracyRef.current = getDateAccuracy(dateFormat, timeFormat);
const [calendarOpen, setCalendarOpen] = useState(false);
const isCalendarOpen = useRef(calendarOpen);
useLayoutEffect(() => {
isCalendarOpen.current = calendarOpen;
}, [calendarOpen]);
const onOpenCalendar = useMemoFunction(() => {
setCalendarOpen(true);
});
const onCloseCalendar = useMemoFunction(() => {
setCalendarOpen(false);
});
const { getPrefix } = useConfigProvider();
const prefixCls = getPrefix(prefix);
// #region mask config
const fullFormat = useMemo(() => [dateFormat, timeFormat].filter(Boolean).join(' '), [dateFormat, timeFormat]);
const mask = useMemo(() => maskConfig ?? datePicker(fullFormat), [fullFormat, maskConfig]);
// #endregion
// #region calendar data
const { calendarValue, inputValue } = useMemo(() => {
const dateRaw = getValidDayjs(value);
const date = cutDateByAccuracy(dateRaw, dateAccuracyRef.current);
if (date) {
return ({
calendarValue: date.toDate(),
inputValue: date.format(fullFormat),
});
}
return ({
calendarValue: (cutDateByAccuracy(TODAY, dateAccuracyRef.current) || TODAY).toDate(),
inputValue: null,
});
}, [fullFormat, value]);
const minDate = useMemo(() => cutDateByAccuracy(getValidDayjs(min), dateAccuracyRef.current), [min]);
const maxDate = useMemo(() => cutDateByAccuracy(getValidDayjs(max), dateAccuracyRef.current), [max]);
// #endregion
// #region calendar handlers
const handleSelectDate = useMemoFunction((newDate, { changedType }) => {
let newValue = getValidDayjs(newDate);
if (newValue && minDate && newValue < minDate) {
newValue = minDate;
}
if (newValue && maxDate && newValue > maxDate) {
newValue = maxDate;
}
const newDateLimited = cutDateByAccuracy(newValue, dateAccuracyRef.current);
const date = newDateLimited?.format(OUTPUT_FORMAT) || null;
if (changedType !== EChangeType.TIME) {
onCloseCalendar();
}
onChange(date);
});
// #endregion
// #region input handlers
const handleComplete = useHandleInputCompleteHook(onChange, {
min: minDate,
max: maxDate,
format: fullFormat,
dateAccuracy: dateAccuracyRef.current,
});
const onFocusCallback = useMemoFunction((event) => {
onOpenCalendar();
onFocus?.(event);
});
const { handleFocus, handleBlur, } = useDatePickerInputEvents(isCalendarOpen, inputRef, {
onFocus: onFocusCallback,
onEnterKeyDown: onOpenCalendar,
onBlur,
}, {
minDate,
maxDate,
fullFormat,
value,
});
useEffect(() => {
const parentElement = document?.getElementById(parentId);
parentElement?.addEventListener('scroll', onOpenCalendar);
return () => {
parentElement?.removeEventListener('scroll', onOpenCalendar);
};
}, [parentId, onOpenCalendar]);
const calendarProps = useMemo(() => ({
minDate: minDate?.toDate(),
maxDate: maxDate?.toDate(),
}), [minDate, maxDate]);
const suffixIconFinal = useMemo(() => (_jsx("div", { className: `${prefixCls}-datepicker__suffix-icon-button`, children: suffixIcon === undefined ? _jsx(CalendarIcon, {}) : suffixIcon })), [prefixCls, suffixIcon]);
return (visible ? (_jsx("div", { className: (classNames(`${prefixCls}-datepicker`, className, {
[`${prefixCls}-datepicker_opened`]: calendarOpen,
})), style: style, children: _jsx(Popover, { closeOnEsc: true, returnFocusRef: inputRef, disabled: disabled, readOnly: readOnly, open: calendarOpen, onOpenChange: setCalendarOpen, placement: placement, className: classNames(popupClassName, `${prefixCls}-datepicker-popover`), getContainer: getContainer, components: {
Content: (_jsx(FocusTrap, { children: _jsx(Calendar, { onSelect: handleSelectDate, activeStartDate: calendarValue, view: calendarView, dateAccuracy: dateAccuracyRef.current, ...calendarProps }) })),
}, children: _jsx(InputMask, { ...restProps, ref: inputRef, value: inputValue, onBlur: handleBlur, onFocus: handleFocus, onComplete: handleComplete, suffixIcon: suffixIconFinal, className: classNames('datepicker__input'), prefix: prefixCls, disabled: disabled || readOnly, error: error, readOnly: readOnly, maskConfig: mask }) }) })) : null);
});
DatePicker.displayName = 'DatePicker';