UNPKG

rsuite

Version:

A suite of react components

305 lines (300 loc) 9.45 kB
'use client'; import _extends from "@babel/runtime/helpers/esm/extends"; import React, { useEffect, useMemo } from 'react'; import isNil from 'lodash/isNil'; import CheckTreeNode from "./CheckTreeNode.js"; import IndentLine from "../Tree/IndentLine.js"; import SearchBox from "../internals/SearchBox/index.js"; import Box from "../internals/Box/index.js"; import useTreeSearch from "../Tree/hooks/useTreeSearch.js"; import useFocusTree from "../Tree/hooks/useFocusTree.js"; import useVirtualizedTreeData from "../Tree/hooks/useVirtualizedTreeData.js"; import useTreeCheckState from "./hooks/useTreeCheckState.js"; import useTreeNodeProps from "./hooks/useTreeNodeProps.js"; import { forwardRef } from "../internals/utils/index.js"; import { List, AutoSizer, defaultItemSize } from "../internals/Windowing/index.js"; import { useStyles, useCustom, useEventCallback } from "../internals/hooks/index.js"; import { getPathTowardsItem, getKeyParentMap } from "../internals/Tree/utils/index.js"; import { onMenuKeyDown } from "../internals/Picker/index.js"; import { TreeView } from "../internals/Tree/index.js"; import { hasGrandchild, isEveryFirstLevelNodeUncheckable, getFormattedTree, isNodeUncheckable } from "./utils.js"; import { hasVisibleChildren, getActiveItem, isExpand } from "../Tree/utils/index.js"; import { useTreeContextProps } from "../internals/Tree/TreeProvider.js"; /** * Props for the CheckTreeView component. */ const CheckTreeView = forwardRef((props, ref) => { const { as, className, classPrefix = 'check-tree', cascade = true, data = [], disabledItemValues = [], expandItemValues = [], height = 360, locale: overrideLocale, listProps, listRef, style, searchKeyword, showIndentLine, searchable, searchInputRef, uncheckableItemValues = [], loadingNodeValues = [], flattenedNodes = {}, searchBy, onChange, onSearch, onSelect, onSelectItem, onScroll, onExpand, onFocusItem, ...rest } = props; const { getLocale } = useCustom(); const { searchPlaceholder, noResultsText } = getLocale('Combobox', overrideLocale); const { childrenKey, valueKey, virtualized, scrollShadow } = useTreeContextProps(); const { prefix, merge, withPrefix } = useStyles(classPrefix); const { getCheckedValues } = useTreeCheckState({ cascade, flattenedNodes, uncheckableItemValues, disabledItemValues }); const handleSearchCallback = (value, _data, event) => { onSearch === null || onSearch === void 0 || onSearch(value, event); }; const { filteredData, keyword, setFilteredData, handleSearch } = useTreeSearch({ callback: handleSearchCallback, data, searchKeyword, searchBy }); const { focusItemValue, setFocusItemValue, onTreeKeydown, saveTreeNodeRef } = useFocusTree({ filteredData, disabledItemValues, expandItemValues, searchKeyword: keyword, flattenedNodes, onFocused: onFocusItem, onExpand }); const transformation = useVirtualizedTreeData(flattenedNodes, filteredData, { cascade, expandItemValues, searchKeyword: keyword, disabledItemValues }); /** * Get formatted nodes for render tree * @params render - renderNode function. only used when virtualized setting false */ const getFormattedNodes = render => { if (virtualized) { return transformation().filter(item => item.visible); } return getFormattedTree(flattenedNodes, filteredData, { childrenKey, cascade, disabledItemValues, valueKey }).map(node => render === null || render === void 0 ? void 0 : render(node, 1)).filter(item => item); }; const getTreeNodeProps = useTreeNodeProps({ uncheckableItemValues, disabledItemValues, loadingNodeValues, focusItemValue, flattenedNodes, keyword }); useEffect(() => { setFilteredData(data, keyword); }, [data, keyword, setFilteredData]); // TODO-Doma // Replace `getKeyParentMap` with `getParentMap` const itemParentMap = useMemo(() => getKeyParentMap(data, node => node[valueKey], node => node[childrenKey]), [childrenKey, data, valueKey]); const handleSelect = useEventCallback((node, event) => { const currentNode = node.refKey ? flattenedNodes[node.refKey] : null; if (!node || !currentNode) { return; } const checkedValues = getCheckedValues(node, !currentNode.check); const path = getPathTowardsItem(node, item => itemParentMap.get(item[valueKey])); setFocusItemValue(node[valueKey]); onChange === null || onChange === void 0 || onChange(checkedValues, event); onSelect === null || onSelect === void 0 || onSelect(node, checkedValues, event); onSelectItem === null || onSelectItem === void 0 || onSelectItem(node, path); }); const selectActiveItem = event => { if (isNil(focusItemValue)) return; const activeItem = getActiveItem(focusItemValue, flattenedNodes, valueKey); if (!isNodeUncheckable(activeItem, { uncheckableItemValues, valueKey }) && activeItem !== null) { handleSelect(activeItem, event); } }; const handleTreeKeyDown = useEventCallback(event => { onTreeKeydown(event); onMenuKeyDown(event, { enter: selectActiveItem }); }); const renderNode = (node, layer) => { const { visible, refKey, parent } = node; // when searching, all nodes should be expand const expanded = isExpand(keyword, expandItemValues.includes(node[valueKey])); if (!visible) { return null; } const children = node[childrenKey]; const hasChildren = keyword ? hasVisibleChildren(node, childrenKey) : Boolean(children); const treeNodeProps = { // The spread operator does not copy non-enumerable properties, // so we need to copy the `parent` property manually. ...getTreeNodeProps({ ...node, parent }), layer, expanded, hasChildren, onSelect: handleSelect, onExpand }; if (hasChildren) { layer += 1; const childClassName = merge(prefix('node-children'), { [prefix('node-expanded')]: expanded }); const nodes = children || []; return /*#__PURE__*/React.createElement("div", { className: childClassName, key: node[valueKey] }, /*#__PURE__*/React.createElement(CheckTreeNode, _extends({}, treeNodeProps, { treeItemRef: ref => saveTreeNodeRef(ref, refKey) })), /*#__PURE__*/React.createElement("div", { className: prefix('group'), role: "group" }, nodes.map(child => renderNode(child, layer)), showIndentLine && /*#__PURE__*/React.createElement(IndentLine, null))); } return /*#__PURE__*/React.createElement(CheckTreeNode, _extends({ key: node[valueKey], treeItemRef: ref => saveTreeNodeRef(ref, refKey) }, treeNodeProps)); }; const renderVirtualListNode = ({ index, style, data }) => { const node = data[index]; const { layer, refKey, visible, hasChildren, parent } = node; const expanded = isExpand(keyword, expandItemValues.includes(node[valueKey])); const treeNodeProps = { // The spread operator does not copy non-enumerable properties, // so we need to copy the `parent` property manually. ...getTreeNodeProps({ ...node, parent }), onSelect: handleSelect, onExpand, expanded, layer, hasChildren }; return visible && /*#__PURE__*/React.createElement(CheckTreeNode, _extends({ style: style, ref: ref => saveTreeNodeRef(ref, refKey) }, treeNodeProps)); }; const classes = merge(className, withPrefix({ 'without-children': !hasGrandchild(data, childrenKey), virtualized })); const formattedNodes = getFormattedNodes(renderNode); const treeNodesClass = merge(prefix('root'), { [prefix('all-uncheckable')]: isEveryFirstLevelNodeUncheckable(flattenedNodes, uncheckableItemValues, valueKey) }); return /*#__PURE__*/React.createElement(Box, { as: as, ref: ref, className: classes, style: style }, searchable ? /*#__PURE__*/React.createElement(SearchBox, { placeholder: searchPlaceholder, onChange: handleSearch, value: keyword, inputRef: searchInputRef }) : null, keyword && formattedNodes.length === 0 ? /*#__PURE__*/React.createElement("div", { className: prefix('empty') }, noResultsText) : null, /*#__PURE__*/React.createElement(TreeView, _extends({}, rest, { multiselectable: true, treeRootClassName: treeNodesClass, className: prefix('view'), onScroll: onScroll, onKeyDown: handleTreeKeyDown, height: height }), virtualized ? /*#__PURE__*/React.createElement(AutoSizer, { defaultHeight: height, style: { width: 'auto', height: 'auto' }, className: prefix('virt-auto-sizer') }, ({ height }) => /*#__PURE__*/React.createElement(List, _extends({ ref: listRef, height: height, itemSize: defaultItemSize, itemCount: formattedNodes.length, itemData: formattedNodes, className: prefix('virt-list'), scrollShadow: scrollShadow }, listProps), renderVirtualListNode)) : formattedNodes)); }); CheckTreeView.displayName = 'CheckTreeView'; export default CheckTreeView;