UNPKG

rsuite

Version:

A suite of react components

112 lines (108 loc) 4.15 kB
'use client'; import omit from 'lodash/omit'; import isNil from 'lodash/isNil'; import useForceUpdate from "./useForceUpdate.js"; import { useCallback, useRef, useEffect } from 'react'; import { shallowEqual } from "../../internals/utils/index.js"; import { formatNodeRefKey } from "../utils/index.js"; /** * Custom hook that flattens a tree data structure into a map of nodes. * */ function useFlattenTree(data, options) { const { value, labelKey, valueKey, childrenKey, uncheckableItemValues = [], disabledItemValues = [], cascade, multiple, callback } = options; const forceUpdate = useForceUpdate(); const flattenedNodes = useRef({}); const seenValues = useRef(new Set()); const updateTreeNodeCheckState = useCallback((value = []) => { // Reset values to false Object.keys(flattenedNodes.current).forEach(refKey => { const node = flattenedNodes.current[refKey]; // Check if this node or any of its parents is disabled const isNodeDisabled = disabledItemValues.some(disabledValue => shallowEqual(node[valueKey], disabledValue)); let hasDisabledParent = false; let currentNode = node; while (currentNode.parent && !hasDisabledParent) { const parentRefKey = currentNode.parent.refKey; if (parentRefKey && flattenedNodes.current[parentRefKey]) { const parentValue = flattenedNodes.current[parentRefKey][valueKey]; if (disabledItemValues.some(disabledValue => shallowEqual(parentValue, disabledValue))) { hasDisabledParent = true; } } currentNode = currentNode.parent; } // Skip disabled nodes - they should not be affected by cascade or value changes if (isNodeDisabled || hasDisabledParent) { return; } if (cascade && !isNil(node.parent) && !isNil(node.parent.refKey)) { node.check = flattenedNodes.current[node.parent.refKey].check; } else { node.check = false; } value.forEach(nodeVal => { if (shallowEqual(flattenedNodes.current[refKey][valueKey], nodeVal) && !uncheckableItemValues.some(uncheckableValue => shallowEqual(nodeVal, uncheckableValue))) { flattenedNodes.current[refKey].check = true; } }); }); }, [cascade, uncheckableItemValues, disabledItemValues, valueKey]); const flattenTreeData = useCallback((treeData, parent, layer = 1) => { if (!Array.isArray(treeData) || treeData.length === 0) { return []; } treeData.map(node => { const value = node[valueKey]; // Check for duplicate values if (seenValues.current.has(value)) { console.error(`[rsuite] The value '${value}' is duplicated. Each node in the tree data must have a unique value.`); } seenValues.current.add(value); const refKey = formatNodeRefKey(value); node.refKey = refKey; flattenedNodes.current[refKey] = { layer, [labelKey]: node[labelKey], [valueKey]: node[valueKey], uncheckable: uncheckableItemValues.some(value => shallowEqual(node[valueKey], value)), ...node }; if (parent) { flattenedNodes.current[refKey].parent = omit(parent, 'parent', 'children'); } flattenTreeData(node[childrenKey], node, layer + 1); }); callback === null || callback === void 0 || callback(flattenedNodes.current); forceUpdate(); }, [callback, forceUpdate, valueKey, labelKey, uncheckableItemValues, childrenKey]); useEffect(() => { // when data is changed, should clear the flattenedNodes, avoid duplicate keys flattenedNodes.current = {}; seenValues.current.clear(); flattenTreeData(data); }, [data]); useEffect(() => { if (multiple) { updateTreeNodeCheckState(value); forceUpdate(); } /** * Add a dependency on data, because when loading data asynchronously through getChildren, * data may change and the node status needs to be updated. * @see https://github.com/rsuite/rsuite/issues/3973 */ }, [value, data]); return flattenedNodes.current; } export default useFlattenTree;