@gravity-ui/uikit
Version:
Gravity UI base styling and components
176 lines (175 loc) • 10.5 kB
JavaScript
'use client';
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Select = exports.DEFAULT_RENDER_POPUP = void 0;
const tslib_1 = require("tslib");
const jsx_runtime_1 = require("react/jsx-runtime");
const React = tslib_1.__importStar(require("react"));
const constants_1 = require("../../constants.js");
const hooks_1 = require("../../hooks/index.js");
const OuterAdditionalContent_1 = require("../controls/common/OuterAdditionalContent/OuterAdditionalContent.js");
const utils_1 = require("../controls/utils.js");
const mobile_1 = require("../mobile/index.js");
const filterDOMProps_1 = require("../utils/filterDOMProps.js");
const components_1 = require("./components/index.js");
const constants_2 = require("./constants.js");
const hooks_2 = require("./hooks/index.js");
const hooks_public_1 = require("./hooks-public/index.js");
const tech_components_1 = require("./tech-components.js");
const utils_2 = require("./utils.js");
require("./Select.css");
const DEFAULT_RENDER_POPUP = ({ renderFilter, renderList }) => {
return ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [renderFilter(), renderList()] }));
};
exports.DEFAULT_RENDER_POPUP = DEFAULT_RENDER_POPUP;
exports.Select = React.forwardRef(function Select(props, ref) {
const { onUpdate, onOpenChange, onFilterChange, renderControl, renderFilter, renderOption, renderOptionGroup, renderSelectedOption, renderEmptyOptions, renderPopup = exports.DEFAULT_RENDER_POPUP, getOptionHeight, getOptionGroupHeight, filterOption, name, form, className, controlClassName, popupClassName, qa, value: propsValue, defaultValue, defaultOpen, open: propsOpen, label, placeholder, filterPlaceholder, width, popupWidth, popupPlacement, error, virtualizationThreshold = constants_2.DEFAULT_VIRTUALIZATION_THRESHOLD, view = 'normal', size = 'm', pin = 'round-round', multiple = false, disabled = false, filterable = false, filter: propsFilter, disablePortal, hasClear = false, onClose, id, hasCounter, renderCounter, title, } = props;
const mobile = (0, mobile_1.useMobile)();
const [filter, setFilter] = (0, hooks_1.useControlledState)(propsFilter, '', onFilterChange);
// to avoid problem with incorrect popper offset calculation
// for example: https://github.com/radix-ui/primitives/issues/1567
const controlWrapRef = React.useRef(null);
const controlRef = React.useRef(null);
const filterRef = React.useRef(null);
const listRef = React.useRef(null);
const handleControlRef = (0, hooks_1.useForkRef)(ref, controlRef);
const { value, open, toggleOpen, setValue, handleSelection, handleClearValue } = (0, hooks_1.useSelect)({
onUpdate,
value: propsValue,
defaultValue,
defaultOpen,
multiple,
open: propsOpen,
onClose,
onOpenChange,
disabled,
});
React.useEffect(() => {
if (!open && filterable && mobile) {
// FIXME: add handlers to Sheet like in https://github.com/gravity-ui/uikit/issues/1354
setTimeout(() => {
setFilter('');
}, 300);
}
}, [open, filterable, setFilter, mobile]);
const propsOptions = props.options || (0, utils_2.getOptionsFromChildren)(props.children);
const options = (0, hooks_public_1.useSelectOptions)({
options: propsOptions,
filter,
filterable,
filterOption,
});
const filteredOptions = (0, hooks_public_1.getSelectFilteredOptions)(options);
const selectedOptionsContent = (0, utils_2.getSelectedOptionsContent)(options, value, renderSelectedOption);
const virtualized = filteredOptions.length >= virtualizationThreshold;
const { errorMessage, errorPlacement, validationState } = (0, utils_1.errorPropsMapper)({
error,
errorMessage: props.errorMessage,
errorPlacement: props.errorPlacement || 'outside',
validationState: props.validationState,
});
const errorMessageId = (0, hooks_1.useUniqId)();
const isErrorStateVisible = validationState === 'invalid';
const isErrorMsgVisible = isErrorStateVisible && Boolean(errorMessage) && errorPlacement === 'outside';
const isErrorIconVisible = isErrorStateVisible && Boolean(errorMessage) && errorPlacement === 'inside';
const handleOptionClick = React.useCallback((option) => {
if (!option || option?.disabled || 'label' in option) {
return;
}
if (multiple) {
const activeItemIndex = listRef?.current?.getActiveItem();
if (!mobile) {
filterRef.current?.focus();
}
if (typeof activeItemIndex === 'number') {
// prevent item deactivation in case of multiple selection
// https://github.com/gravity-ui/uikit/blob/main/src/components/List/List.tsx#L369
// Will fixed after https://github.com/gravity-ui/uikit/issues/385
setTimeout(() => {
listRef?.current?.activateItem(activeItemIndex, true);
}, 50);
}
}
handleSelection(option);
}, [handleSelection, mobile, multiple]);
const handleControlKeyDown = React.useCallback((e) => {
// prevent dialog closing in case of item selection by Enter/Spacebar keydown
if ([constants_1.KeyCode.ENTER, constants_1.KeyCode.SPACEBAR].includes(e.key) && open) {
e.preventDefault();
if (e.key === constants_1.KeyCode.SPACEBAR) {
handleOptionClick((0, utils_2.getActiveItem)(listRef));
}
}
if ([constants_1.KeyCode.ARROW_DOWN, constants_1.KeyCode.ARROW_UP].includes(e.key) && !open) {
e.preventDefault();
toggleOpen();
}
listRef?.current?.onKeyDown(e);
}, [handleOptionClick, open, toggleOpen]);
const handleFilterKeyDown = React.useCallback((e) => {
listRef?.current?.onKeyDown(e);
}, []);
const handleQuickSearchChange = React.useCallback((search) => {
if (search) {
const itemIndex = (0, utils_2.findItemIndexByQuickSearch)(search, (0, utils_2.getListItems)(listRef));
if (typeof itemIndex === 'number' && itemIndex !== -1) {
listRef?.current?.activateItem(itemIndex, true);
}
}
}, []);
(0, hooks_2.useQuickSearch)({
onChange: handleQuickSearchChange,
open,
disabled: filterable,
});
const mods = {
...(width === 'max' && { width }),
};
const inlineStyles = {};
if (typeof width === 'number') {
inlineStyles.width = width;
}
const handleClose = React.useCallback(() => toggleOpen(false), [toggleOpen]);
const { onFocus, onBlur } = props;
const { focusWithinProps } = (0, hooks_1.useFocusWithin)({
onFocusWithin: onFocus,
onBlurWithin: React.useCallback((e) => {
onBlur?.(e);
if (!mobile) {
handleClose();
}
}, [handleClose, mobile, onBlur]),
});
const uniqId = (0, hooks_1.useUniqId)();
const selectId = id ?? uniqId;
const popupId = `select-popup-${selectId}`;
const [activeIndex, setActiveIndex] = (0, hooks_2.useActiveItemIndex)({
options: filteredOptions,
open,
value,
});
const _renderFilter = () => {
if (filterable) {
return ((0, jsx_runtime_1.jsx)(components_1.SelectFilter, { ref: filterRef, size: size, value: filter, placeholder: filterPlaceholder, onChange: setFilter, onKeyDown: handleFilterKeyDown, renderFilter: renderFilter, popupId: popupId, activeIndex: activeIndex }));
}
return null;
};
const _renderList = () => {
if (filteredOptions.length || props.loading) {
return ((0, jsx_runtime_1.jsx)(components_1.SelectList, { ref: listRef, size: size, value: value, mobile: mobile, flattenOptions: filteredOptions, multiple: multiple, virtualized: virtualized, onOptionClick: handleOptionClick, renderOption: renderOption, renderOptionGroup: renderOptionGroup, getOptionHeight: getOptionHeight, getOptionGroupHeight: getOptionGroupHeight, loading: props.loading, onLoadMore: props.onLoadMore, id: popupId, activeIndex: activeIndex, onChangeActive: setActiveIndex }));
}
return (0, jsx_runtime_1.jsx)(components_1.EmptyOptions, { filter: filter, renderEmptyOptions: renderEmptyOptions });
};
return ((0, jsx_runtime_1.jsxs)("div", { ref: controlWrapRef, className: (0, constants_2.selectBlock)(mods, className), ...focusWithinProps, style: inlineStyles, tabIndex: -1, children: [(0, jsx_runtime_1.jsx)(components_1.SelectControl, { ...(0, filterDOMProps_1.filterDOMProps)(props, { labelable: true }), toggleOpen: toggleOpen, hasClear: hasClear, clearValue: handleClearValue, ref: handleControlRef, className: controlClassName, qa: qa, view: view, size: size, pin: pin, label: label, placeholder: placeholder, selectedOptionsContent: selectedOptionsContent, isErrorVisible: isErrorStateVisible, errorMessage: isErrorIconVisible ? errorMessage : undefined, open: open, disabled: disabled, onKeyDown: handleControlKeyDown, renderControl: renderControl, value: value, popupId: popupId, selectId: selectId, activeIndex: activeIndex, hasCounter: multiple && hasCounter, renderCounter: renderCounter, title: title }), (0, jsx_runtime_1.jsx)(components_1.SelectPopup, { ref: controlWrapRef, className: popupClassName, controlRef: controlRef, width: popupWidth, open: open, handleClose: handleClose, disablePortal: disablePortal, virtualized: virtualized, mobile: mobile, placement: popupPlacement, onAfterOpen: filterable
? () => {
filterRef.current?.focus();
}
: undefined, onAfterClose: filterable
? () => {
setFilter('');
}
: undefined, children: renderPopup({ renderFilter: _renderFilter, renderList: _renderList }) }), (0, jsx_runtime_1.jsx)(OuterAdditionalContent_1.OuterAdditionalContent, { errorMessage: isErrorMsgVisible ? errorMessage : null, errorMessageId: errorMessageId }), (0, jsx_runtime_1.jsx)(components_1.HiddenSelect, { name: name, value: value, disabled: disabled, form: form, onReset: setValue })] }));
});
exports.Select.Option = tech_components_1.Option;
exports.Select.OptionGroup = tech_components_1.OptionGroup;
//# sourceMappingURL=Select.js.map