es-grid-template
Version:
es-grid-template
1,038 lines (959 loc) • 30.5 kB
JavaScript
import collectNodes from "./collectNodes";
import isLeafNode from "./isLeafNode";
export 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
};
}
export 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
*/
export 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;
}
export 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);
}
export 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);
}
export 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;
}
export 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;
}
export 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;
}
export 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;
}
export 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;
}
export 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;
}
export 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;
}
export 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;
}
export 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;
}
export function getAllEnabledKeys1(treeIndex, isRowDisabled) {
const result = [];
for (const key of treeIndex.nodeMap.keys()) {
if (!isRowDisabled?.(key)) {
result.push(key);
}
}
return result;
}
export 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;
}
export 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;
}
export 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;
}
export 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;
}
export 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;
}
export const getAllEnabledParentKeys = (tree, primaryKey, isDisabled) => {
return tree.filter(row => !isDisabled?.(row)).map(row => row[primaryKey]);
return collectNodes(tree, 'pre').filter(row => !isLeafNode(row) && !isDisabled?.(row)).map(row => row[primaryKey]);
};
export 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;
}
export 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;
}
export 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;
}
export 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;
}