UNPKG

es-grid-template

Version:

es-grid-template

1,070 lines (990 loc) 31.9 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.buildDisabledKeySet = buildDisabledKeySet; exports.buildTreeIndex = buildTreeIndex; exports.getAllEnabledKeys = getAllEnabledKeys; exports.getAllEnabledKeys1 = getAllEnabledKeys1; exports.getAllEnabledParentKeys = void 0; exports.getDescendants = getDescendants; exports.getParentKeysLarge = getParentKeysLarge; exports.getRowsByKeysKeepTreeOrder = getRowsByKeysKeepTreeOrder; exports.handleSelectOptimized = handleSelectOptimized; exports.handleSelectOptimized1 = handleSelectOptimized1; exports.isCheckedAll = isCheckedAll; exports.isEqualSetLarge = isEqualSetLarge; exports.isIndeterminateAll = isIndeterminateAll; exports.isIndeterminateAll1 = isIndeterminateAll1; exports.isNodeOrAncestorDisabled = isNodeOrAncestorDisabled; exports.isRowChecked = isRowChecked; exports.isRowChecked1 = isRowChecked1; exports.isRowChecked2 = isRowChecked2; exports.isRowCheckedTree = isRowCheckedTree; exports.isRowIndeterminate = isRowIndeterminate; exports.isRowIndeterminate1 = isRowIndeterminate1; exports.toggleTreeSelection = toggleTreeSelection; exports.toggleTreeSelection1 = toggleTreeSelection1; exports.toggleTreeSelection2 = toggleTreeSelection2; exports.toggleTreeSelection3 = toggleTreeSelection3; var _collectNodes = _interopRequireDefault(require("./collectNodes")); var _isLeafNode = _interopRequireDefault(require("./isLeafNode")); function buildTreeIndex(data, primaryKey, childrenKey = 'children') { const nodeMap = new Map(); const parentMap = new Map(); const childrenMap = new Map(); function walk(nodes, parentKey) { for (const node of nodes) { const key = node[primaryKey]; nodeMap.set(key, node); parentMap.set(key, parentKey); const children = node[childrenKey] || []; const childKeys = children.map(c => c[primaryKey]); childrenMap.set(key, childKeys); if (children.length) { walk(children, key); } } } walk(data, null); return { nodeMap, parentMap, childrenMap }; } function getDescendants(key, childrenMap) { const result = []; const stack = [key]; while (stack.length > 0) { const current = stack.pop(); const children = childrenMap.get(current) || []; for (const child of children) { result.push(child); stack.push(child); } } return result; } /** * Check if node or any of its ancestors is disabled * If parents disabled, children are also considered disabled */ function isNodeOrAncestorDisabled(key, treeIndex, isDisabledFn) { const { nodeMap, parentMap } = treeIndex; // Check the node itself const node = nodeMap.get(key); if (node && isDisabledFn(node)) { return true; } // Check all ancestors upward let parentKey = parentMap.get(key); while (parentKey) { const parentNode = nodeMap.get(parentKey); if (parentNode && isDisabledFn(parentNode)) { return true; } parentKey = parentMap.get(parentKey) || null; } return false; } function handleSelectOptimized1(treeIndex, currentSelected, targetKey, checked, options) { const { checkStrictly = false, checkedStrategy = 'all' } = options; const { parentMap, childrenMap } = treeIndex; // clone and, for parent strategy, expand parents to their full descendant set so // that toggling takes siblings into account. const next = new Set(currentSelected); if (!checkStrictly && checkedStrategy === 'parent') { for (const key of Array.from(currentSelected)) { next.add(key); const desc = getDescendants(key, childrenMap); desc.forEach(k => next.add(k)); } } // 1) Strict mode: only toggle self if (checkStrictly) { if (checked) next.add(targetKey);else next.delete(targetKey); return Array.from(next); } // 2) Normal tree behavior: toggle self and all descendants const descendants = getDescendants(targetKey, childrenMap); if (checked) { next.add(targetKey); descendants.forEach(k => next.add(k)); } else { next.delete(targetKey); descendants.forEach(k => next.delete(k)); } // 3) Update parents upward: a parent is selected only when all children are selected let parentKey = parentMap.get(targetKey); while (parentKey) { const siblings = childrenMap.get(parentKey) || []; const allChecked = siblings.length > 0 && siblings.every(k => next.has(k)); if (allChecked) next.add(parentKey);else next.delete(parentKey); parentKey = parentMap.get(parentKey) || null; } // 4) Apply checkedStrategy for return value if (checkedStrategy === 'all') { return Array.from(next); } if (checkedStrategy === 'parent') { // for parent strategy we already expanded the selection above; now compress // back to parent nodes only. This also handles the case where a child was // toggled under a previously selected ancestor: the ancestor will have been // removed during upward propagation. return Array.from(next).filter(k => { let p = parentMap.get(k); while (p) { if (next.has(p)) return false; p = parentMap.get(p) || null; } return true; }); } if (checkedStrategy === 'child') { // keep nodes that are not ancestors of any other selected node (i.e., prefer leaves) return Array.from(next).filter(k => { const desc = getDescendants(k, childrenMap); for (const d of desc) if (next.has(d)) return false; return true; }); } return Array.from(next); } function handleSelectOptimized(treeIndex, currentSelected, targetKey, checked, options, isDisabledFn) { const { checkStrictly = false, checkedStrategy = 'all' } = options; const { parentMap, childrenMap } = treeIndex; const next = new Set(currentSelected); // 1) Strict mode: only toggle self if (checkStrictly) { if (checked) next.add(targetKey);else next.delete(targetKey); return Array.from(next); } // 1b) Parent strategy (non-strict) – custom behaviour if (!checkStrictly && checkedStrategy === 'parent') { // Special case: if parent is selected and child is not in selectedKeys, // treat the click as "unchecking" to expand parent into its siblings let actualChecked = checked; if (checked) { const children = childrenMap.get(targetKey) || []; if (children.length === 0) { const parentKey = parentMap.get(targetKey); if (parentKey && next.has(parentKey) && !next.has(targetKey)) { // parent is selected but child is implicit => treat as unchecking actualChecked = false; } } } if (actualChecked) { const children = childrenMap.get(targetKey) || []; if (children.length > 0) { // clicked parent: select parent only, clear its descendants next.add(targetKey); const allDesc = getDescendants(targetKey, childrenMap); allDesc.forEach(k => next.delete(k)); } else { // clicked child: add self and possibly compress to parent next.add(targetKey); const parentKey = parentMap.get(targetKey); if (parentKey) { let siblings = childrenMap.get(parentKey) || []; if (isDisabledFn) { siblings = siblings.filter(k => { const node = treeIndex.nodeMap.get(k); return !(node && isDisabledFn(node)); }); } const allChecked = siblings.length > 0 && siblings.every(k => next.has(k)); if (allChecked) { next.add(parentKey); siblings.forEach(k => next.delete(k)); } } } } else { // unchecking behaviour: expand parent to siblings and remove parent const children = childrenMap.get(targetKey) || []; if (children.length > 0) { next.delete(targetKey); } else { const parentKey = parentMap.get(targetKey); if (parentKey && next.has(parentKey)) { // parent was selected, expand it to siblings next.delete(parentKey); let siblings = childrenMap.get(parentKey) || []; if (isDisabledFn) { siblings = siblings.filter(k => { const node = treeIndex.nodeMap.get(k); return !(node && isDisabledFn(node)); }); } siblings.forEach(k => { if (k !== targetKey) next.add(k); }); } else { // normal unchecking next.delete(targetKey); const keyParent = parentMap.get(targetKey); if (keyParent) next.delete(keyParent); } } } return Array.from(next); } // 2) Normal tree behavior: toggle self and all descendants let descendants = getDescendants(targetKey, childrenMap); // For 'all' strategy: filter out disabled descendants if (!checkStrictly && checkedStrategy === 'all' && isDisabledFn) { descendants = descendants.filter(k => { const node = treeIndex.nodeMap.get(k); return !isDisabledFn(node); }); } if (checked) { next.add(targetKey); descendants.forEach(k => next.add(k)); } else { next.delete(targetKey); descendants.forEach(k => next.delete(k)); } // 3) Update parents upward: a parent is selected only when all children are selected let parentKey = parentMap.get(targetKey); while (parentKey) { const siblings = childrenMap.get(parentKey) || []; let allChecked = false; if (checkedStrategy === 'all' && isDisabledFn) { // For 'all' strategy: only check enabled children const enabledSiblings = siblings.filter(k => { const node = treeIndex.nodeMap.get(k); return !isDisabledFn(node); }); allChecked = enabledSiblings.length > 0 && enabledSiblings.every(k => next.has(k)); } else { // For other strategies: check all children allChecked = siblings.length > 0 && siblings.every(k => next.has(k)); } if (allChecked) next.add(parentKey);else next.delete(parentKey); parentKey = parentMap.get(parentKey) || null; } // 4) Apply checkedStrategy for return value if (checkedStrategy === 'all') { return Array.from(next); } if (checkedStrategy === 'parent') { // for parent strategy we already expanded the selection above; now compress // back to parent nodes only. This also handles the case where a child was // toggled under a previously selected ancestor: the ancestor will have been // removed during upward propagation. return Array.from(next).filter(k => { let p = parentMap.get(k); while (p) { if (next.has(p)) return false; p = parentMap.get(p) || null; } return true; }); } if (checkedStrategy === 'child') { // keep nodes that are not ancestors of any other selected node (i.e., prefer leaves) return Array.from(next).filter(k => { const desc = getDescendants(k, childrenMap); for (const d of desc) if (next.has(d)) return false; return true; }); } return Array.from(next); } function isRowChecked2(key, selectedKeys, treeIndex, checkedStrategy) { const { parentMap } = treeIndex; // CHILD => chỉ check chính nó if (checkedStrategy === 'child') { return selectedKeys.has(key); } // ALL => selectedKeys chứa full tree state if (checkedStrategy === 'all') { return selectedKeys.has(key); } // PARENT STRATEGY // Nếu chính nó là parent được chọn if (selectedKeys.has(key)) return true; // Nếu có ancestor được chọn => nó cũng phải checked let parentKey = parentMap.get(key); while (parentKey) { if (selectedKeys.has(parentKey)) { return true; } parentKey = parentMap.get(parentKey) || null; } return false; } function isRowChecked1(key, selectedKeys, treeIndex, checkedStrategy, isDisabledFn) { const { parentMap, childrenMap, nodeMap } = treeIndex; const node = nodeMap.get(key); const isDisabled = node && isDisabledFn ? isDisabledFn(node) : false; // CHILD => chỉ check chính nó if (checkedStrategy === 'child') { return selectedKeys.has(key); } // ========== ALL & PARENT STRATEGY ========== if (checkedStrategy === 'all' || checkedStrategy === 'parent') { // Nếu chính nó được chọn if (selectedKeys.has(key)) return true; // Nếu là leaf => chỉ cần check chính nó const children = childrenMap.get(key); if (!children || children.length === 0) { return selectedKeys.has(key); } // Lọc ra children không bị disable const enabledChildren = children.filter(childKey => { const childNode = nodeMap.get(childKey); return !(childNode && isDisabledFn && isDisabledFn(childNode)); }); // Nếu tất cả con đều disable if (enabledChildren.length === 0) { return false; } // Kiểm tra tất cả con hợp lệ đều được check const allChildrenChecked = enabledChildren.every(childKey => isRowChecked(childKey, selectedKeys, treeIndex, checkedStrategy, isDisabledFn)); if (allChildrenChecked) return true; } // ========== PARENT STRATEGY (lan truyền từ trên xuống) ========== if (checkedStrategy === 'parent') { let parentKey = parentMap.get(key) || null; while (parentKey) { if (selectedKeys.has(parentKey)) { // nếu node hiện tại disable và không explicit chọn if (isDisabled) { return selectedKeys.has(key); } return true; } parentKey = parentMap.get(parentKey) || null; } } return false; } function isRowChecked(key, selectedKeys, treeIndex, checkedStrategy, isDisabledFn) { const { parentMap, childrenMap, nodeMap } = treeIndex; const node = nodeMap.get(key); const isDisabled = node && isDisabledFn ? isDisabledFn(node) : false; // ========================= // CHILD STRATEGY // ========================= if (checkedStrategy === 'child') { return selectedKeys.has(key); } // ========================= // PARENT STRATEGY // selectedKeys chỉ chứa parent keys hoặc leaf keys được chọn // ========================= if (checkedStrategy === 'parent') { // node disable => chỉ true nếu explicit chọn // console.log('isDisabled', isDisabled) if (isDisabled) { const abc = selectedKeys.has(key); return abc; } // Step 1: nếu chính nó được chọn explicit if (selectedKeys.has(key)) return true; // Step 2: Kiểm tra xem có ancestor nào được chọn không // Nếu có, node này cũng được consider checked let parentKey = parentMap.get(key) || null; while (parentKey) { if (selectedKeys.has(parentKey)) { return true; } parentKey = parentMap.get(parentKey) || null; } // Step 3: Node không explicit selected và không có ancestor selected // Chỉ trả true nếu nó là parent với TẤT CẢ children đều checked const children = childrenMap.get(key); // Nếu là leaf → không checked if (!children || children.length === 0) { return false; } // Lọc ra children enable const enabledChildren = children.filter(childKey => { const childNode = nodeMap.get(childKey); return !(childNode && isDisabledFn && isDisabledFn(childNode)); }); // Nếu tất cả con đều disable → không checked if (enabledChildren.length === 0) { return false; } // Kiểm tra recursive: tất cả con enable có được check không // Điều này bao gồm cả cases con là parent hoặc leaf ở bất kỳ cấp nào for (const childKey of enabledChildren) { if (!isRowChecked(childKey, selectedKeys, treeIndex, checkedStrategy, isDisabledFn)) { return false; } } return true; } // ========================= // ALL STRATEGY // selectedKeys chứa full state // ========================= if (checkedStrategy === 'all') { if (selectedKeys.has(key)) return true; const children = childrenMap.get(key); if (!children || children.length === 0) { return selectedKeys.has(key); } const enabledChildren = children.filter(childKey => { const childNode = nodeMap.get(childKey); return !(childNode && isDisabledFn && isDisabledFn(childNode)); }); if (enabledChildren.length === 0) { return false; } // Recursively check all enabled children at all levels return enabledChildren.every(childKey => isRowChecked(childKey, selectedKeys, treeIndex, checkedStrategy, isDisabledFn)); } return false; } function isRowCheckedTree(key, selectedKeys, treeIndex, isDisabled) { const { parentMap, nodeMap } = treeIndex; const row = nodeMap.get(key); const disabled = isDisabled?.(row) ?? false; if (selectedKeys.has(key)) return true; if (disabled) return false; let parent = parentMap.get(key); while (parent) { if (selectedKeys.has(parent)) return true; parent = parentMap.get(parent); } return false; } function isRowIndeterminate1(key, selectedKeys, treeIndex, checkedStrategy) { const { childrenMap } = treeIndex; // child mode không có khái niệm indeterminate if (checkedStrategy === 'child') return false; const children = childrenMap.get(key); if (!children || children.length === 0) return false; // Nếu node đã fully checked -> không indeterminate if (isRowChecked(key, selectedKeys, treeIndex, checkedStrategy)) { return false; } // ===================================================== // 🔥 NEW LOGIC: // Nếu có BẤT KỲ descendant nào checked -> TRUE // ===================================================== const stack = [...children]; while (stack.length) { const current = stack.pop(); if (isRowChecked(current, selectedKeys, treeIndex, checkedStrategy)) { return true; } const subChildren = childrenMap.get(current); if (subChildren?.length) { stack.push(...subChildren); } } return false; } function isRowIndeterminate(key, selectedKeys, treeIndex, checkedStrategy, isDisabledFn) { const { childrenMap, nodeMap } = treeIndex; // child mode không có indeterminate if (checkedStrategy === 'child') return false; const children = childrenMap.get(key); if (!children || children.length === 0) return false; // Nếu node đã fully checked → không indeterminate if (isRowChecked(key, selectedKeys, treeIndex, checkedStrategy, isDisabledFn)) { return false; } // ===================================================== // Với ALL & PARENT: // cần: // - ít nhất 1 descendant checked (enable) tại BẤT KỲ cấp nào // - ít nhất 1 descendant chưa checked (enable) tại BẤT KỲ cấp nào // ===================================================== let hasChecked = false; let hasUnchecked = false; // Use stack to traverse all levels of descendants const stack = [...children]; while (stack.length && !(hasChecked && hasUnchecked)) { const current = stack.pop(); const node = nodeMap.get(current); // bỏ qua disable node if (node && isDisabledFn && isDisabledFn(node)) { // Still need to check children of disabled node const subChildren = childrenMap.get(current); if (subChildren?.length) { stack.push(...subChildren); } continue; } const checked = isRowChecked(current, selectedKeys, treeIndex, checkedStrategy, isDisabledFn); if (checked) { hasChecked = true; } else { hasUnchecked = true; } // Continue traversing ALL descendants at every level const subChildren = childrenMap.get(current); if (subChildren?.length) { stack.push(...subChildren); } } // phải đồng thời có checked và unchecked return hasChecked && hasUnchecked; } function isCheckedAll(selectedKeys, treeIndex, checkedStrategy, isDisabled) { const { nodeMap } = treeIndex; let total = 0; let checked = 0; for (const [key, row] of nodeMap.entries()) { if (isDisabled?.(row)) continue; total++; if (isRowChecked(key, selectedKeys, treeIndex, checkedStrategy)) { checked++; } } if (total === 0) return false; return checked === total; } function isIndeterminateAll1(selectedKeys, treeIndex, checkedStrategy, isDisabled) { const { nodeMap } = treeIndex; let total = 0; let checked = 0; for (const [key, row] of nodeMap.entries()) { if (isDisabled?.(row)) continue; total++; if (isRowChecked(key, selectedKeys, treeIndex, checkedStrategy)) { checked++; } } if (total === 0) return false; return checked > 0 && checked < total; } function isIndeterminateAll(dataKeys, selectedKeys, treeIndex, checkedStrategy, isDisabledFn) { if (checkedStrategy === 'child') return false; const { nodeMap } = treeIndex; let hasChecked = false; let hasUnchecked = false; // Must check root-level items in dataKeys // For multi-level trees, only check root-level nodes here for (let i = 0; i < dataKeys.length; i++) { const key = dataKeys[i]; const node = nodeMap.get(key); // bỏ qua disable if (node && isDisabledFn && isDisabledFn(node)) { continue; } const checked = isRowChecked(key, selectedKeys, treeIndex, checkedStrategy, isDisabledFn); if (checked) { hasChecked = true; } else { hasUnchecked = true; } // early break tối ưu performance if (hasChecked && hasUnchecked) { return true; } } return hasChecked && hasUnchecked; } function getAllEnabledKeys1(treeIndex, isRowDisabled) { const result = []; for (const key of treeIndex.nodeMap.keys()) { if (!isRowDisabled?.(key)) { result.push(key); } } return result; } function getAllEnabledKeys(data, key, isRowDisabled, childrenKey = 'children') { if (!Array.isArray(data) || data.length === 0) return []; const result = []; const stack = []; // push root level in reverse order for correct DFS for (let i = data.length - 1; i >= 0; i--) { stack.push({ node: data[i], parentDisabled: false }); } // Traverse all levels with proper parent disabled inheritance while (stack.length) { const { node, parentDisabled } = stack.pop(); const selfDisabled = parentDisabled || !!isRowDisabled?.(node); if (!selfDisabled) { result.push(node[key]); } // Get children at any level (multi-level support) const children = node[childrenKey]; if (children && children.length) { for (let i = children.length - 1; i >= 0; i--) { stack.push({ node: children[i], parentDisabled: selfDisabled }); } } } return result; } function buildDisabledKeySet(treeIndex, isDisabled) { if (!isDisabled) return new Set(); const { nodeMap, parentMap } = treeIndex; const disabledSet = new Set(); for (const [key, node] of nodeMap.entries()) { // Nếu chính nó disable if (isDisabled(node)) { disabledSet.add(key); continue; } // Kiểm tra ancestor có disable không let parentKey = parentMap.get(key); while (parentKey) { if (disabledSet.has(parentKey)) { disabledSet.add(key); break; } parentKey = parentMap.get(parentKey) || null; } } return disabledSet; } function getRowsByKeysKeepTreeOrder(keys, tree, key) { const keySet = new Set(keys); const result = []; const stack = [...tree]; while (stack.length) { const node = stack.pop(); if (keySet.has(node[key])) { result.push(node); } if (node.children?.length) { stack.push(...node.children); } } return result; } function getParentKeysLarge(data, keyField = 'id', childrenField = 'children') { let hasTree = false; // scan nhanh xem có node nào có children không const stackScan = data.slice(); while (stackScan.length > 0) { const node = stackScan.pop(); const children = node[childrenField]; if (children && children.length > 0) { hasTree = true; break; } } // nếu không phải tree → trả flat keys if (!hasTree) { const result = new Array(data.length); for (let i = 0; i < data.length; i++) { result[i] = data[i][keyField]; } return result; } // nếu là tree → chỉ trả parent keys const result = []; const stack = data.slice(); while (stack.length > 0) { const node = stack.pop(); const children = node[childrenField]; if (children && children.length > 0) { result.push(node[keyField]); for (let i = 0, len = children.length; i < len; i++) { stack.push(children[i]); } } } return result; } function isEqualSetLarge(a, b) { if (a === b) return true; if (a.size !== b.size) return false; const [small, big] = a.size < b.size ? [a, b] : [b, a]; for (const value of small) { if (!big.has(value)) return false; } return true; } const getAllEnabledParentKeys = (tree, primaryKey, isDisabled) => { return tree.filter(row => !isDisabled?.(row)).map(row => row[primaryKey]); return (0, _collectNodes.default)(tree, 'pre').filter(row => !(0, _isLeafNode.default)(row) && !isDisabled?.(row)).map(row => row[primaryKey]); }; exports.getAllEnabledParentKeys = getAllEnabledParentKeys; function toggleTreeSelection1(key, selectedKeys, treeIndex, isDisabled) { const { parentMap, childrenMap, nodeMap } = treeIndex; const next = new Set(selectedKeys); const parentKey = parentMap.get(key); const children = childrenMap.get(key); const row = nodeMap.get(key); const disabled = isDisabled?.(row); if (disabled) return next; const isChecked = next.has(key); // CLICK NODE CHA if (children && children.length > 0) { if (isChecked) next.delete(key);else next.add(key); return next; } // CLICK NODE CON if (!parentKey) return next; const siblings = childrenMap.get(parentKey) || []; const enabledSiblings = siblings.filter(k => { const r = nodeMap.get(k); return !isDisabled?.(r); }); const parentChecked = next.has(parentKey); // NODE CON CHƯA CHECK if (!isChecked) { const checkedCount = enabledSiblings.filter(k => next.has(k)).length + 1; if (checkedCount === enabledSiblings.length) { enabledSiblings.forEach(k => next.delete(k)); next.add(parentKey); } else { next.add(key); } return next; } // NODE CON ĐÃ CHECK if (parentChecked) { next.delete(parentKey); enabledSiblings.forEach(k => { if (k !== key) next.add(k); }); return next; } next.delete(key); return next; } function toggleTreeSelection2(key, selectedKeys, treeIndex, isDisabled) { const { parentMap, childrenMap, nodeMap } = treeIndex; const next = new Set(selectedKeys); const row = nodeMap.get(key); if (isDisabled?.(row)) return next; const children = childrenMap.get(key); // CLICK NODE CHA if (children && children.length) { if (next.has(key)) next.delete(key);else next.add(key); return next; } // CLICK NODE CON if (next.has(key)) { // nếu parent đang checked → expand lại children const parent = parentMap.get(key); if (parent && next.has(parent)) { next.delete(parent); const siblings = childrenMap.get(parent) || []; for (const s of siblings) { const r = nodeMap.get(s); if (!isDisabled?.(r) && s !== key) { next.add(s); } } return next; } next.delete(key); return next; } // ADD NODE next.add(key); // BUBBLE UP let parent = parentMap.get(key); while (parent) { const siblings = childrenMap.get(parent) || []; const enabledSiblings = siblings.filter(s => { const r = nodeMap.get(s); return !isDisabled?.(r); }); const allChecked = enabledSiblings.every(s => next.has(s)); if (!allChecked) break; for (const s of enabledSiblings) { next.delete(s); } next.add(parent); parent = parentMap.get(parent); } return next; } function toggleTreeSelection(key, selectedKeys, treeIndex, isDisabled) { const { parentMap, childrenMap, nodeMap } = treeIndex; const next = new Set(selectedKeys); const row = nodeMap.get(key); if (isDisabled?.(row)) { return next; } const parent = parentMap.get(key); const isInclude = next.has(key); const children = childrenMap.get(key); // TOGGLE NODE: add || delete key row hiện tại if (isInclude) { next.delete(key); } else { next.add(key); } // cấp cha đầu tiên: đã add | delete bên trên nên không làm gì if (!parent) {} // node con cấp cuối cùng cuối cùng if (children && children.length === 0) {} else {} if (parent && next.has(parent)) { next.delete(parent); const siblings = childrenMap.get(parent) || []; for (const s of siblings) { const r = nodeMap.get(s); if (!isDisabled?.(r) && s !== key) { next.add(s); } } } // BUBBLE UP let currentParent = parentMap.get(key); while (currentParent) { const siblings = childrenMap.get(currentParent) || []; const enabledSiblings = siblings.filter(s => { const r = nodeMap.get(s); return !isDisabled?.(r); }); const allChecked = enabledSiblings.every(s => next.has(s)); if (!allChecked) { break; } for (const s of enabledSiblings) { next.delete(s); } next.add(currentParent); currentParent = parentMap.get(currentParent); } return next; } function toggleTreeSelection3(key, selectedKeys, treeIndex, isDisabled) { const { parentMap, childrenMap, nodeMap } = treeIndex; const next = new Set(selectedKeys); const row = nodeMap.get(key); if (isDisabled?.(row)) return next; const parent = parentMap.get(key); const children = childrenMap.get(key); const isChecked = next.has(key); // ========================= // CLICK NODE CHA // ========================= if (children && children.length) { if (isChecked) next.delete(key);else next.add(key); return next; } // ========================= // CLICK NODE CON // ========================= if (!parent) return next; const siblings = childrenMap.get(parent) || []; const enabledSiblings = siblings.filter(k => { const r = nodeMap.get(k); return !isDisabled?.(r); }); const parentChecked = next.has(parent); // -------- // node con CHƯA checked // -------- if (!isChecked) { next.add(key); const allChecked = enabledSiblings.every(k => next.has(k)); if (allChecked) { for (const s of enabledSiblings) { next.delete(s); } next.add(parent); } } // -------- // node con ĐÃ checked // -------- else { if (parentChecked) { next.delete(parent); for (const s of enabledSiblings) { if (s !== key) { next.add(s); } } } else { next.delete(key); } } // ========================= // BUBBLE UP // ========================= let currentParent = parentMap.get(parent); while (currentParent) { const siblings2 = childrenMap.get(currentParent) || []; const enabledSiblings2 = siblings2.filter(k => { const r = nodeMap.get(k); return !isDisabled?.(r); }); const allChecked = enabledSiblings2.every(k => next.has(k)); if (!allChecked) break; for (const s of enabledSiblings2) { next.delete(s); } next.add(currentParent); currentParent = parentMap.get(currentParent); } return next; }