UNPKG

acud

Version:

acg react 组件库

518 lines (517 loc) • 20.7 kB
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty"; import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray"; import _extends from "@babel/runtime/helpers/esm/extends"; import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; var __rest = this && this.__rest || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import * as React from 'react'; import KeyCode from "rc-util/es/KeyCode"; import pickAttrs from "rc-util/es/pickAttrs"; import useMemo from "rc-util/es/hooks/useMemo"; import classNames from 'classnames'; import OutlinedRight from "acud-icon/es/icons/OutlinedRight"; import OutlinedDown from "acud-icon/es/icons/OutlinedDown"; import { toRawString } from './utils/valueUtil'; import List from '../../virtual-list'; import Checkbox from '../../checkbox'; import { useLocaleReceiver } from '../../locale-provider/LocaleReceiver'; var formatContent = function formatContent(content, searchValue, prefixCls) { var lowerContent = toRawString(content).toLowerCase(); var lowerSearch = searchValue.toLowerCase(); var searchStartIndex = lowerContent.indexOf(lowerSearch); var len = lowerSearch.length; // 存在搜索值不在content的情况,比如value与children不一致时 if (searchStartIndex === -1) { return /*#__PURE__*/React.createElement(React.Fragment, null, content); } return /*#__PURE__*/React.createElement(React.Fragment, null, content.toString().slice(0, searchStartIndex), /*#__PURE__*/React.createElement("span", { className: "".concat(prefixCls, "-search-highlight") }, content.toString().slice(searchStartIndex, searchStartIndex + len)), content.toString().slice(searchStartIndex + len)); }; /** * {afterSearch} * ;list of option display. * Will fallback to dom if use customize render. */ // const OptionList: React.RefForwardingComponent< var OptionList = function OptionList(_ref, ref) { var prefixCls = _ref.prefixCls, id = _ref.id, flattenOptions = _ref.flattenOptions, childrenAsData = _ref.childrenAsData, values = _ref.values, searchValue = _ref.searchValue, multiple = _ref.multiple, defaultActiveFirstOption = _ref.defaultActiveFirstOption, height = _ref.height, itemHeight = _ref.itemHeight, notFoundContent = _ref.notFoundContent, mode = _ref.mode, open = _ref.open, virtual = _ref.virtual, _ref$keepExpand = _ref.keepExpand, keepExpand = _ref$keepExpand === void 0 ? false : _ref$keepExpand, _ref$defaultExpandGro = _ref.defaultExpandGroupKey, defaultExpandGroupKey = _ref$defaultExpandGro === void 0 ? false : _ref$defaultExpandGro, _ref$showTitle = _ref.showTitle, showTitle = _ref$showTitle === void 0 ? false : _ref$showTitle, _ref$showSelectAll = _ref.showSelectAll, showSelectAll = _ref$showSelectAll === void 0 ? true : _ref$showSelectAll, optionRender = _ref.optionRender, renderEmpty = _ref.renderEmpty, onSelect = _ref.onSelect, onToggleOpen = _ref.onToggleOpen, onActiveValue = _ref.onActiveValue, onScroll = _ref.onScroll, onMouseEnter = _ref.onMouseEnter, triggerChange = _ref.triggerChange, setInnerOpen = _ref.setInnerOpen, onExpandChange = _ref.onExpandChange, cleanSearchValue = _ref.cleanSearchValue; var itemPrefixCls = "".concat(prefixCls, "-item"); var _useLocaleReceiver = useLocaleReceiver('Select'), _useLocaleReceiver2 = _slicedToArray(_useLocaleReceiver, 1), selectLocale = _useLocaleReceiver2[0]; var memoFlattenOptions = useMemo(function () { return flattenOptions; }, [open, flattenOptions], function (prev, next) { return next[0] && prev[1] !== next[1]; }); // =========================== List =========================== var listRef = React.useRef(null); var onListMouseDown = function onListMouseDown(event) { event.preventDefault(); }; var scrollIntoView = function scrollIntoView(index) { if (listRef.current) { listRef.current.scrollTo({ index: index }); } }; // ========================== Active ========================== var getEnabledActiveIndex = function getEnabledActiveIndex(index) { var offset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; var len = memoFlattenOptions.length; for (var i = 0; i < len; i += 1) { var current = (index + i * offset + len) % len; var _memoFlattenOptions$c = memoFlattenOptions[current], group = _memoFlattenOptions$c.group, data = _memoFlattenOptions$c.data; if (!group && !data.disabled) { return current; } } return -1; }; var _React$useState = React.useState(function () { return getEnabledActiveIndex(0); }), _React$useState2 = _slicedToArray(_React$useState, 2), activeIndex = _React$useState2[0], setActiveIndex = _React$useState2[1]; var setActive = function setActive(index) { var fromKeyboard = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; setActiveIndex(index); var info = { source: fromKeyboard ? 'keyboard' : 'mouse' }; // Trigger active event var flattenItem = memoFlattenOptions[index]; if (!flattenItem) { onActiveValue(null, -1, info); return; } onActiveValue(flattenItem.data.value, index, info); }; // Auto active first item when list length or searchValue changed React.useEffect(function () { setActive(defaultActiveFirstOption !== false ? getEnabledActiveIndex(0) : -1); // eslint-disable-next-line react-hooks/exhaustive-deps }, [memoFlattenOptions.length, searchValue]); // Auto scroll to item position in single mode React.useEffect(function () { var _a; /** * React will skip `onChange` when component update. * `setActive` function will call root accessibility state update which makes re-render. * So we need to delay to let Input component trigger onChange first. */ var timeoutId = setTimeout(function () { if (!multiple && open && values.size === 1) { var value = Array.from(values)[0]; var index = memoFlattenOptions.findIndex(function (_ref2) { var data = _ref2.data; return data.value === value; }); setActive(index); scrollIntoView(index); } }); // Force trigger scrollbar visible when open if (open) { (_a = listRef.current) === null || _a === void 0 ? void 0 : _a.scrollTo(undefined); } return function () { return clearTimeout(timeoutId); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]); // ========================== Values ========================== var onSelectValue = function onSelectValue(value) { if (value !== undefined) { onSelect(value, { selected: !values.has(value) }); } // Single mode should always close by select if (!multiple) { onToggleOpen(false); } }; // ========================= Keyboard ========================= React.useImperativeHandle(ref, function () { return { onKeyDown: function onKeyDown(event) { var which = event.which; switch (which) { // >>> Arrow keys case KeyCode.UP: case KeyCode.DOWN: { var offset = 0; if (which === KeyCode.UP) { offset = -1; } else if (which === KeyCode.DOWN) { offset = 1; } if (offset !== 0) { var nextActiveIndex = getEnabledActiveIndex(activeIndex + offset, offset); scrollIntoView(nextActiveIndex); setActive(nextActiveIndex, true); } break; } // >>> Select case KeyCode.ENTER: { // value var item = memoFlattenOptions[activeIndex]; if (item && !item.data.disabled) { onSelectValue(item.data.value); } else { onSelectValue(undefined); } if (open) { event.preventDefault(); } break; } // >>> Close case KeyCode.ESC: { onToggleOpen(false); if (open) { event.stopPropagation(); } } } }, onKeyUp: function onKeyUp() {}, scrollTo: function scrollTo(index) { scrollIntoView(index); } }; }); // ===================== Empty ===================== var mergedNotFound; if (notFoundContent !== undefined) { mergedNotFound = notFoundContent; } else if (mode === 'combobox') { mergedNotFound = null; } else { mergedNotFound = renderEmpty('Select', searchValue ? selectLocale === null || selectLocale === void 0 ? void 0 : selectLocale.filterEmpty : undefined); } function renderItem(index) { var item = memoFlattenOptions[index]; if (!item) { return null; } var itemData = item.data || {}; var value = itemData.value, label = itemData.label, children = itemData.children; var attrs = pickAttrs(itemData, true); var mergedLabel = childrenAsData ? children : label; return item ? /*#__PURE__*/React.createElement("div", _extends({ "aria-label": typeof mergedLabel === 'string' ? mergedLabel : null }, attrs, { key: index, role: "option", id: "".concat(id, "_list_").concat(index), "aria-selected": values.has(value) }), value) : null; } // 是否全选状态 var selectAllChecked = false; // 半选状态 var indeterminate = false; // 所有enable项的值 var enableAllValues = []; // 已选中的disabled项的值 var disabledValues = []; // 已选中的enable项的值 var enableValues = []; // 所有group key var groupKeys = []; if (multiple) { var enableOptions = memoFlattenOptions.filter(function (option) { return !option.data.disabled && !option.group; }); var disabledOptions = memoFlattenOptions.filter(function (option) { return option.data.disabled; }); enableAllValues = enableOptions.map(function (option) { return option.value; }); var enableOptionsValues = new Set(enableOptions.map(function (option) { return option.value; })); enableValues = _toConsumableArray(values).filter(function (value) { return enableOptionsValues.has(value); }); var disabledOptionsValues = new Set(disabledOptions.map(function (option) { return option.value; })); disabledValues = _toConsumableArray(values).filter(function (value) { return disabledOptionsValues.has(value); }); selectAllChecked = enableValues.length === enableOptions.length; indeterminate = !selectAllChecked && !!enableValues.length; groupKeys = memoFlattenOptions.filter(function (option) { return option.group; }).map(function (option) { return option.key; }); } var expandGroupKeys = []; if (defaultExpandGroupKey === true) { // 默认展开所有折叠项 expandGroupKeys = groupKeys; } else if (Array.isArray(defaultExpandGroupKey)) { expandGroupKeys = defaultExpandGroupKey; } else if (typeof defaultExpandGroupKey === 'string' || typeof defaultExpandGroupKey === 'number') { expandGroupKeys = [defaultExpandGroupKey]; } var _React$useState3 = React.useState(expandGroupKeys), _React$useState4 = _slicedToArray(_React$useState3, 2), expandGroupKey = _React$useState4[0], setExpandGroupKey = _React$useState4[1]; var handleCheckAll = function handleCheckAll() { if (searchValue === '' || searchValue === undefined) { triggerChange(selectAllChecked ? disabledValues : [].concat(_toConsumableArray(enableAllValues), _toConsumableArray(disabledValues))); } else { var changeSelectedValues; if (selectAllChecked) { var enableAllValuesSet = new Set(enableAllValues); changeSelectedValues = _toConsumableArray(values).filter(function (v) { return !enableAllValuesSet.has(v); }); } else { changeSelectedValues = Array.from(new Set([].concat(_toConsumableArray(values), _toConsumableArray(enableAllValues)))); } triggerChange(changeSelectedValues); } if (!selectAllChecked && setInnerOpen) { setInnerOpen(false); } cleanSearchValue && cleanSearchValue(); }; var getCheckStatus = function getCheckStatus(groupKey, type) { var groupOptions = memoFlattenOptions.filter(function (option) { return option.groupKey === groupKey; }); var groupEnableOptions = groupOptions.filter(function (option) { return !option.data.disabled; }); if (type === 'all') { return groupEnableOptions.every(function (option) { return values.has(option.value); }); } else if (type === 'indeterminate') { return !groupEnableOptions.every(function (option) { return values.has(option.value); }) && groupEnableOptions.some(function (option) { return values.has(option.value); }); } return false; }; var isKeepExpand = !multiple && keepExpand; var handleCheckGroup = function handleCheckGroup(groupKey) { if (!multiple) { return; } var groupOptions = memoFlattenOptions.filter(function (option) { return !option.disabled && option.groupKey === groupKey; }); var groupEnableOptions = groupOptions.filter(function (option) { return !option.data.disabled; }); var allValues = groupEnableOptions.map(function (option) { return option.value; }); var checkAll = getCheckStatus(groupKey, 'all'); if (checkAll) { var allValuesSet = new Set(allValues); triggerChange(_toConsumableArray(Array.from(values).filter(function (value) { return !allValuesSet.has(value); }))); } else { triggerChange(Array.from(new Set([].concat(_toConsumableArray(values), _toConsumableArray(allValues))))); } cleanSearchValue && cleanSearchValue(); }; var renderOption = function renderOption(data, values, groupOption, label, itemIndex, groupKey) { var _a = data, disabled = _a.disabled, value = _a.value, title = _a.title, children = _a.children, style = _a.style, className = _a.className, maxLabelLength = _a.maxLabelLength, extra = _a.extra, otherProps = __rest(_a, ["disabled", "value", "title", "children", "style", "className", "maxLabelLength", "extra"]); // Option var selected = values.has(value); var optionPrefixCls = "".concat(itemPrefixCls, "-option"); var optionClassName = classNames(itemPrefixCls, optionPrefixCls, className, _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({}, "".concat(optionPrefixCls, "-grouped"), groupOption), "".concat(optionPrefixCls, "-grouped-keep-expand"), isKeepExpand), "".concat(optionPrefixCls, "-active"), activeIndex === itemIndex && !disabled), "".concat(optionPrefixCls, "-disabled"), disabled), "".concat(optionPrefixCls, "-selected"), selected)); var mergedLabel = childrenAsData ? children : label; // const iconVisible = !menuItemSelectedIcon || typeof menuItemSelectedIcon === 'function' || selected; var content = mergedLabel || value; var optionTitle; if (showTitle) { optionTitle = typeof content === 'string' || typeof content === 'number' ? content.toString() : undefined; if (title !== undefined) { optionTitle = title; } } var fContent = (typeof content === 'string' || typeof content === 'number') && searchValue ? formatContent(content, searchValue, prefixCls) : content; if (optionRender) { fContent = optionRender(fContent, data); } return /*#__PURE__*/React.createElement("div", _extends({}, otherProps, { "aria-selected": selected, className: optionClassName, title: optionTitle, onMouseMove: function onMouseMove() { if (activeIndex === itemIndex || disabled) { return; } setActive(itemIndex); }, onMouseLeave: function onMouseLeave() { setActive(-1); }, onClick: function onClick() { if (!disabled) { onSelectValue(value); } }, style: _extends({ display: groupOption && !expandGroupKey.includes(groupKey) && !isKeepExpand ? 'none' : '' }, style) }), multiple && /*#__PURE__*/React.createElement(Checkbox, { checked: selected, disabled: disabled }), /*#__PURE__*/React.createElement("div", { className: "".concat(optionPrefixCls, "-content") }, fContent), extra); }; var disabledVirtual = React.useMemo(function () { return !virtual || (mode === 'multiple' || mode !== 'multiple' && !keepExpand) && memoFlattenOptions.some(function (option) { return option.group; }); }, [virtual, mode, keepExpand, memoFlattenOptions]); var handleExpandChange = React.useCallback(function (e, key) { var gIndex = expandGroupKey.indexOf(key); var newGroupKey = _toConsumableArray(expandGroupKey); gIndex !== -1 ? newGroupKey.splice(gIndex, 1) : newGroupKey.push(key); setExpandGroupKey(newGroupKey); onExpandChange === null || onExpandChange === void 0 ? void 0 : onExpandChange(newGroupKey); e.stopPropagation(); }, [expandGroupKey, onExpandChange]); // ========================== Render ========================== if (memoFlattenOptions.length === 0) { return /*#__PURE__*/React.createElement("div", { role: "listbox", id: "".concat(id, "_list"), className: "".concat(itemPrefixCls, "-empty"), onMouseDown: onListMouseDown }, mergedNotFound); } return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", { role: "listbox", id: "".concat(id, "_list"), style: { height: 0, width: 0, overflow: 'hidden' } }, renderItem(activeIndex - 1), renderItem(activeIndex), renderItem(activeIndex + 1)), multiple && showSelectAll && /*#__PURE__*/React.createElement("div", { className: classNames("".concat(itemPrefixCls, "-checkall")), onClick: handleCheckAll }, /*#__PURE__*/React.createElement(Checkbox, { indeterminate: indeterminate, checked: selectAllChecked }), /*#__PURE__*/React.createElement("div", { className: "".concat(itemPrefixCls, "-option-content") }, selectLocale.checkAll)), /*#__PURE__*/React.createElement(List, { itemKey: "key", ref: listRef, data: memoFlattenOptions, height: height, itemHeight: itemHeight, fullHeight: false, onMouseDown: onListMouseDown, onScroll: onScroll, virtual: !disabledVirtual, onMouseEnter: onMouseEnter }, function (_ref3, itemIndex) { var group = _ref3.group, groupOption = _ref3.groupOption, data = _ref3.data, groupKey = _ref3.groupKey; var label = data.label, key = data.key; // Group if (group) { return /*#__PURE__*/React.createElement("div", { className: classNames(itemPrefixCls, "".concat(itemPrefixCls, "-group"), _defineProperty({}, "".concat(itemPrefixCls, "-group-keep-expand"), isKeepExpand)), onClick: function onClick() { return handleCheckGroup(key); } }, !isKeepExpand && /*#__PURE__*/React.createElement("span", { className: "".concat(itemPrefixCls, "-group-arrow"), onClick: function onClick(e) { handleExpandChange(e, key); } }, expandGroupKey.includes(key) ? /*#__PURE__*/React.createElement(OutlinedDown, null) : /*#__PURE__*/React.createElement(OutlinedRight, null)), multiple && /*#__PURE__*/React.createElement(Checkbox, { className: "".concat(itemPrefixCls, "-group-checkbox"), indeterminate: getCheckStatus(key, 'indeterminate'), checked: getCheckStatus(key, 'all') }), /*#__PURE__*/React.createElement("span", null, label !== undefined ? label : key)); } return renderOption(data, values, groupOption, label, itemIndex, groupKey); })); }; var RefOptionList = /*#__PURE__*/React.forwardRef(OptionList); RefOptionList.displayName = 'OptionList'; export default RefOptionList;