UNPKG

bonree-cascader

Version:

cascade select ui component for react

410 lines (333 loc) 13.5 kB
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty"; import _extends from "@babel/runtime/helpers/esm/extends"; import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2"; import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; /* eslint-disable default-case */ import * as React from 'react'; import classNames from 'classnames'; import KeyCode from "rc-util/es/KeyCode"; import { SelectContext } from "bonree-tree-select/es/Context"; import Column from './Column'; import { ALL_KEY, DATA_EMPTY, isLeaf, restoreCompatibleValue } from '../util'; import CascaderContext from '../context'; import useSearchResult from '../hooks/useSearchResult'; var RefOptionList = /*#__PURE__*/React.forwardRef(function (props, ref) { var _optionColumns$, _optionColumns$$optio, _classNames; var prefixCls = props.prefixCls, options = props.options, onSelect = props.onSelect, multiple = props.multiple, open = props.open, flattenOptions = props.flattenOptions, searchValue = props.searchValue, onToggleOpen = props.onToggleOpen, notFoundContent = props.notFoundContent, direction = props.direction, showAll = props.showAll, selectAllText = props.selectAllText, onSelectAll = props.onSelectAll, _props$disabledShowAl = props.disabledShowAll, disabledShowAll = _props$disabledShowAl === void 0 ? false : _props$disabledShowAl; var containerRef = React.useRef(); var rtl = direction === 'rtl'; var _React$useContext = React.useContext(SelectContext), checkedKeys = _React$useContext.checkedKeys, halfCheckedKeys = _React$useContext.halfCheckedKeys; var _React$useContext2 = React.useContext(CascaderContext), changeOnSelect = _React$useContext2.changeOnSelect, expandTrigger = _React$useContext2.expandTrigger, fieldNames = _React$useContext2.fieldNames, loadData = _React$useContext2.loadData, search = _React$useContext2.search, dropdownPrefixCls = _React$useContext2.dropdownPrefixCls, treeCheckStrictly = _React$useContext2.treeCheckStrictly; var mergedPrefixCls = dropdownPrefixCls || prefixCls; // ========================= loadData ========================= // const [loadingKeys, setLoadingKeys] = React.useState([]); var internalLoadData = function internalLoadData(pathValue) { // Do not load when search if (!loadData || searchValue) { return; } var entity = flattenOptions.find(function (flattenOption) { return flattenOption.data.value === pathValue; }); if (entity && !isLeaf(entity.data.node)) { var _restoreCompatibleVal = restoreCompatibleValue(entity, fieldNames), optionList = _restoreCompatibleVal.options; var rawOptionList = optionList.map(function (opt) { return opt.node; }); // setLoadingKeys(keys => [...keys, entity.key]); loadData(rawOptionList); } }; // zombieJ: This is bad. We should make this same as `rc-tree` to use Promise instead. // React.useEffect(() => { // if (loadingKeys.length) { // loadingKeys.forEach(loadingKey => { // const option = flattenOptions.find(opt => opt.value === loadingKey); // if (option.data.children || option.data.isLeaf === true) { // setLoadingKeys(keys => keys.filter(key => key !== loadingKey)); // } // }); // } // }, [flattenOptions, loadingKeys]); // ========================== Values ========================== var checkedSet = React.useMemo(function () { return new Set(checkedKeys); }, [checkedKeys]); var halfCheckedSet = React.useMemo(function () { return new Set(halfCheckedKeys); }, [halfCheckedKeys]); // =========================== Open =========================== var _React$useState = React.useState(null), _React$useState2 = _slicedToArray(_React$useState, 2), openFinalValue = _React$useState2[0], setOpenFinalValue = _React$useState2[1]; var mergedOpenPath = React.useMemo(function () { if (searchValue) { return openFinalValue !== undefined && openFinalValue !== null ? [openFinalValue] : []; } var entity = flattenOptions.find(function (flattenOption) { return flattenOption.data.value === openFinalValue; }); if (entity) { var _restoreCompatibleVal2 = restoreCompatibleValue(entity, fieldNames), path = _restoreCompatibleVal2.path; return path; } return []; }, [openFinalValue, flattenOptions, searchValue]); React.useEffect(function () { if (open) { var nextOpenPath = null; if (!multiple && checkedKeys.length) { var entity = flattenOptions.find(function (flattenOption) { return flattenOption.data.value === checkedKeys[0]; }); if (entity) { nextOpenPath = entity.data.value; } } setOpenFinalValue(nextOpenPath); } }, [open]); // =========================== Path =========================== var onPathOpen = function onPathOpen(index, pathValue) { setOpenFinalValue(pathValue); // Trigger loadData internalLoadData(pathValue); }; var onPathSelect = function onPathSelect(pathValue, leaf) { onSelect(pathValue, { selected: !checkedSet.has(pathValue) }); if (!multiple && (leaf || changeOnSelect && expandTrigger === 'hover')) { onToggleOpen(false); } }; var getPathList = function getPathList(pathList) { var currentOptions = options; var _loop = function _loop(i) { currentOptions = (currentOptions || []).find(function (option) { return option.value === pathList[i]; }).children; }; for (var i = 0; i < pathList.length; i += 1) { _loop(i); } return currentOptions; }; // ========================== Search ========================== var searchOptions = useSearchResult(_objectSpread(_objectSpread({}, props), {}, { prefixCls: mergedPrefixCls, fieldNames: fieldNames, changeOnSelect: changeOnSelect, searchConfig: search, treeCheckStrictly: treeCheckStrictly })); // ========================== Column ========================== var optionColumns = React.useMemo(function () { if (searchValue) { return [{ options: searchOptions }]; } var rawOptionColumns = []; for (var i = 0; i <= mergedOpenPath.length; i += 1) { var subOptions = getPathList(mergedOpenPath.slice(0, i)); if (subOptions) { rawOptionColumns.push({ options: subOptions }); } else { break; } } return rawOptionColumns; }, [searchValue, searchOptions, mergedOpenPath]); // ========================= Keyboard ========================= var getActiveOption = function getActiveOption(activeColumnIndex, offset) { var _optionColumns$active; var pathActiveValue = mergedOpenPath[activeColumnIndex]; var currentOptions = ((_optionColumns$active = optionColumns[activeColumnIndex]) === null || _optionColumns$active === void 0 ? void 0 : _optionColumns$active.options) || []; var activeOptionIndex = currentOptions.findIndex(function (opt) { return opt.value === pathActiveValue; }); var len = currentOptions.length; // Last one is special since -1 may back 2 offset if (offset === -1 && activeOptionIndex === -1) { activeOptionIndex = len; } for (var i = 1; i <= len; i += 1) { var current = (activeOptionIndex + i * offset + len) % len; var option = currentOptions[current]; if (!option.disabled) { return option; } } return null; }; var prevColumn = function prevColumn() { if (mergedOpenPath.length <= 1) { onToggleOpen(false); } setOpenFinalValue(mergedOpenPath[mergedOpenPath.length - 2]); }; var nextColumn = function nextColumn() { var nextColumnIndex = mergedOpenPath.length; var nextActiveOption = getActiveOption(nextColumnIndex, 1); if (nextActiveOption) { onPathOpen(nextColumnIndex, nextActiveOption.value); } }; React.useImperativeHandle(ref, function () { return { // scrollTo: treeRef.current?.scrollTo, 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 activeColumnIndex = Math.max(mergedOpenPath.length - 1, 0); var nextActiveOption = getActiveOption(activeColumnIndex, offset); if (nextActiveOption) { var _containerRef$current, _ele$scrollIntoView; var ele = (_containerRef$current = containerRef.current) === null || _containerRef$current === void 0 ? void 0 : _containerRef$current.querySelector("li[data-value=\"".concat(nextActiveOption.value, "\"]")); ele === null || ele === void 0 ? void 0 : (_ele$scrollIntoView = ele.scrollIntoView) === null || _ele$scrollIntoView === void 0 ? void 0 : _ele$scrollIntoView.call(ele, { block: 'nearest' }); onPathOpen(activeColumnIndex, nextActiveOption.value); } } break; } case KeyCode.LEFT: { if (rtl) { nextColumn(); } else { prevColumn(); } break; } case KeyCode.RIGHT: { if (rtl) { prevColumn(); } else { nextColumn(); } break; } case KeyCode.BACKSPACE: { if (!searchValue) { prevColumn(); } break; } // >>> Select case KeyCode.ENTER: { var _optionColumns, _optionColumns$option; var lastValue = mergedOpenPath[mergedOpenPath.length - 1]; var option = (_optionColumns = optionColumns[mergedOpenPath.length - 1]) === null || _optionColumns === void 0 ? void 0 : (_optionColumns$option = _optionColumns.options) === null || _optionColumns$option === void 0 ? void 0 : _optionColumns$option.find(function (opt) { return opt.value === lastValue; }); // Skip when no select if (option) { var leaf = isLeaf(option); if (multiple || changeOnSelect || leaf) { onPathSelect(lastValue, leaf); } // Close for changeOnSelect if (changeOnSelect) { onToggleOpen(false); } } break; } // >>> Close case KeyCode.ESC: { onToggleOpen(false); if (open) { event.stopPropagation(); } } } }, onKeyUp: function onKeyUp() {} }; }); // ========================== Render ========================== var columnProps = _objectSpread(_objectSpread({}, props), {}, { onOpen: onPathOpen, onSelect: onPathSelect, onToggleOpen: onToggleOpen, checkedSet: checkedSet, halfCheckedSet: halfCheckedSet, openFinalValue: openFinalValue, onSelectAll: onSelectAll // loadingKeys, }); // >>>>> Empty var isEmpty = !((_optionColumns$ = optionColumns[0]) === null || _optionColumns$ === void 0 ? void 0 : (_optionColumns$$optio = _optionColumns$.options) === null || _optionColumns$$optio === void 0 ? void 0 : _optionColumns$$optio.length); var emptyList = [{ title: notFoundContent, value: DATA_EMPTY, disabled: true, node: null }]; // >>>>> Columns var mergedOptionColumns = isEmpty ? [{ options: emptyList }] : optionColumns; // 如果是第一层级且需要显示"全部"选项且treeCheckStrictly为true,则在选项列表前插入"全部"选项 if (!isEmpty && showAll && multiple && treeCheckStrictly) { var allOption = { title: selectAllText, value: ALL_KEY, disabled: disabledShowAll, node: { label: selectAllText, value: ALL_KEY } }; if (mergedOptionColumns[0].options[0].value === ALL_KEY) { mergedOptionColumns[0].options.shift(); mergedOptionColumns[0].options.unshift(allOption); } else { mergedOptionColumns[0].options.unshift(allOption); } } var columnNodes = mergedOptionColumns.map(function (col, index) { return /*#__PURE__*/React.createElement(Column, _extends({ key: index, index: index }, columnProps, { prefixCls: mergedPrefixCls, options: col.options, openKey: mergedOpenPath[index] })); }); // >>>>> Render return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", { className: classNames("".concat(mergedPrefixCls, "-menus"), (_classNames = {}, _defineProperty(_classNames, "".concat(mergedPrefixCls, "-menu-empty"), isEmpty), _defineProperty(_classNames, "".concat(mergedPrefixCls, "-rtl"), rtl), _classNames)), ref: containerRef }, columnNodes)); }); export default RefOptionList;