UNPKG

@rc-component/tree-select

Version:
363 lines (348 loc) 12.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _select = require("@rc-component/select"); var _tree = _interopRequireWildcard(require("@rc-component/tree")); var _KeyCode = _interopRequireDefault(require("@rc-component/util/lib/KeyCode")); var _useMemo = _interopRequireDefault(require("@rc-component/util/lib/hooks/useMemo")); var React = _interopRequireWildcard(require("react")); var _LegacyContext = _interopRequireDefault(require("./LegacyContext")); var _TreeSelectContext = _interopRequireDefault(require("./TreeSelectContext")); var _valueUtil = require("./utils/valueUtil"); var _util = require("@rc-component/util"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } const HIDDEN_STYLE = { width: 0, height: 0, display: 'flex', overflow: 'hidden', opacity: 0, border: 0, padding: 0, margin: 0 }; const OptionList = (_, ref) => { const { prefixCls, multiple, searchValue, toggleOpen, open, notFoundContent } = (0, _select.useBaseProps)(); const { virtual, listHeight, listItemHeight, listItemScrollOffset, treeData, fieldNames, onSelect, popupMatchSelectWidth, treeExpandAction, treeTitleRender, onPopupScroll, leftMaxCount, leafCountOnly, valueEntities, classNames: treeClassNames, styles } = React.useContext(_TreeSelectContext.default); const { checkable, checkedKeys, halfCheckedKeys, treeExpandedKeys, treeDefaultExpandAll, treeDefaultExpandedKeys, onTreeExpand, treeIcon, showTreeIcon, switcherIcon, treeLine, treeNodeFilterProp, loadData, treeLoadedKeys, treeMotion, onTreeLoad, keyEntities } = React.useContext(_LegacyContext.default); const treeRef = React.useRef(); const memoTreeData = (0, _useMemo.default)(() => treeData, // eslint-disable-next-line react-hooks/exhaustive-deps [open, treeData], (prev, next) => next[0] && prev[1] !== next[1]); // ========================== Values ========================== const mergedCheckedKeys = React.useMemo(() => { if (!checkable) { return null; } return { checked: checkedKeys, halfChecked: halfCheckedKeys }; }, [checkable, checkedKeys, halfCheckedKeys]); // ========================== Scroll ========================== React.useEffect(() => { // Single mode should scroll to current key if (open && !multiple && checkedKeys.length) { treeRef.current?.scrollTo({ key: checkedKeys[0] }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]); // ========================== Events ========================== const onListMouseDown = event => { event.preventDefault(); }; const onInternalSelect = (__, info) => { const { node } = info; if (checkable && (0, _valueUtil.isCheckDisabled)(node)) { return; } onSelect(node.key, { selected: !checkedKeys.includes(node.key) }); if (!multiple) { toggleOpen(false); } }; // =========================== Keys =========================== const [expandedKeys, setExpandedKeys] = React.useState(treeDefaultExpandedKeys); const [searchExpandedKeys, setSearchExpandedKeys] = React.useState(null); const mergedExpandedKeys = React.useMemo(() => { if (treeExpandedKeys) { return [...treeExpandedKeys]; } return searchValue ? searchExpandedKeys : expandedKeys; }, [expandedKeys, searchExpandedKeys, treeExpandedKeys, searchValue]); const onInternalExpand = keys => { setExpandedKeys(keys); setSearchExpandedKeys(keys); if (onTreeExpand) { onTreeExpand(keys); } }; // ========================== Search ========================== const lowerSearchValue = String(searchValue).toLowerCase(); const filterTreeNode = treeNode => { if (!lowerSearchValue) { return false; } return String(treeNode[treeNodeFilterProp]).toLowerCase().includes(lowerSearchValue); }; React.useEffect(() => { if (searchValue) { setSearchExpandedKeys((0, _valueUtil.getAllKeys)(treeData, fieldNames)); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchValue]); // ========================= Disabled ========================= // Cache disabled states in React state to ensure re-render when cache updates const [disabledCache, setDisabledCache] = React.useState(() => new Map()); React.useEffect(() => { if (leftMaxCount) { setDisabledCache(new Map()); } }, [leftMaxCount]); function getDisabledWithCache(node) { const value = node[fieldNames.value]; if (!disabledCache.has(value)) { const entity = valueEntities.get(value); const isLeaf = (entity.children || []).length === 0; if (!isLeaf) { const checkableChildren = entity.children.filter(childTreeNode => !childTreeNode.node.disabled && !childTreeNode.node.disableCheckbox && !checkedKeys.includes(childTreeNode.node[fieldNames.value])); const checkableChildrenCount = checkableChildren.length; disabledCache.set(value, checkableChildrenCount > leftMaxCount); } else { disabledCache.set(value, false); } } return disabledCache.get(value); } const nodeDisabled = (0, _util.useEvent)(node => { const nodeValue = node[fieldNames.value]; if (checkedKeys.includes(nodeValue)) { return false; } if (leftMaxCount === null) { return false; } if (leftMaxCount <= 0) { return true; } // This is a low performance calculation if (leafCountOnly && leftMaxCount) { return getDisabledWithCache(node); } return false; }); // ========================== Get First Selectable Node ========================== const getFirstMatchingNode = nodes => { for (const node of nodes) { if (node.disabled || node.selectable === false) { continue; } if (searchValue) { if (filterTreeNode(node)) { return node; } } else { return node; } if (node[fieldNames.children]) { const matchInChildren = getFirstMatchingNode(node[fieldNames.children]); if (matchInChildren) { return matchInChildren; } } } return null; }; // ========================== Active ========================== const [activeKey, setActiveKey] = React.useState(null); const activeEntity = keyEntities[activeKey]; React.useEffect(() => { if (!open) { return; } let nextActiveKey = null; const getFirstNode = () => { const firstNode = getFirstMatchingNode(memoTreeData); return firstNode ? firstNode[fieldNames.value] : null; }; // single mode active first checked node if (!multiple && checkedKeys.length && !searchValue) { nextActiveKey = checkedKeys[0]; } else { nextActiveKey = getFirstNode(); } setActiveKey(nextActiveKey); // eslint-disable-next-line react-hooks/exhaustive-deps }, [open, searchValue]); // ========================= Keyboard ========================= React.useImperativeHandle(ref, () => ({ scrollTo: treeRef.current?.scrollTo, onKeyDown: event => { const { which } = event; switch (which) { // >>> Arrow keys case _KeyCode.default.UP: case _KeyCode.default.DOWN: case _KeyCode.default.LEFT: case _KeyCode.default.RIGHT: treeRef.current?.onKeyDown(event); break; // >>> Select item case _KeyCode.default.ENTER: { if (activeEntity) { const isNodeDisabled = nodeDisabled(activeEntity.node); const { selectable, value, disabled } = activeEntity?.node || {}; if (selectable !== false && !disabled && !isNodeDisabled) { onInternalSelect(null, { node: { key: activeKey }, selected: !checkedKeys.includes(value) }); } } break; } // >>> Close case _KeyCode.default.ESC: { toggleOpen(false); } } }, onKeyUp: () => {} })); const hasLoadDataFn = (0, _useMemo.default)(() => searchValue ? false : true, // eslint-disable-next-line react-hooks/exhaustive-deps [searchValue, treeExpandedKeys || expandedKeys], ([preSearchValue], [nextSearchValue, nextExcludeSearchExpandedKeys]) => preSearchValue !== nextSearchValue && !!(nextSearchValue || nextExcludeSearchExpandedKeys)); const syncLoadData = hasLoadDataFn ? loadData : null; // ========================== Render ========================== if (memoTreeData.length === 0) { return /*#__PURE__*/React.createElement("div", { role: "listbox", className: `${prefixCls}-empty`, onMouseDown: onListMouseDown }, notFoundContent); } const treeProps = { fieldNames }; if (treeLoadedKeys) { treeProps.loadedKeys = treeLoadedKeys; } if (mergedExpandedKeys) { treeProps.expandedKeys = mergedExpandedKeys; } return /*#__PURE__*/React.createElement("div", { onMouseDown: onListMouseDown }, activeEntity && open && /*#__PURE__*/React.createElement("span", { style: HIDDEN_STYLE, "aria-live": "assertive" }, activeEntity.node.value), /*#__PURE__*/React.createElement(_tree.UnstableContext.Provider, { value: { nodeDisabled } }, /*#__PURE__*/React.createElement(_tree.default, _extends({ classNames: treeClassNames?.popup, styles: styles?.popup, ref: treeRef, focusable: false, prefixCls: `${prefixCls}-tree`, treeData: memoTreeData, height: listHeight, itemHeight: listItemHeight, itemScrollOffset: listItemScrollOffset, virtual: virtual !== false && popupMatchSelectWidth !== false, multiple: multiple, icon: treeIcon, showIcon: showTreeIcon, switcherIcon: switcherIcon, showLine: treeLine, loadData: syncLoadData, motion: treeMotion, activeKey: activeKey // We handle keys by out instead tree self , checkable: checkable, checkStrictly: true, checkedKeys: mergedCheckedKeys, selectedKeys: !checkable ? checkedKeys : [], defaultExpandAll: treeDefaultExpandAll, titleRender: treeTitleRender }, treeProps, { // Proxy event out onActiveChange: setActiveKey, onSelect: onInternalSelect, onCheck: onInternalSelect, onExpand: onInternalExpand, onLoad: onTreeLoad, filterTreeNode: filterTreeNode, expandAction: treeExpandAction, onScroll: onPopupScroll })))); }; const RefOptionList = /*#__PURE__*/React.forwardRef(OptionList); if (process.env.NODE_ENV !== 'production') { RefOptionList.displayName = 'OptionList'; } var _default = exports.default = RefOptionList;