UNPKG

@i-novus/ui-core

Version:

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

126 lines (125 loc) 7.93 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { forwardRef, useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import classNames from 'classnames'; import { Popover } from '../../data-display/Popover'; import { Input } from '../Input'; import { FocusTrap, useForwardedRef } from '../../core'; import { useComposeRef } from '../../core/hooks/useComposeRef'; import { InputSuffixIcon as ChevronButton } from './components/InputSuffixIcon'; import { ClearButton } from './components/ClearButton'; import { Tags } from './components'; const REACT_TOOLTIP_PLACEMENT_CLASS_PREFIX = 'react-tooltip__place-'; const INITIAL_POPOVER_PLACEMENT = 'bottom'; // eslint-disable-next-line sonarjs/cognitive-complexity export const MultipleSelector = forwardRef((props, ref) => { const { className, prefixCls, dropdownInnerComponentProps: { onOptionClick: onExternalOptionClick, ...restDropdownOptions }, dropdownInnerComponent: DropdownInnerComponent, tagListProps: { selectedOptions, onCloseTag: onExternalCloseTag, handleClearAllSelectedOptions, }, onBlur, onFocus, onOpen, onClose, style, readOnly = false, disabled = false, loading = false, error = false, clearIcon = true, type, value, disableInput = false, onChange, onKeyDown, ...inputProps } = props; const [isDropdownOpened, setDropdownOpened] = useState(false); const [observedPopoverPlacement, setObservedPopoverPlacement] = useState(INITIAL_POPOVER_PLACEMENT); const forwardedRef = useForwardedRef(ref); const returnFocusElementRef = useRef(null); const dropdownComponentRef = useRef(null); const popoverRef = useRef(null); const focusEventRef = useRef(); const inputRef = useRef(null); const composedRef = useComposeRef(forwardedRef, inputRef); const dropdownPrefix = `${prefixCls}-dropdown`; const dropdownClasses = useMemo(() => classNames(className, dropdownPrefix, `${dropdownPrefix}_place_${observedPopoverPlacement}`, { [`${dropdownPrefix}_opened`]: isDropdownOpened, }), [className, dropdownPrefix, isDropdownOpened, observedPopoverPlacement]); const popoverClasses = useMemo(() => classNames(`${dropdownPrefix}-popover`, `${dropdownPrefix}-popover_place_${observedPopoverPlacement}`, { [`${dropdownPrefix}-popover_opened`]: isDropdownOpened, }), [dropdownPrefix, isDropdownOpened, observedPopoverPlacement]); const inputContainerClasses = useMemo(() => classNames(`${dropdownPrefix}__input`, { [`${dropdownPrefix}__input_disabled`]: disabled, [`${dropdownPrefix}__input_error`]: error, [`${dropdownPrefix}__input_readonly`]: readOnly, }), [disabled, dropdownPrefix, error, readOnly]); const saveEvent = useCallback((event) => { focusEventRef.current = event; }, []); const handleClearMultiselect = useCallback((event) => { handleClearAllSelectedOptions?.(event); }, [handleClearAllSelectedOptions]); const close = useCallback(() => { setDropdownOpened(false); }, []); const onOpenDropdown = useCallback(() => { onFocus?.(focusEventRef.current); onOpen?.(focusEventRef.current); }, [onFocus, onOpen]); const onCloseDropdown = useCallback(() => { onClose?.(); onBlur?.(focusEventRef.current); }, [onBlur, onClose]); const handleInput = useCallback(() => { setDropdownOpened(true); }, []); useEffect(() => { const element = popoverRef.current; if (!element) { return undefined; } const onPopoverReplace = (mutationRecords) => { const record = mutationRecords[0]; const classes = Array.from((record?.target).classList); const popoverPlacement = classes .find(className => className.startsWith(REACT_TOOLTIP_PLACEMENT_CLASS_PREFIX))?.replace(REACT_TOOLTIP_PLACEMENT_CLASS_PREFIX, ''); if (popoverPlacement) { setObservedPopoverPlacement(popoverPlacement); } }; const popoverObserver = new MutationObserver(onPopoverReplace); popoverObserver.observe(element, { attributes: true, attributeFilter: ['class'], }); return () => { popoverObserver.disconnect(); }; /** Необходимо вызвать хук,как только меняется реф, что создать подписку на MutationObserver */ // eslint-disable-next-line react-hooks/exhaustive-deps }, [popoverRef.current]); const onCloseTag = (id, evt) => { evt.stopPropagation(); onExternalCloseTag?.(id, evt); }; const handleKeyDown = (event) => { onKeyDown?.(event); if (event.key === 'Backspace' && selectedOptions.length && !event.target.value) { const tagToDelete = selectedOptions?.at(-1); if (tagToDelete) { onCloseTag(tagToDelete.id, event); } } }; const onOptionClick = (newValue, event) => { onExternalOptionClick(newValue, event); if (!disableInput) { // @ts-ignore onChange('', event); inputRef?.current?.focus(); } }; const handleClickInside = useCallback((event) => { event.preventDefault(); if (dropdownComponentRef?.current?.contains(event?.target) && !disableInput) { inputRef?.current?.focus(); } }, [disableInput]); useEffect(() => { document.addEventListener('mouseup', handleClickInside); return () => document.removeEventListener('mouseup', handleClickInside); }, [handleClickInside]); return (_jsx("div", { className: dropdownClasses, ref: dropdownComponentRef, style: style, children: _jsx(Popover, { closeOnEsc: true, returnFocusRef: returnFocusElementRef, ref: popoverRef, afterShow: onOpenDropdown, afterHide: onCloseDropdown, style: { width: dropdownComponentRef.current?.offsetWidth, minWidth: dropdownComponentRef.current?.offsetWidth, maxWidth: dropdownComponentRef.current?.offsetWidth, }, disabled: disabled, readOnly: readOnly, prefix: prefixCls, open: isDropdownOpened, onOpenChange: setDropdownOpened, placement: INITIAL_POPOVER_PLACEMENT, className: popoverClasses, offset: 2, components: { Content: (_jsx(FocusTrap, { children: _jsx(DropdownInnerComponent, { ...restDropdownOptions, onOptionClick: onOptionClick, close: close, prefix: dropdownPrefix, value: value }) })), }, children: _jsxs("div", { className: inputContainerClasses, children: [_jsxs("div", { className: `${dropdownPrefix}__input-items`, children: [_jsx(Tags, { selectedOptions: selectedOptions, disabled: disabled, prefix: prefixCls, ...((!disabled || readOnly) && { onCloseTag }) }), disableInput || disabled ? null : (_jsx("div", { className: `${dropdownPrefix}__input-wrapper`, "data-value": value, children: _jsx(Input, { ...inputProps, onChange: onChange, onKeyDown: handleKeyDown, onInput: handleInput, value: value, ref: composedRef, prefix: prefixCls, disabled: disabled || readOnly, readOnly: readOnly, error: error, onClick: saveEvent, type: type, tabIndex: 0 }) }))] }), _jsxs("div", { className: `${dropdownPrefix}__input-controls`, children: [_jsx(ClearButton, { className: `${prefixCls}-dropdown__clear-icon`, clearIcon: clearIcon, disabled: disabled || readOnly, visible: Boolean(selectedOptions.length) || Boolean(value), onClick: handleClearMultiselect, ref: returnFocusElementRef }), _jsx(ChevronButton, { className: `${prefixCls}-dropdown__suffix-icon-button`, disabled: disabled || readOnly, error: error, loading: loading, onClick: saveEvent, ref: returnFocusElementRef })] })] }) }) })); }); MultipleSelector.displayName = 'DropdownMultipleInput';