rsuite
Version:
A suite of react components
139 lines (135 loc) • 4.9 kB
JavaScript
'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;