import _typeof from "@babel/runtime/helpers/esm/typeof"; import _extends from "@babel/runtime/helpers/esm/extends"; import _defineProperty from "@babel/runtime/helpers/esm/defineProperty"; import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray"; import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2"; import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties"; var _excluded = ["id", "prefixCls", "className", "showSearch", "tagRender", "direction", "omitDomProps", "displayValues", "onDisplayValuesChange", "emptyOptions", "notFoundContent", "onClear", "mode", "disabled", "loading", "getInputElement", "getRawInputElement", "open", "defaultOpen", "onDropdownVisibleChange", "activeValue", "onActiveValueChange", "activeDescendantId", "searchValue", "autoClearSearchValue", "onSearch", "onSearchSplit", "tokenSeparators", "allowClear", "suffixIcon", "clearIcon", "OptionList", "animation", "transitionName", "dropdownStyle", "dropdownClassName", "dropdownMatchSelectWidth", "dropdownRender", "dropdownAlign", "placement", "builtinPlacements", "getPopupContainer", "showAction", "onFocus", "onBlur", "onKeyUp", "onKeyDown", "onMouseDown"]; import classNames from 'classnames'; import useLayoutEffect from "rc-util/es/hooks/useLayoutEffect"; import useMergedState from "rc-util/es/hooks/useMergedState"; import isMobile from "rc-util/es/isMobile"; import KeyCode from "rc-util/es/KeyCode"; import { useComposeRef } from "rc-util/es/ref"; import * as React from 'react'; import { useAllowClear } from "./hooks/useAllowClear"; import { BaseSelectContext } from "./hooks/useBaseProps"; import useDelayReset from "./hooks/useDelayReset"; import useLock from "./hooks/useLock"; import useSelectTriggerControl from "./hooks/useSelectTriggerControl"; import Selector from "./Selector"; import SelectTrigger from "./SelectTrigger"; import TransBtn from "./TransBtn"; import { getSeparatedContent } from "./utils/valueUtil"; var DEFAULT_OMIT_PROPS = ['value', 'onChange', 'removeIcon', 'placeholder', 'autoFocus', 'maxTagCount', 'maxTagTextLength', 'maxTagPlaceholder', 'choiceTransitionName', 'onInputKeyDown', 'onPopupScroll', 'tabIndex']; export function isMultiple(mode) { return mode === 'tags' || mode === 'multiple'; } var BaseSelect = /*#__PURE__*/React.forwardRef(function (props, ref) { var _customizeRawInputEle, _classNames2; var id = props.id, prefixCls = props.prefixCls, className = props.className, showSearch = props.showSearch, tagRender = props.tagRender, direction = props.direction, omitDomProps = props.omitDomProps, displayValues = props.displayValues, onDisplayValuesChange = props.onDisplayValuesChange, emptyOptions = props.emptyOptions, _props$notFoundConten = props.notFoundContent, notFoundContent = _props$notFoundConten === void 0 ? 'Not Found' : _props$notFoundConten, onClear = props.onClear, mode = props.mode, disabled = props.disabled, loading = props.loading, getInputElement = props.getInputElement, getRawInputElement = props.getRawInputElement, open = props.open, defaultOpen = props.defaultOpen, onDropdownVisibleChange = props.onDropdownVisibleChange, activeValue = props.activeValue, onActiveValueChange = props.onActiveValueChange, activeDescendantId = props.activeDescendantId, searchValue = props.searchValue, autoClearSearchValue = props.autoClearSearchValue, onSearch = props.onSearch, onSearchSplit = props.onSearchSplit, tokenSeparators = props.tokenSeparators, allowClear = props.allowClear, suffixIcon = props.suffixIcon, clearIcon = props.clearIcon, OptionList = props.OptionList, animation = props.animation, transitionName = props.transitionName, dropdownStyle = props.dropdownStyle, dropdownClassName = props.dropdownClassName, dropdownMatchSelectWidth = props.dropdownMatchSelectWidth, dropdownRender = props.dropdownRender, dropdownAlign = props.dropdownAlign, placement = props.placement, builtinPlacements = props.builtinPlacements, getPopupContainer = props.getPopupContainer, _props$showAction = props.showAction, showAction = _props$showAction === void 0 ? [] : _props$showAction, onFocus = props.onFocus, onBlur = props.onBlur, onKeyUp = props.onKeyUp, onKeyDown = props.onKeyDown, onMouseDown = props.onMouseDown, restProps = _objectWithoutProperties(props, _excluded); // ============================== MISC ============================== var multiple = isMultiple(mode); var mergedShowSearch = (showSearch !== undefined ? showSearch : multiple) || mode === 'combobox'; var domProps = _objectSpread({}, restProps); DEFAULT_OMIT_PROPS.forEach(function (propName) { delete domProps[propName]; }); omitDomProps === null || omitDomProps === void 0 ? void 0 : omitDomProps.forEach(function (propName) { delete domProps[propName]; }); // ============================= Mobile ============================= var _React$useState = React.useState(false), _React$useState2 = _slicedToArray(_React$useState, 2), mobile = _React$useState2[0], setMobile = _React$useState2[1]; React.useEffect(function () { // Only update on the client side setMobile(isMobile()); }, []); // ============================== Refs ============================== var containerRef = React.useRef(null); var selectorDomRef = React.useRef(null); var triggerRef = React.useRef(null); var selectorRef = React.useRef(null); var listRef = React.useRef(null); var blurRef = React.useRef(false); /** Used for component focused management */ var _useDelayReset = useDelayReset(), _useDelayReset2 = _slicedToArray(_useDelayReset, 3), mockFocused = _useDelayReset2[0], setMockFocused = _useDelayReset2[1], cancelSetMockFocused = _useDelayReset2[2]; // =========================== Imperative =========================== React.useImperativeHandle(ref, function () { var _selectorRef$current, _selectorRef$current2; return { focus: (_selectorRef$current = selectorRef.current) === null || _selectorRef$current === void 0 ? void 0 : _selectorRef$current.focus, blur: (_selectorRef$current2 = selectorRef.current) === null || _selectorRef$current2 === void 0 ? void 0 : _selectorRef$current2.blur, scrollTo: function scrollTo(arg) { var _listRef$current; return (_listRef$current = listRef.current) === null || _listRef$current === void 0 ? void 0 : _listRef$current.scrollTo(arg); } }; }); // ========================== Search Value ========================== var mergedSearchValue = React.useMemo(function () { var _displayValues$; if (mode !== 'combobox') { return searchValue; } var val = (_displayValues$ = displayValues[0]) === null || _displayValues$ === void 0 ? void 0 : _displayValues$.value; return typeof val === 'string' || typeof val === 'number' ? String(val) : ''; }, [searchValue, mode, displayValues]); // ========================== Custom Input ========================== // Only works in `combobox` var customizeInputElement = mode === 'combobox' && typeof getInputElement === 'function' && getInputElement() || null; // Used for customize replacement for `rc-cascader` var customizeRawInputElement = typeof getRawInputElement === 'function' && getRawInputElement(); var customizeRawInputRef = useComposeRef(selectorDomRef, customizeRawInputElement === null || customizeRawInputElement === void 0 ? void 0 : (_customizeRawInputEle = customizeRawInputElement.props) === null || _customizeRawInputEle === void 0 ? void 0 : _customizeRawInputEle.ref); // ============================== Open ============================== // SSR not support Portal which means we need delay `open` for the first time render var _React$useState3 = React.useState(false), _React$useState4 = _slicedToArray(_React$useState3, 2), rendered = _React$useState4[0], setRendered = _React$useState4[1]; useLayoutEffect(function () { setRendered(true); }, []); var _useMergedState = useMergedState(false, { defaultValue: defaultOpen, value: open }), _useMergedState2 = _slicedToArray(_useMergedState, 2), innerOpen = _useMergedState2[0], setInnerOpen = _useMergedState2[1]; var mergedOpen = rendered ? innerOpen : false; // Not trigger `open` in `combobox` when `notFoundContent` is empty var emptyListContent = !notFoundContent && emptyOptions; if (disabled || emptyListContent && mergedOpen && mode === 'combobox') { mergedOpen = false; } var triggerOpen = emptyListContent ? false : mergedOpen; var onToggleOpen = React.useCallback(function (newOpen) { var nextOpen = newOpen !== undefined ? newOpen : !mergedOpen; if (!disabled) { setInnerOpen(nextOpen); if (mergedOpen !== nextOpen) { onDropdownVisibleChange === null || onDropdownVisibleChange === void 0 ? void 0 : onDropdownVisibleChange(nextOpen); } } }, [disabled, mergedOpen, setInnerOpen, onDropdownVisibleChange]); // ============================= Search ============================= var tokenWithEnter = React.useMemo(function () { return (tokenSeparators || []).some(function (tokenSeparator) { return ['\n', '\r\n'].includes(tokenSeparator); }); }, [tokenSeparators]); var onInternalSearch = function onInternalSearch(searchText, fromTyping, isCompositing) { var ret = true; var newSearchText = searchText; onActiveValueChange === null || onActiveValueChange === void 0 ? void 0 : onActiveValueChange(null); // Check if match the `tokenSeparators` var patchLabels = isCompositing ? null : getSeparatedContent(searchText, tokenSeparators); // Ignore combobox since it's not split-able if (mode !== 'combobox' && patchLabels) { newSearchText = ''; onSearchSplit === null || onSearchSplit === void 0 ? void 0 : onSearchSplit(patchLabels); // Should close when paste finish onToggleOpen(false); // Tell Selector that break next actions ret = false; } if (onSearch && mergedSearchValue !== newSearchText) { onSearch(newSearchText, { source: fromTyping ? 'typing' : 'effect' }); } return ret; }; // Only triggered when menu is closed & mode is tags // If menu is open, OptionList will take charge // If mode isn't tags, press enter is not meaningful when you can't see any option var onInternalSearchSubmit = function onInternalSearchSubmit(searchText) { // prevent empty tags from appearing when you click the Enter button if (!searchText || !searchText.trim()) { return; } onSearch(searchText, { source: 'submit' }); }; // Close will clean up single mode search text React.useEffect(function () { if (!mergedOpen && !multiple && mode !== 'combobox') { onInternalSearch('', false, false); } }, [mergedOpen]); // ============================ Disabled ============================ // Close dropdown & remove focus state when disabled change React.useEffect(function () { if (innerOpen && disabled) { setInnerOpen(false); } // After onBlur is triggered, the focused does not need to be reset if (disabled && !blurRef.current) { setMockFocused(false); } }, [disabled]); // ============================ Keyboard ============================ /** * We record input value here to check if can press to clean up by backspace * - null: Key is not down, this is reset by key up * - true: Search text is empty when first time backspace down * - false: Search text is not empty when first time backspace down */ var _useLock = useLock(), _useLock2 = _slicedToArray(_useLock, 2), getClearLock = _useLock2[0], setClearLock = _useLock2[1]; // KeyDown var onInternalKeyDown = function onInternalKeyDown(event) { var clearLock = getClearLock(); var which = event.which; if (which === KeyCode.ENTER) { // Do not submit form when type in the input if (mode !== 'combobox') { event.preventDefault(); } // We only manage open state here, close logic should handle by list component if (!mergedOpen) { onToggleOpen(true); } } setClearLock(!!mergedSearchValue); // Remove value by `backspace` if (which === KeyCode.BACKSPACE && !clearLock && multiple && !mergedSearchValue && displayValues.length) { var cloneDisplayValues = _toConsumableArray(displayValues); var removedDisplayValue = null; for (var i = cloneDisplayValues.length - 1; i >= 0; i -= 1) { var current = cloneDisplayValues[i]; if (!current.disabled) { cloneDisplayValues.splice(i, 1); removedDisplayValue = current; break; } } if (removedDisplayValue) { onDisplayValuesChange(cloneDisplayValues, { type: 'remove', values: [removedDisplayValue] }); } } for (var _len = arguments.length, rest = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { rest[_key - 1] = arguments[_key]; } if (mergedOpen && listRef.current) { var _listRef$current2; (_listRef$current2 = listRef.current).onKeyDown.apply(_listRef$current2, [event].concat(rest)); } onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown.apply(void 0, [event].concat(rest)); }; // KeyUp var onInternalKeyUp = function onInternalKeyUp(event) { for (var _len2 = arguments.length, rest = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { rest[_key2 - 1] = arguments[_key2]; } if (mergedOpen && listRef.current) { var _listRef$current3; (_listRef$current3 = listRef.current).onKeyUp.apply(_listRef$current3, [event].concat(rest)); } onKeyUp === null || onKeyUp === void 0 ? void 0 : onKeyUp.apply(void 0, [event].concat(rest)); }; // ============================ Selector ============================ var onSelectorRemove = function onSelectorRemove(val) { var newValues = displayValues.filter(function (i) { return i !== val; }); onDisplayValuesChange(newValues, { type: 'remove', values: [val] }); }; // ========================== Focus / Blur ========================== /** Record real focus status */ var focusRef = React.useRef(false); var onContainerFocus = function onContainerFocus() { setMockFocused(true); if (!disabled) { if (onFocus && !focusRef.current) { onFocus.apply(void 0, arguments); } // `showAction` should handle `focus` if set if (showAction.includes('focus')) { onToggleOpen(true); } } focusRef.current = true; }; var onContainerBlur = function onContainerBlur() { blurRef.current = true; setMockFocused(false, function () { focusRef.current = false; blurRef.current = false; onToggleOpen(false); }); if (disabled) { return; } if (mergedSearchValue) { // `tags` mode should move `searchValue` into values if (mode === 'tags') { onSearch(mergedSearchValue, { source: 'submit' }); } else if (mode === 'multiple') { // `multiple` mode only clean the search value but not trigger event onSearch('', { source: 'blur' }); } } if (onBlur) { onBlur.apply(void 0, arguments); } }; // Give focus back of Select var activeTimeoutIds = []; React.useEffect(function () { return function () { activeTimeoutIds.forEach(function (timeoutId) { return clearTimeout(timeoutId); }); activeTimeoutIds.splice(0, activeTimeoutIds.length); }; }, []); var onInternalMouseDown = function onInternalMouseDown(event) { var _triggerRef$current; var target = event.target; var popupElement = (_triggerRef$current = triggerRef.current) === null || _triggerRef$current === void 0 ? void 0 : _triggerRef$current.getPopupElement(); // We should give focus back to selector if clicked item is not focusable if (popupElement && popupElement.contains(target)) { var timeoutId = setTimeout(function () { var index = activeTimeoutIds.indexOf(timeoutId); if (index !== -1) { activeTimeoutIds.splice(index, 1); } cancelSetMockFocused(); if (!mobile && !popupElement.contains(document.activeElement)) { var _selectorRef$current3; (_selectorRef$current3 = selectorRef.current) === null || _selectorRef$current3 === void 0 ? void 0 : _selectorRef$current3.focus(); } }); activeTimeoutIds.push(timeoutId); } for (var _len3 = arguments.length, restArgs = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { restArgs[_key3 - 1] = arguments[_key3]; } onMouseDown === null || onMouseDown === void 0 ? void 0 : onMouseDown.apply(void 0, [event].concat(restArgs)); }; // ============================ Dropdown ============================ var _React$useState5 = React.useState({}), _React$useState6 = _slicedToArray(_React$useState5, 2), forceUpdate = _React$useState6[1]; // We need force update here since popup dom is render async function onPopupMouseEnter() { forceUpdate({}); } // Used for raw custom input trigger var onTriggerVisibleChange; if (customizeRawInputElement) { onTriggerVisibleChange = function onTriggerVisibleChange(newOpen) { onToggleOpen(newOpen); }; } // Close when click on non-select element useSelectTriggerControl(function () { var _triggerRef$current2; return [containerRef.current, (_triggerRef$current2 = triggerRef.current) === null || _triggerRef$current2 === void 0 ? void 0 : _triggerRef$current2.getPopupElement()]; }, triggerOpen, onToggleOpen, !!customizeRawInputElement); // ============================ Context ============================= var baseSelectContext = React.useMemo(function () { return _objectSpread(_objectSpread({}, props), {}, { notFoundContent: notFoundContent, open: mergedOpen, triggerOpen: triggerOpen, id: id, showSearch: mergedShowSearch, multiple: multiple, toggleOpen: onToggleOpen }); }, [props, notFoundContent, triggerOpen, mergedOpen, id, mergedShowSearch, multiple, onToggleOpen]); // ================================================================== // == Render == // ================================================================== // ============================= Arrow ============================== var showSuffixIcon = !!suffixIcon || loading; var arrowNode; if (showSuffixIcon) { arrowNode = /*#__PURE__*/React.createElement(TransBtn, { className: classNames("".concat(prefixCls, "-arrow"), _defineProperty({}, "".concat(prefixCls, "-arrow-loading"), loading)), customizeIcon: suffixIcon, customizeIconProps: { loading: loading, searchValue: mergedSearchValue, open: mergedOpen, focused: mockFocused, showSearch: mergedShowSearch } }); } // ============================= Clear ============================== var onClearMouseDown = function onClearMouseDown() { var _selectorRef$current4; onClear === null || onClear === void 0 ? void 0 : onClear(); (_selectorRef$current4 = selectorRef.current) === null || _selectorRef$current4 === void 0 ? void 0 : _selectorRef$current4.focus(); onDisplayValuesChange([], { type: 'clear', values: displayValues }); onInternalSearch('', false, false); }; var _useAllowClear = useAllowClear(prefixCls, onClearMouseDown, displayValues, allowClear, clearIcon, disabled, mergedSearchValue, mode), mergedAllowClear = _useAllowClear.allowClear, clearNode = _useAllowClear.clearIcon; // =========================== OptionList =========================== var optionList = /*#__PURE__*/React.createElement(OptionList, { ref: listRef }); // ============================= Select ============================= var mergedClassName = classNames(prefixCls, className, (_classNames2 = {}, _defineProperty(_classNames2, "".concat(prefixCls, "-focused"), mockFocused), _defineProperty(_classNames2, "".concat(prefixCls, "-multiple"), multiple), _defineProperty(_classNames2, "".concat(prefixCls, "-single"), !multiple), _defineProperty(_classNames2, "".concat(prefixCls, "-allow-clear"), allowClear), _defineProperty(_classNames2, "".concat(prefixCls, "-show-arrow"), showSuffixIcon), _defineProperty(_classNames2, "".concat(prefixCls, "-disabled"), disabled), _defineProperty(_classNames2, "".concat(prefixCls, "-loading"), loading), _defineProperty(_classNames2, "".concat(prefixCls, "-open"), mergedOpen), _defineProperty(_classNames2, "".concat(prefixCls, "-customize-input"), customizeInputElement), _defineProperty(_classNames2, "".concat(prefixCls, "-show-search"), mergedShowSearch), _classNames2)); // >>> Selector var selectorNode = /*#__PURE__*/React.createElement(SelectTrigger, { ref: triggerRef, disabled: disabled, prefixCls: prefixCls, visible: triggerOpen, popupElement: optionList, animation: animation, transitionName: transitionName, dropdownStyle: dropdownStyle, dropdownClassName: dropdownClassName, direction: direction, dropdownMatchSelectWidth: dropdownMatchSelectWidth, dropdownRender: dropdownRender, dropdownAlign: dropdownAlign, placement: placement, builtinPlacements: builtinPlacements, getPopupContainer: getPopupContainer, empty: emptyOptions, getTriggerDOMNode: function getTriggerDOMNode() { return selectorDomRef.current; }, onPopupVisibleChange: onTriggerVisibleChange, onPopupMouseEnter: onPopupMouseEnter }, customizeRawInputElement ? /*#__PURE__*/React.cloneElement(customizeRawInputElement, { ref: customizeRawInputRef }) : /*#__PURE__*/React.createElement(Selector, _extends({}, props, { domRef: selectorDomRef, prefixCls: prefixCls, inputElement: customizeInputElement, ref: selectorRef, id: id, showSearch: mergedShowSearch, autoClearSearchValue: autoClearSearchValue, mode: mode, activeDescendantId: activeDescendantId, tagRender: tagRender, values: displayValues, open: mergedOpen, onToggleOpen: onToggleOpen, activeValue: activeValue, searchValue: mergedSearchValue, onSearch: onInternalSearch, onSearchSubmit: onInternalSearchSubmit, onRemove: onSelectorRemove, tokenWithEnter: tokenWithEnter }))); // >>> Render var renderNode; // Render raw if (customizeRawInputElement) { renderNode = selectorNode; } else { renderNode = /*#__PURE__*/React.createElement("div", _extends({ className: mergedClassName }, domProps, { ref: containerRef, onMouseDown: onInternalMouseDown, onKeyDown: onInternalKeyDown, onKeyUp: onInternalKeyUp, onFocus: onContainerFocus, onBlur: onContainerBlur }), mockFocused && !mergedOpen && /*#__PURE__*/React.createElement("span", { style: { width: 0, height: 0, position: 'absolute', overflow: 'hidden', opacity: 0 }, "aria-live": "polite" }, "".concat(displayValues.map(function (_ref) { var label = _ref.label, value = _ref.value; return ['number', 'string'].includes(_typeof(label)) ? label : value; }).join(', '))), selectorNode, arrowNode, mergedAllowClear && clearNode); } return /*#__PURE__*/React.createElement(BaseSelectContext.Provider, { value: baseSelectContext }, renderNode); }); // Set display name for dev if (process.env.NODE_ENV !== 'production') { BaseSelect.displayName = 'BaseSelect'; } export default BaseSelect;