UNPKG

rsuite

Version:

A suite of react components

139 lines (135 loc) 4.9 kB
'use client'; import { useCallback } from 'react'; import isNil from 'lodash/isNil'; import cloneDeep from 'lodash/cloneDeep'; import { useEventCallback } from "../../internals/hooks/index.js"; import { useItemDataKeys } from "../../internals/Tree/TreeProvider.js"; import { isEveryChildChecked, getDisabledState } from "../utils.js"; function useTreeCheckState(props) { const { cascade, flattenedNodes, uncheckableItemValues, disabledItemValues = [] } = props; const { valueKey, childrenKey } = useItemDataKeys(); const checkParentNode = useEventCallback((nodes, node, checked) => { const currentNode = node.refKey ? nodes[node.refKey] : null; if (cascade && currentNode) { if (!checked) { currentNode.check = checked; currentNode.checkAll = checked; } else { if (isEveryChildChecked(currentNode, { nodes, childrenKey, disabledItemValues, valueKey })) { currentNode.check = true; currentNode.checkAll = true; } else { currentNode.check = false; currentNode.checkAll = false; } } if (currentNode.parent) { checkParentNode(nodes, currentNode.parent, checked); } } }); /** * Recursively checks if a node has any disabled descendants. * This is used to determine if a parent node's checkAll state should be true. * If any descendant is disabled, checkAll must be false because not all descendants can be checked. * @param nodes - The flattened tree node map * @param node - The node to check for disabled descendants * @returns true if any descendant (at any depth) is disabled, false otherwise */ const hasDisabledDescendant = useEventCallback((nodes, node) => { if (!node[childrenKey] || !node[childrenKey].length) { return false; } return node[childrenKey].some(child => { const isChildDisabled = getDisabledState(nodes, child, { disabledItemValues, valueKey }); if (isChildDisabled) { return true; } // Recursively check descendants return hasDisabledDescendant(nodes, child); }); }); const checkChildNode = useEventCallback((nodes, node, isChecked) => { const currentNode = node.refKey ? nodes[node.refKey] : null; if (!currentNode) { return; } // Check if the current node is disabled const isDisabled = getDisabledState(nodes, node, { disabledItemValues, valueKey }); // Skip checking disabled nodes if (isDisabled) { return; } currentNode.check = isChecked; if (!currentNode[childrenKey] || !currentNode[childrenKey].length || !cascade) { currentNode.checkAll = false; } else { // Check if any descendant (not just direct children) is disabled const hasDisabledDesc = hasDisabledDescendant(nodes, currentNode); // Only set checkAll to true if all descendants will be checked // If there are any disabled descendants, checkAll should be false currentNode.checkAll = isChecked && !hasDisabledDesc; currentNode[childrenKey].forEach(child => { checkChildNode(nodes, child, isChecked); }); } }); const getCheckedValuesByParent = useCallback(nodes => { const values = []; for (const key in nodes) { const currentNode = nodes[key]; if (!isNil(currentNode.parent) && !isNil(currentNode.parent.refKey)) { const parentNode = nodes[currentNode.parent.refKey]; if (currentNode.check) { // Optimization: When a parent node is checked with checkAll=true, it represents // the entire checked subtree. If the current node also has checkAll=true and its // parent is checked, skip adding this node's value to avoid redundant representation. // The parent's value already implies all descendants are checked. if (currentNode.checkAll && parentNode.check) { continue; } if (!(parentNode !== null && parentNode !== void 0 && parentNode.checkAll)) { values.push(currentNode[valueKey]); } else if (parentNode !== null && parentNode !== void 0 && parentNode.uncheckable) { values.push(currentNode[valueKey]); } } } else if (currentNode.check) { values.push(currentNode[valueKey]); } } return values; }, [valueKey]); const getCheckedValues = useEventCallback((node, isChecked) => { const nodes = cloneDeep(flattenedNodes); checkChildNode(nodes, node, isChecked); if (node.parent) { checkParentNode(nodes, node.parent, isChecked); } const values = getCheckedValuesByParent(nodes); return values.filter(v => !uncheckableItemValues.includes(v)); }); return { getCheckedValues }; } export default useTreeCheckState;