UNPKG

@vectara/vectara-ui

Version:

Vectara's design system, codified as a React and Sass component library

70 lines (69 loc) 3.09 kB
// Flattens a list of rows (linked by `parentId`) into a render-ordered, depth-tagged array, // showing only visible nodes based on `expandedIds`. The render layer maps this 1:1 to <tr>s. // Orphans (rows with missing parents) are dropped. Cycles are avoided using an ancestor set. export const buildAndFlattenSpans = (rows, expandedIds, getId, getParentId) => { // First pass: collect every known id so we can detect orphans below. const allIds = new Set(); for (const row of rows) { allIds.add(getId(row)); } // Group rows by their parent id, with top-level rows under a sentinel key. // The sentinel can't collide with a real id because it contains spaces. const ROOT_KEY = " __VuiSpansRoot__ "; const childrenByParent = new Map(); for (const row of rows) { const parentId = getParentId(row); // Drop orphans: parent id set but not present in the rows list. if (parentId !== null && !allIds.has(parentId)) continue; const key = parentId === null ? ROOT_KEY : parentId; const list = childrenByParent.get(key); if (list) { list.push(row); } else { childrenByParent.set(key, [row]); } } const result = []; // Recursive depth-first walk. `ancestors` carries the chain of ids from the // root down to (but not including) the current row, used to detect cycles. const walk = (parentKey, parentId, depth, ancestors) => { const siblings = childrenByParent.get(parentKey); if (!siblings) return; siblings.forEach((row, index) => { const id = getId(row); // Break cycle: this id appears earlier in its own ancestor chain. if (ancestors.has(id)) return; const ownChildren = childrenByParent.get(id); const ownChildCount = ownChildren ? ownChildren.length : 0; const hasLoadedChildren = ownChildCount > 0; // The chevron should appear if either we already have children to show // or the row is hinting that lazily-loadable children exist. const hasChildren = row.hasChildren || hasLoadedChildren; result.push({ row, id, parentId, depth, hasChildren, hasLoadedChildren, posInSet: index + 1, setSize: siblings.length }); // Only descend if the user has expanded this row AND there's something // loaded to descend into. The lazy-only case (`hasChildren` but // `!hasLoadedChildren`) is handled by the loading row in the render // layer, not here. if (hasChildren && expandedIds.has(id) && ownChildCount > 0) { const nextAncestors = new Set(ancestors); nextAncestors.add(id); walk(id, id, depth + 1, nextAncestors); } }); }; walk(ROOT_KEY, null, 0, new Set()); return result; };