@rc-component/tree-select
Version:
tree-select ui component for react
363 lines (348 loc) • 12.2 kB
JavaScript
"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;