UNPKG

@i-novus/ui-core

Version:

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

138 lines (137 loc) 7.58 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { forwardRef, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import dayjs from 'dayjs'; import customParseFormat from 'dayjs/plugin/customParseFormat'; import classNames from 'classnames'; import { Calendar } from '../../../data-display/Calendar/Calendar'; import { Popover } from '../../../data-display/Popover'; import { Paper } from '../../../layout/Paper'; import { CalendarIcon, useConfigProvider } from '../../../core'; import { InputMask } from '../../InputMask'; import { dateRangePicker } from '../../config/mask'; import { getDateAccuracy, getValidDayjs } from '../../dayjs-utils'; import { DATE_FORMAT, INTERVAL_SEPARATOR, TODAY } from '../../const'; import { EChangeType } from '../../../data-display/Calendar/types'; import { useDatePickerInputEvents } from '../hooks/date-picker-input-events'; import { FocusTrap } from '../../../core/components/focus-trap'; import { useForwardedRef } from '../../../core/hooks'; import { useHandleInputCompleteHook } from './hooks/handle-input-complete'; import { useHandleSelectDate } from './hooks/handle-select-date'; dayjs.extend(customParseFormat); // eslint-disable-next-line sonarjs/cognitive-complexity export const DateRangePicker = forwardRef((props, ref) => { const { prefix, popupClassName, className, value = null, onChange, onBlur, onFocus, error = false, disabled = false, readOnly = false, placement = 'bottom', parentId = 'popover', minDate: min, maxDate: max, suffixIcon = _jsx(CalendarIcon, {}), dateFormat = DATE_FORMAT, timeFormat, style, visible = true, getContainer, ...restProps } = props; const isCalendarOpen = useRef(false); const inputRef = useForwardedRef(ref); const containerRef = useRef(null); const { getPrefix } = useConfigProvider(); const prefixCls = getPrefix(prefix); // #region mask config const fullFormat = useMemo(() => [dateFormat, timeFormat].filter(Boolean).join(' '), [dateFormat, timeFormat]); const maskConfig = useMemo(() => dateRangePicker(fullFormat, INTERVAL_SEPARATOR), [fullFormat]); const dateAccuracy = getDateAccuracy(dateFormat, timeFormat); // #endregion // #region calendar data const { calendarValue, inputValue } = useMemo(() => { const begin = getValidDayjs(value?.begin); const end = getValidDayjs(value?.end); if (!begin || !end) { return ({ calendarValue: { begin: TODAY.toDate(), end: TODAY.toDate(), }, inputValue: null, }); } return ({ calendarValue: { begin: begin.toDate(), end: end.toDate(), }, inputValue: `${begin.format(fullFormat)} ${INTERVAL_SEPARATOR} ${end.format(fullFormat)}`, }); }, [fullFormat, value]); const minDate = useMemo(() => getValidDayjs(min), [min]); const maxDate = useMemo(() => getValidDayjs(max), [max]); const beginCalendarProps = useMemo(() => ({ minDate: minDate?.toDate() }), [minDate]); const endCalendarProps = useMemo(() => ({ maxDate: maxDate?.toDate() }), [maxDate]); const [canBeClosed, setCanBeClosed] = useState({ begin: false, end: false }); const [calendarOpen, setCalendarOpen] = useState(false); const [beginMaxDate, setBeginMaxDate] = useState(calendarValue.end || maxDate?.toDate()); const [endMinDate, setEndMinDate] = useState(calendarValue.begin || minDate?.toDate()); const onOpenCalendar = useCallback(() => { setCalendarOpen(true); }, []); const onCloseCalendar = useCallback(() => { setCalendarOpen(false); }, []); useLayoutEffect(() => { setBeginMaxDate(calendarValue.end || maxDate?.toDate()); }, [calendarValue.end, maxDate]); useLayoutEffect(() => { setEndMinDate(calendarValue.begin || minDate?.toDate()); }, [calendarValue.begin, minDate]); useLayoutEffect(() => { if (canBeClosed.begin && canBeClosed.end) { onCloseCalendar(); } }, [canBeClosed, onCloseCalendar]); useLayoutEffect(() => { isCalendarOpen.current = calendarOpen; if (!calendarOpen) { setCanBeClosed({ begin: false, end: false }); } }, [calendarOpen]); // #endregion // #region calendar handlers const { handleSelectBegin: onSelectBegin, handleSelectEnd: onSelectEnd } = useHandleSelectDate(onChange, value); const handleSelectBegin = useCallback((value, meta) => { if (meta.changedType !== EChangeType.TIME) { setCanBeClosed(state => ({ ...state, begin: true, })); } if (value) { setEndMinDate(value); } onSelectBegin(value); }, [onSelectBegin]); const handleSelectEnd = useCallback((value, meta) => { if (meta.changedType !== EChangeType.TIME) { setCanBeClosed(state => ({ ...state, end: true, })); } if (value) { setBeginMaxDate(value); } onSelectEnd(value); }, [onSelectEnd]); // #endregion // #region input handlers const handleComplete = useHandleInputCompleteHook(onChange, { min: minDate, max: maxDate, format: fullFormat }); const onFocusCallback = useCallback((event) => { onOpenCalendar(); onFocus?.(event); }, [onOpenCalendar, onFocus]); const { handleFocus, handleBlur, } = useDatePickerInputEvents(calendarOpen, inputRef, { onFocus: onFocusCallback, onEnterKeyDown: onOpenCalendar, onBlur, }); // #endregion useEffect(() => { const parentElement = document.getElementById(parentId); parentElement?.addEventListener('scroll', onOpenCalendar); return () => { parentElement?.removeEventListener('scroll', onOpenCalendar); }; }, [parentId, onOpenCalendar]); return (visible ? (_jsx("div", { ref: containerRef, className: (classNames(`${prefixCls}-date-range-picker`, { [`${prefixCls}-date-range-picker_opened`]: calendarOpen }, className)), style: style, children: _jsx(Popover, { closeOnEsc: true, returnFocusRef: inputRef, disabled: disabled, readOnly: readOnly, open: calendarOpen, onOpenChange: setCalendarOpen, placement: placement, className: classNames(popupClassName, `${prefixCls}-date-range-picker-popover`), getContainer: getContainer, components: { Content: (_jsx(FocusTrap, { children: _jsxs(Paper, { display: "flex", direction: "horizontal", children: [_jsx(Calendar, { className: "calendar_unbordered", onSelect: handleSelectBegin, activeStartDate: calendarValue.begin, maxDate: beginMaxDate, dateAccuracy: dateAccuracy, ...beginCalendarProps }, "begin"), _jsx(Calendar, { className: "calendar_unbordered", onSelect: handleSelectEnd, activeStartDate: calendarValue.end, minDate: endMinDate, dateAccuracy: dateAccuracy, ...endCalendarProps }, "end")] }) })), }, children: _jsx(InputMask, { ...restProps, ref: inputRef, className: classNames('date-range-picker__input'), value: inputValue, prefix: prefixCls, onBlur: handleBlur, onFocus: handleFocus, onComplete: handleComplete, suffixIcon: (_jsx("div", { className: `${prefixCls}-datepicker__suffix-icon-button`, children: suffixIcon })), disabled: disabled || readOnly, readOnly: readOnly, error: error, maskConfig: maskConfig }) }) })) : null); }); DateRangePicker.displayName = 'DateInterval';