/
kaws
/
ui-kit-ce
Обзор
Документация
Войти
/
kaws
/
ui-kit-ce
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
packages/tree/src/hooks/useCheck/utils.ts
463 строки
13 KB
Pavel Uryadyshev
feat(tree-dnd): реализован новый компонент Tree с поддержкой drag-and-drop
09 июл 2025, 13:07
09 июл 2025, 13:07
a06fe68
Код
Авторство
О чём код?
import { TreeNodePlain, TreeItem, TreeGetters } from '../types' import { TreeCheckMode, TreeCheckModeType } from './useCheck' type ProcessKeysResult = { /** * Объект ключей выбранных узлов. * * @inner */ keysToCheck: Record<React.Key, boolean> /** * Объект ключей частично выбранных узлов. * * @inner */ keysToIndeterminate: Record<React.Key, boolean> } export type ProcessChildrenKeysOptions<TItem = TreeItem> = { /** * Значение выбранного целевого узла дерева. * * @inner */ targetValue?: boolean /** * Массив ключей дочерних узлов у целевого узла. * * @inner */ keys?: React.Key[] /** * Объект ключей заблокированных узлов дерева. * * @inner */ disabledKeys?: Record<React.Key, boolean> /** * Ссылка на объект всех выбранных ключей узлов дерева. * * @inner */ currentCheckedKeys?: Record<React.Key, boolean> /** * * @returns */ getTreeItemCheckDisabled?: TreeGetters<TItem>['getTreeItemCheckDisabled'] } export type ProcessParentKeysOptions<TItem = TreeItem> = { /** * Ключ родительского узла у целевого узла. * * @inner */ parentKey?: React.Key | null /** * Объект выбранных ключей дочерних узлов дерева у целевого узла. * * @inner */ checkedKeys?: Record<React.Key, boolean> /** * Ссылка на объект всех частично выбранных ключей узлов дерева. * * @inner */ currentIndeterminateKeys?: Record<React.Key, boolean> /** * Ссылка на объект всех выбранных ключей узлов дерева. * * @inner */ currentCheckedKeys?: Record<React.Key, boolean> /** * * @returns */ getTreeItemCheckDisabled?: TreeGetters<TItem>['getTreeItemCheckDisabled'] } export type ProcessChildrenKeysResult = ProcessKeysResult & { /** * Все дочерние узлы целевого узла выбраны. * * @inner */ isAllChildrenChecked: boolean } export type ProcessParentKeysResult = ProcessKeysResult export type GetKeysOptions<TItem = TreeItem> = { /** * Объект всех частично выбранных ключей узлов дерева. * * @inner */ currentIndeterminateKeys?: Record<React.Key, boolean> /** * Массив ключей узлов дерева для выбора. * * @inner */ arrayKeys?: React.Key[] /** * Объект, содержащий текущие ключи выбранных узлов дерева. * * @inner */ currentCheckedKeys?: Record<React.Key, boolean> /** * Объект ключей заблокированных узлов дерева. * * @inner */ disabledKeys?: Record<React.Key, boolean> /** * Целевое значение для чекбоксов. Если указано это свойство, то целевое значение берется отсюда, а не определяется * автоматически с помощью инвертирования текущего значения. * * @inner */ targetValue?: boolean /** * * @returns */ getTreeItemCheckDisabled?: TreeGetters<TItem>['getTreeItemCheckDisabled'] /** * * @inner */ checkMode?: TreeCheckModeType } export type GetKeysResult = { /** * Объект выбранных ключей узлов дерева. * * @inner */ keysToCheck: Record<React.Key, boolean> /** * Объект частично выбранных ключей узлов дерева. * * @inner */ keysToIndeterminate: Record<React.Key, boolean> } /** * Функция для обхода дочерних узлов дерева в глубину. * * @param treeNodes Плоский объект дерева узлов. * @param options Объект с конфигурацией обхода дерева. * @param options.keys * @param options.targetValue * @param options.disabledKeys * @param options.currentCheckedKeys * * @returns Объект с объектом ключей выбранных узлов, с объектом ключей частично выбранных узлов и * флагом, что все дочерние узлы текущего узла были выбраны. */ export const processChildrenKeys = <TItem = TreeItem>( treeNodes: TreeNodePlain<TItem>, { keys = [], targetValue, disabledKeys, currentCheckedKeys, }: ProcessChildrenKeysOptions<TItem> = {} ): ProcessChildrenKeysResult => { const childrenKeys = [...keys] const keysToCheck: Record<React.Key, boolean> = {} const keysToIndeterminate: Record<React.Key, boolean> = {} let childrenCount = 0 let checkedChildrenCount = 0 while (childrenKeys.length > 0) { const childrenKey = childrenKeys.shift() if (!childrenKey) { return { keysToCheck, keysToIndeterminate, isAllChildrenChecked: false, } } childrenCount++ if (treeNodes[childrenKey].children) { childrenKeys.push( ...(treeNodes[childrenKey].$meta.boundaryChildrenKeys || []) ) } if (!disabledKeys?.[childrenKey]) { if (targetValue) { keysToCheck[childrenKey] = targetValue } else if (currentCheckedKeys) { delete currentCheckedKeys[childrenKey] } keysToIndeterminate[childrenKey] = false checkedChildrenCount++ } else { const parentKey = treeNodes[childrenKey].$meta.parentKey if (parentKey) { const parentKeysToIndeterminate = [parentKey] while (parentKeysToIndeterminate.length > 0) { const parentKeyToIndeterminate = parentKeysToIndeterminate.shift() if (!parentKeyToIndeterminate) { continue } const parent = treeNodes[parentKeyToIndeterminate] if (parent.$meta.parentKey) { parentKeysToIndeterminate.push(parent.$meta.parentKey) } keysToIndeterminate[parent.$meta.id] = !!targetValue } } } } return { keysToCheck, keysToIndeterminate, isAllChildrenChecked: childrenCount === checkedChildrenCount, } } /** * Функция для обхода родительских узлов дерева наверх. * * @param treeNodes Плоский объект дерева узлов. * @param options Объект с конфигурацией обхода дерева. * @param options.checkedKeys * @param options.currentIndeterminateKeys * @param options.currentCheckedKeys * @param options.parentKey * @param options.getTreeItemCheckDisabled * * @returns Объект с объектом ключей выбранных узело и с объектом ключей полувыбранных узлов. */ export const processParentKeys = <TItem = TreeItem>( treeNodes: TreeNodePlain<TItem>, { checkedKeys = {}, currentIndeterminateKeys = {}, currentCheckedKeys, parentKey, getTreeItemCheckDisabled, }: ProcessParentKeysOptions<TItem> = {} ): ProcessParentKeysResult => { const keysToCheck: Record<React.Key, boolean> = {} const keysToCheckWithoutFalse: Record<React.Key, boolean> = {} const keysToIndeterminate: Record<React.Key, boolean> = {} const parentKeys = [parentKey] while (parentKeys.length > 0) { let checkedChildrenCount = 0 let indeterminateChildrenCount = 0 const parentKey = parentKeys.shift() if (!parentKey) { return { keysToCheck, keysToIndeterminate } } const treeNodeItem = treeNodes[parentKey] if (treeNodeItem.$meta.parentKey) { parentKeys.push(treeNodeItem.$meta.parentKey) } const targetKey = treeNodeItem.$meta.id if (treeNodeItem.children) { for (let i = 0; i < treeNodeItem.children.length; i++) { const key = treeNodeItem.children[i].$meta.id if ( keysToCheck[key] === undefined ? checkedKeys[key] : keysToCheck[key] ) { checkedChildrenCount++ } if ( (keysToIndeterminate[key] === undefined ? currentIndeterminateKeys[key] : keysToIndeterminate[key]) && !keysToCheck[key] ) { indeterminateChildrenCount++ } } const isAllChildrenChecked = checkedChildrenCount !== 0 && checkedChildrenCount === treeNodeItem.children.length && indeterminateChildrenCount === 0 const isCheckDisabled = getTreeItemCheckDisabled?.( treeNodes[targetKey] as unknown as TItem ) if (isAllChildrenChecked) { keysToCheck[targetKey] = isCheckDisabled ? false : isAllChildrenChecked keysToCheckWithoutFalse[targetKey] = isCheckDisabled ? false : isAllChildrenChecked } else { keysToCheck[targetKey] = false if (currentCheckedKeys) { delete currentCheckedKeys[targetKey] } } keysToIndeterminate[targetKey] = isAllChildrenChecked && !isCheckDisabled ? false : checkedChildrenCount > 0 || indeterminateChildrenCount > 0 } } return { keysToCheck: keysToCheckWithoutFalse, keysToIndeterminate } } /** * Функция для получения объектов с ключами выбранных и частично выбранных узлов дерева. * * @param treeNodes Плоский объект дерева узлов. * @param targetKey Ключ целевого узла дерева. * @param options Объект с конфигурацией обхода дерева. * @param options.currentIndeterminateKeys * @param options.currentCheckedKeys * @param options.disabledKeys * @param options.targetValue * @param options.getTreeItemCheckDisabled * @param options.checkMode * * @returns */ export const getKeys = <TItem = TreeItem>( treeNodes: TreeNodePlain<TItem>, targetKey: React.Key, { currentIndeterminateKeys = {}, currentCheckedKeys: currentCheckedKeysProp, disabledKeys, targetValue, getTreeItemCheckDisabled, checkMode = TreeCheckMode.affect, }: GetKeysOptions<TItem> ): GetKeysResult => { const currentCheckedKeys: Record<React.Key, boolean> = { ...currentCheckedKeysProp, } let keysToIndeterminate: Record<React.Key, boolean> = {} let keysToCheck: Record<React.Key, boolean> = {} const treeItem = treeNodes[targetKey] if (!treeItem) { return { keysToCheck, keysToIndeterminate } } if (checkMode === TreeCheckMode.strict) { keysToCheck = { ...currentCheckedKeys, } if (!disabledKeys?.[targetKey]) { const value = targetValue === undefined ? !keysToCheck[targetKey] : targetValue if (value) { keysToCheck[targetKey] = true } else { delete keysToCheck[targetKey] } } return { keysToCheck, keysToIndeterminate } } const treeItemChecked = currentIndeterminateKeys[targetKey] ? false : (targetValue === undefined && !currentCheckedKeys[targetKey]) || targetValue if (!disabledKeys?.[targetKey]) { if (treeItemChecked) { keysToCheck = { [targetKey]: treeItemChecked, } } else { delete currentCheckedKeys[targetKey] } } const { keysToCheck: childrenKeysToCheck, keysToIndeterminate: childrenKeysToIndeterminate, isAllChildrenChecked, } = processChildrenKeys(treeNodes, { currentCheckedKeys, targetValue: treeItemChecked, keys: treeItem.$meta.boundaryChildrenKeys, disabledKeys, getTreeItemCheckDisabled, }) const { keysToCheck: parentKeysToCheck, keysToIndeterminate: parentKeysToIndeterminate, } = processParentKeys(treeNodes, { currentCheckedKeys, checkedKeys: Object.assign( {}, currentCheckedKeys, childrenKeysToCheck, keysToCheck ), currentIndeterminateKeys: Object.assign( currentIndeterminateKeys, childrenKeysToIndeterminate ), parentKey: treeItem.$meta.parentKey, getTreeItemCheckDisabled, }) Object.assign(keysToCheck, childrenKeysToCheck, parentKeysToCheck) if (treeItemChecked !== undefined) { keysToIndeterminate = { [targetKey]: treeItemChecked ? !isAllChildrenChecked : treeItemChecked, } } Object.assign( keysToIndeterminate, childrenKeysToIndeterminate, parentKeysToIndeterminate ) return { keysToCheck: { ...currentCheckedKeys, ...keysToCheck, }, keysToIndeterminate, } }