From b86ef138721e50ca205df4b8b85bced473a57a97 Mon Sep 17 00:00:00 2001 From: Lukas Harbarth Date: Fri, 4 Sep 2026 09:31:53 +0200 Subject: [PATCH] fix(AnalyticalTable - useIndeterminateRowSelection): improve performance --- .../useIndeterminateRowSelection.tsx | 79 +++++++------------ 1 file changed, 29 insertions(+), 50 deletions(-) diff --git a/packages/main/src/components/AnalyticalTable/pluginHooks/useIndeterminateRowSelection.tsx b/packages/main/src/components/AnalyticalTable/pluginHooks/useIndeterminateRowSelection.tsx index 5cc393e2084..1e66f603d76 100644 --- a/packages/main/src/components/AnalyticalTable/pluginHooks/useIndeterminateRowSelection.tsx +++ b/packages/main/src/components/AnalyticalTable/pluginHooks/useIndeterminateRowSelection.tsx @@ -10,64 +10,43 @@ type onIndeterminateChange = (e: { tableInstance: TableInstance; }) => void; -const getParentRow = (id: string, rowsById: TableInstance['rowsById']): [RowType, number] => { - let lastDotIndex = id.lastIndexOf('.'); - if (lastDotIndex === -1) { - lastDotIndex = Infinity; - } - const parentRowId = id.slice(0, lastDotIndex); - return [rowsById[parentRowId], lastDotIndex]; -}; - -const getIndeterminateRowIds = (id: string): Record => { - const indeterminateRowsById: Record = {}; - const lastDotIndex = id.lastIndexOf('.'); - indeterminateRowsById[id] = true; - if (lastDotIndex !== -1) { - // set all parent rows to indeterminate - Object.assign(indeterminateRowsById, getIndeterminateRowIds(id.slice(0, lastDotIndex))); - } - return indeterminateRowsById; -}; - +/** + * Marks a row indeterminate if its subtree contains a node whose direct sub-rows are partially selected (some, not all). + * A single O(n) post-order traversal visits each row once and reads every sub-row reference once. + */ const getIndeterminate = ( rows: RowType[], - rowsById: TableInstance['rowsById'], state: { selectedRowIds: AnalyticalTableState['selectedRowIds'] }, ): Record => { const indeterminateRowsById: Record = {}; - let usedParentIndex = ''; - const getIndeterminateRecursive = (subRows: RowType[], rowIdScope: string | null = null) => { - for (const row of subRows) { - if (row.subRows.length > 0) { - // find leaf nodes - getIndeterminateRecursive(row.subRows, row.id); - } else if (rowIdScope !== null && usedParentIndex !== rowIdScope) { - usedParentIndex = rowIdScope; - const checkIndeterminate = (rowId: string) => { - const [parentRow, dotIndex] = getParentRow(rowId, rowsById); - const selectedRows = parentRow.subRows.filter((item) => state.selectedRowIds[item.id]); - const areAllSelected = parentRow.subRows.length === selectedRows.length; - const isOneSelected = selectedRows.length > 0; - - // if not all, but at least one subRow is selected, set the parent row's state to indeterminate - if (isOneSelected && !areAllSelected) { - const parentRowId = parentRow.id; - Object.assign(indeterminateRowsById, getIndeterminateRowIds(parentRowId)); - return; - } - if (dotIndex !== Infinity) { - // recursively check indeterminate state until root nodes are reached - checkIndeterminate(parentRow.id); - } - return; - }; + const { selectedRowIds } = state; - checkIndeterminate(row.id); + const markSubtree = (row: RowType): boolean => { + const subRows = row.subRows; + if (!subRows?.length) { + return false; + } + let selectedCount = 0; + let subtreeHasIndeterminate = false; + for (const subRow of subRows) { + if (selectedRowIds[subRow.id]) { + selectedCount++; } + if (markSubtree(subRow)) { + subtreeHasIndeterminate = true; + } + } + const isPartiallySelected = selectedCount > 0 && selectedCount < subRows.length; + if (isPartiallySelected || subtreeHasIndeterminate) { + indeterminateRowsById[row.id] = true; + return true; } + return false; }; - getIndeterminateRecursive(rows); + + for (const row of rows) { + markSubtree(row); + } return indeterminateRowsById; }; @@ -138,7 +117,7 @@ export const useIndeterminateRowSelection = (onIndeterminateChange?: onIndetermi }; } - const indeterminateRowsById = getIndeterminate(rows, rowsById, { selectedRowIds: newState.selectedRowIds }); + const indeterminateRowsById = getIndeterminate(rows, { selectedRowIds: newState.selectedRowIds }); return { ...newState,