From b66530f8fd00edbb076bfd2e7b57b9842b748fa7 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Thu, 4 Dec 2025 04:07:24 -0800 Subject: [PATCH] fix table not re-rendering on column configuration change --- .../item-table-list/item-table-list.tsx | 875 +++++++++--------- .../shared/components/table-config.tsx | 2 +- 2 files changed, 420 insertions(+), 457 deletions(-) diff --git a/src/renderer/components/item-list/item-table-list/item-table-list.tsx b/src/renderer/components/item-list/item-table-list/item-table-list.tsx index fe178942c..ef71d045b 100644 --- a/src/renderer/components/item-list/item-table-list/item-table-list.tsx +++ b/src/renderer/components/item-list/item-table-list/item-table-list.tsx @@ -130,312 +130,432 @@ interface VirtualizedTableGridProps { totalRowCount: number; } -const VirtualizedTableGrid = React.memo( - ({ - activeRowId, - calculatedColumnWidths, - CellComponent, - cellPadding, - controls, - data, - enableAlternateRowColors, - enableColumnReorder, - enableColumnResize, - enableDrag, - enableExpansion, - enableHeader, - enableHorizontalBorders, - enableRowHoverHighlight, - enableSelection, - enableVerticalBorders, - getRowHeight, - groups, - headerHeight, - internalState, - itemType, - mergedRowRef, - onRangeChanged, - parsedColumns, - pinnedLeftColumnCount, - pinnedLeftColumnRef, - pinnedRightColumnCount, - pinnedRightColumnRef, - pinnedRowCount, - pinnedRowRef, - playerContext, - showLeftShadow, - showRightShadow, - showTopShadow, - size, - startRowIndex, - tableId, - totalColumnCount, - totalRowCount, - }: VirtualizedTableGridProps) => { - const columnWidth = useCallback( - (index: number) => calculatedColumnWidths[index], - [calculatedColumnWidths], +const VirtualizedTableGrid = ({ + activeRowId, + calculatedColumnWidths, + CellComponent, + cellPadding, + controls, + data, + enableAlternateRowColors, + enableColumnReorder, + enableColumnResize, + enableDrag, + enableExpansion, + enableHeader, + enableHorizontalBorders, + enableRowHoverHighlight, + enableSelection, + enableVerticalBorders, + getRowHeight, + groups, + headerHeight, + internalState, + itemType, + mergedRowRef, + onRangeChanged, + parsedColumns, + pinnedLeftColumnCount, + pinnedLeftColumnRef, + pinnedRightColumnCount, + pinnedRightColumnRef, + pinnedRowCount, + pinnedRowRef, + playerContext, + showLeftShadow, + showRightShadow, + showTopShadow, + size, + startRowIndex, + tableId, + totalColumnCount, + totalRowCount, +}: VirtualizedTableGridProps) => { + const columnWidth = useCallback( + (index: number) => calculatedColumnWidths[index], + [calculatedColumnWidths], + ); + + // Calculate pinned column widths for group header positioning + const pinnedLeftColumnWidths = useMemo(() => { + return Array.from({ length: pinnedLeftColumnCount }, (_, i) => columnWidth(i)); + }, [pinnedLeftColumnCount, columnWidth]); + + const pinnedRightColumnWidths = useMemo(() => { + return Array.from({ length: pinnedRightColumnCount }, (_, i) => + columnWidth(i + pinnedLeftColumnCount + totalColumnCount), ); + }, [pinnedRightColumnCount, pinnedLeftColumnCount, totalColumnCount, columnWidth]); - // Calculate pinned column widths for group header positioning - const pinnedLeftColumnWidths = useMemo(() => { - return Array.from({ length: pinnedLeftColumnCount }, (_, i) => columnWidth(i)); - }, [pinnedLeftColumnCount, columnWidth]); + // Create data array with group headers inserted as null values + // Groups are defined by itemCount, so we calculate indexes based on cumulative item counts + const dataWithGroups = useMemo(() => { + const result: (null | unknown)[] = enableHeader ? [null] : []; - const pinnedRightColumnWidths = useMemo(() => { - return Array.from({ length: pinnedRightColumnCount }, (_, i) => - columnWidth(i + pinnedLeftColumnCount + totalColumnCount), - ); - }, [pinnedRightColumnCount, pinnedLeftColumnCount, totalColumnCount, columnWidth]); - - // Create data array with group headers inserted as null values - // Groups are defined by itemCount, so we calculate indexes based on cumulative item counts - const dataWithGroups = useMemo(() => { - const result: (null | unknown)[] = enableHeader ? [null] : []; - - if (!groups || groups.length === 0) { - // No groups, just add all data - result.push(...data); - return result; - } - - // Calculate group header indexes based on itemCounts - const groupIndexes: number[] = []; - let cumulativeDataIndex = 0; - const headerOffset = enableHeader ? 1 : 0; - - groups.forEach((group, groupIndex) => { - // Group header appears before its items - // Index = header offset + cumulative data index + number of previous group headers - const groupHeaderIndex = headerOffset + cumulativeDataIndex + groupIndex; - groupIndexes.push(groupHeaderIndex); - cumulativeDataIndex += group.itemCount; - }); - - let dataIndex = 0; - const startIndex = enableHeader ? 1 : 0; - let groupHeaderCount = 0; - - // Iterate through the expanded row space (data + group headers) - for ( - let rowIndex = startIndex; - rowIndex < startIndex + data.length + groupIndexes.length; - rowIndex++ - ) { - // Check if this row should have a group header - const expectedGroupIndex = groupIndexes[groupHeaderCount]; - if (expectedGroupIndex !== undefined && rowIndex === expectedGroupIndex) { - result.push(null); // Group header row - groupHeaderCount++; - } else if (dataIndex < data.length) { - result.push(data[dataIndex]); - dataIndex++; - } - } + if (!groups || groups.length === 0) { + // No groups, just add all data + result.push(...data); return result; - }, [data, enableHeader, groups]); + } - const adjustedRowIndexMap = useMemo(() => { - const map = new Map(); + // Calculate group header indexes based on itemCounts + const groupIndexes: number[] = []; + let cumulativeDataIndex = 0; + const headerOffset = enableHeader ? 1 : 0; - if (!groups || groups.length === 0) { - const startIndex = enableHeader ? 1 : 0; - const endIndex = enableHeader ? dataWithGroups.length : dataWithGroups.length; - for (let rowIndex = startIndex; rowIndex < endIndex; rowIndex++) { - map.set(rowIndex, enableHeader ? rowIndex : rowIndex + 1); - } - return map; + groups.forEach((group, groupIndex) => { + // Group header appears before its items + // Index = header offset + cumulative data index + number of previous group headers + const groupHeaderIndex = headerOffset + cumulativeDataIndex + groupIndex; + groupIndexes.push(groupHeaderIndex); + cumulativeDataIndex += group.itemCount; + }); + + let dataIndex = 0; + const startIndex = enableHeader ? 1 : 0; + let groupHeaderCount = 0; + + // Iterate through the expanded row space (data + group headers) + for ( + let rowIndex = startIndex; + rowIndex < startIndex + data.length + groupIndexes.length; + rowIndex++ + ) { + // Check if this row should have a group header + const expectedGroupIndex = groupIndexes[groupHeaderCount]; + if (expectedGroupIndex !== undefined && rowIndex === expectedGroupIndex) { + result.push(null); // Group header row + groupHeaderCount++; + } else if (dataIndex < data.length) { + result.push(data[dataIndex]); + dataIndex++; } + } + return result; + }, [data, enableHeader, groups]); - const groupIndexes: number[] = []; - let cumulativeDataIndex = 0; - const headerOffset = enableHeader ? 1 : 0; - - groups.forEach((group, groupIndex) => { - const groupHeaderIndex = headerOffset + cumulativeDataIndex + groupIndex; - groupIndexes.push(groupHeaderIndex); - cumulativeDataIndex += group.itemCount; - }); - - let adjustedIndex = 1; - const startIndex = enableHeader ? 0 : 0; - const endIndex = dataWithGroups.length; + const adjustedRowIndexMap = useMemo(() => { + const map = new Map(); + if (!groups || groups.length === 0) { + const startIndex = enableHeader ? 1 : 0; + const endIndex = enableHeader ? dataWithGroups.length : dataWithGroups.length; for (let rowIndex = startIndex; rowIndex < endIndex; rowIndex++) { - if (enableHeader && rowIndex === 0) { - // Header row - map.set(rowIndex, 0); - } else if (groupIndexes.includes(rowIndex)) { - // Group header row - don't increment adjustedIndex - map.set(rowIndex, 0); - } else { - // Data row - map.set(rowIndex, adjustedIndex); - adjustedIndex++; - } + map.set(rowIndex, enableHeader ? rowIndex : rowIndex + 1); } - return map; - }, [dataWithGroups, enableHeader, groups]); + } - const itemProps: TableItemProps = useMemo( - () => ({ - activeRowId, - adjustedRowIndexMap, - calculatedColumnWidths, - cellPadding, - columns: parsedColumns, - controls, - data: dataWithGroups, - enableAlternateRowColors, - enableColumnReorder, - enableColumnResize, - enableDrag, - enableExpansion, - enableHeader, - enableHorizontalBorders, - enableRowHoverHighlight, - enableSelection, - enableVerticalBorders, - getRowHeight, - groups, - internalState, - itemType, - pinnedLeftColumnCount, - pinnedLeftColumnWidths, - pinnedRightColumnCount, - pinnedRightColumnWidths, - playerContext, - size, - startRowIndex, - tableId, - }), - [ - activeRowId, - adjustedRowIndexMap, - calculatedColumnWidths, - cellPadding, - parsedColumns, - controls, - dataWithGroups, - enableAlternateRowColors, - enableColumnReorder, - enableColumnResize, - enableDrag, - enableExpansion, - enableHeader, - enableHorizontalBorders, - enableRowHoverHighlight, - enableSelection, - enableVerticalBorders, - getRowHeight, - groups, - internalState, - itemType, - pinnedLeftColumnCount, - pinnedLeftColumnWidths, - pinnedRightColumnCount, - pinnedRightColumnWidths, - playerContext, - size, - startRowIndex, - tableId, - ], - ); + const groupIndexes: number[] = []; + let cumulativeDataIndex = 0; + const headerOffset = enableHeader ? 1 : 0; - const PinnedRowCell = useCallback( - (cellProps: CellComponentProps & TableItemProps) => { - return ( - - ); - }, - [pinnedLeftColumnCount, CellComponent], - ); + groups.forEach((group, groupIndex) => { + const groupHeaderIndex = headerOffset + cumulativeDataIndex + groupIndex; + groupIndexes.push(groupHeaderIndex); + cumulativeDataIndex += group.itemCount; + }); - const PinnedColumnCell = useCallback( - (cellProps: CellComponentProps & TableItemProps) => { - return ( - - ); - }, - [pinnedRowCount, CellComponent], - ); + let adjustedIndex = 1; + const startIndex = enableHeader ? 0 : 0; + const endIndex = dataWithGroups.length; - const PinnedRightColumnCell = useCallback( - (cellProps: CellComponentProps & TableItemProps) => { - return ( - ({ + activeRowId, + adjustedRowIndexMap, + calculatedColumnWidths, + cellPadding, + columns: parsedColumns, + controls, + data: dataWithGroups, + enableAlternateRowColors, + enableColumnReorder, + enableColumnResize, + enableDrag, + enableExpansion, + enableHeader, + enableHorizontalBorders, + enableRowHoverHighlight, + enableSelection, + enableVerticalBorders, + getRowHeight, + groups, + internalState, + itemType, + pinnedLeftColumnCount, + pinnedLeftColumnWidths, + pinnedRightColumnCount, + pinnedRightColumnWidths, + playerContext, + size, + startRowIndex, + tableId, + }), + [ + activeRowId, + adjustedRowIndexMap, + calculatedColumnWidths, + cellPadding, + parsedColumns, + controls, + dataWithGroups, + enableAlternateRowColors, + enableColumnReorder, + enableColumnResize, + enableDrag, + enableExpansion, + enableHeader, + enableHorizontalBorders, + enableRowHoverHighlight, + enableSelection, + enableVerticalBorders, + getRowHeight, + groups, + internalState, + itemType, + pinnedLeftColumnCount, + pinnedLeftColumnWidths, + pinnedRightColumnCount, + pinnedRightColumnWidths, + playerContext, + size, + startRowIndex, + tableId, + ], + ); + + const PinnedRowCell = useCallback( + (cellProps: CellComponentProps & TableItemProps) => { + return ( + + ); + }, + [pinnedLeftColumnCount, CellComponent], + ); + + const PinnedColumnCell = useCallback( + (cellProps: CellComponentProps & TableItemProps) => { + return ; + }, + [pinnedRowCount, CellComponent], + ); + + const PinnedRightColumnCell = useCallback( + (cellProps: CellComponentProps & TableItemProps) => { + return ( + + ); + }, + [pinnedLeftColumnCount, pinnedRowCount, totalColumnCount, CellComponent], + ); + + const PinnedRightIntersectionCell = useCallback( + (cellProps: CellComponentProps & TableItemProps) => { + return ( + + ); + }, + [pinnedLeftColumnCount, totalColumnCount, CellComponent], + ); + + const RowCell = useCallback( + (cellProps: CellComponentProps) => { + return ( + + ); + }, + [pinnedLeftColumnCount, pinnedRowCount, CellComponent], + ); + + const handleOnCellsRendered = useCallback( + (items: { + columnStartIndex: number; + columnStopIndex: number; + rowStartIndex: number; + rowStopIndex: number; + }) => { + onRangeChanged?.({ + startIndex: items.rowStartIndex, + stopIndex: items.rowStopIndex, + }); + }, + [onRangeChanged], + ); + + return ( +
+
0).reduce( + (a, _, i) => a + columnWidth(i), + 0, + )}px`, + } as React.CSSProperties + } + > + {!!(pinnedLeftColumnCount || pinnedRowCount) && ( +
0).reduce( + (a, _, i) => a + getRowHeight(i, itemProps), + 0, + )}px`, + overflow: 'hidden', + }} + > + +
+ )} + {enableHeader && showTopShadow && ( +
+ )} + {!!pinnedLeftColumnCount && ( +
+ { + return getRowHeight(index + pinnedRowCount, cellProps); + }} + /> +
+ )} +
+
+ {!!pinnedRowCount && ( +
0, + ).reduce((a, _, i) => a + getRowHeight(i, itemProps), 0)}px`, + overflow: 'hidden', + } as React.CSSProperties } - rowIndex={cellProps.rowIndex + pinnedRowCount} + > + { + return columnWidth(index + pinnedLeftColumnCount); + }} + rowCount={Array.from({ length: pinnedRowCount }, () => 0).length} + rowHeight={getRowHeight} + /> +
+ )} + {enableHeader && showTopShadow && ( +
+ )} +
+ { + return columnWidth(index + pinnedLeftColumnCount); + }} + onCellsRendered={handleOnCellsRendered} + rowCount={totalRowCount} + rowHeight={(index, cellProps) => { + return getRowHeight(index + pinnedRowCount, cellProps); + }} /> - ); - }, - [pinnedLeftColumnCount, pinnedRowCount, totalColumnCount, CellComponent], - ); - - const PinnedRightIntersectionCell = useCallback( - (cellProps: CellComponentProps & TableItemProps) => { - return ( - - ); - }, - [pinnedLeftColumnCount, totalColumnCount, CellComponent], - ); - - const RowCell = useCallback( - (cellProps: CellComponentProps) => { - return ( - - ); - }, - [pinnedLeftColumnCount, pinnedRowCount, CellComponent], - ); - - const handleOnCellsRendered = useCallback( - (items: { - columnStartIndex: number; - columnStopIndex: number; - rowStartIndex: number; - rowStopIndex: number; - }) => { - onRangeChanged?.({ - startIndex: items.rowStartIndex, - stopIndex: items.rowStopIndex, - }); - }, - [onRangeChanged], - ); - - return ( -
+ {pinnedLeftColumnCount > 0 && showLeftShadow && ( +
+ )} + {pinnedRightColumnCount > 0 && showRightShadow && ( +
+ )} +
+
+ {!!pinnedRightColumnCount && (
0, - ).reduce((a, _, i) => a + columnWidth(i), 0)}px`, + ).reduce( + (a, _, i) => + a + columnWidth(i + pinnedLeftColumnCount + totalColumnCount), + 0, + )}px`, } as React.CSSProperties } > - {!!(pinnedLeftColumnCount || pinnedRowCount) && ( + {!!(pinnedRightColumnCount || pinnedRowCount) && (
{ + return columnWidth( + index + pinnedLeftColumnCount + totalColumnCount, + ); + }} rowCount={pinnedRowCount} rowHeight={getRowHeight} /> @@ -462,192 +586,31 @@ const VirtualizedTableGrid = React.memo( {enableHeader && showTopShadow && (
)} - {!!pinnedLeftColumnCount && ( -
- { - return getRowHeight(index + pinnedRowCount, cellProps); - }} - /> -
- )} -
-
- {!!pinnedRowCount && ( -
0, - ).reduce((a, _, i) => a + getRowHeight(i, itemProps), 0)}px`, - overflow: 'hidden', - } as React.CSSProperties - } - > - { - return columnWidth(index + pinnedLeftColumnCount); - }} - rowCount={Array.from({ length: pinnedRowCount }, () => 0).length} - rowHeight={getRowHeight} - /> -
- )} - {enableHeader && showTopShadow && ( -
- )} -
+
{ - return columnWidth(index + pinnedLeftColumnCount); + return columnWidth( + index + pinnedLeftColumnCount + totalColumnCount, + ); }} - onCellsRendered={handleOnCellsRendered} rowCount={totalRowCount} rowHeight={(index, cellProps) => { return getRowHeight(index + pinnedRowCount, cellProps); }} /> - {pinnedLeftColumnCount > 0 && showLeftShadow && ( -
- )} - {pinnedRightColumnCount > 0 && showRightShadow && ( -
- )}
- {!!pinnedRightColumnCount && ( -
0, - ).reduce( - (a, _, i) => - a + - columnWidth(i + pinnedLeftColumnCount + totalColumnCount), - 0, - )}px`, - } as React.CSSProperties - } - > - {!!(pinnedRightColumnCount || pinnedRowCount) && ( -
0, - ).reduce((a, _, i) => a + getRowHeight(i, itemProps), 0)}px`, - overflow: 'hidden', - }} - > - { - return columnWidth( - index + pinnedLeftColumnCount + totalColumnCount, - ); - }} - rowCount={pinnedRowCount} - rowHeight={getRowHeight} - /> -
- )} - {enableHeader && showTopShadow && ( -
- )} -
- { - return columnWidth( - index + pinnedLeftColumnCount + totalColumnCount, - ); - }} - rowCount={totalRowCount} - rowHeight={(index, cellProps) => { - return getRowHeight(index + pinnedRowCount, cellProps); - }} - /> -
-
- )} -
- ); - }, - (prevProps, nextProps) => { - const prevColumnIds = prevProps.parsedColumns.map((col) => col.id).join(','); - const nextColumnIds = nextProps.parsedColumns.map((col) => col.id).join(','); - - const columnWidthsEqual = prevProps.calculatedColumnWidths.every( - (width, index) => width === nextProps.calculatedColumnWidths[index], - ); - - if (prevColumnIds !== nextColumnIds) { - return false; - } - - return ( - columnWidthsEqual && - prevProps.activeRowId === nextProps.activeRowId && - prevProps.data === nextProps.data && - prevProps.size === nextProps.size && - prevProps.startRowIndex === nextProps.startRowIndex && - prevProps.enableVerticalBorders === nextProps.enableVerticalBorders && - prevProps.enableHorizontalBorders === nextProps.enableHorizontalBorders && - prevProps.enableRowHoverHighlight === nextProps.enableRowHoverHighlight && - prevProps.enableAlternateRowColors === nextProps.enableAlternateRowColors && - prevProps.pinnedLeftColumnCount === nextProps.pinnedLeftColumnCount && - prevProps.pinnedRightColumnCount === nextProps.pinnedRightColumnCount && - prevProps.totalColumnCount === nextProps.totalColumnCount && - prevProps.totalRowCount === nextProps.totalRowCount - ); - }, -); + )} +
+ ); +}; VirtualizedTableGrid.displayName = 'VirtualizedTableGrid'; diff --git a/src/renderer/features/shared/components/table-config.tsx b/src/renderer/features/shared/components/table-config.tsx index 7a127ffc6..aab7f0ccb 100644 --- a/src/renderer/features/shared/components/table-config.tsx +++ b/src/renderer/features/shared/components/table-config.tsx @@ -774,7 +774,7 @@ const TableColumnItem = memo( postProcess: 'sentenceCase', }), }} - variant={item.align === 'end' ? 'filled' : 'subtle'} + variant={item.align === 'end' ? 'outline' : 'subtle'} />