@i-novus/ui-core
Version:
Базовые UI компоненты
126 lines (125 loc) • 7.93 kB
JavaScript
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';