UNPKG

@i-novus/ui-core

Version:

Базовые UI компоненты

116 lines (115 loc) 6.24 kB
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';