diff --git a/packages/common/src/core/__tests__/slickGrid.spec.ts b/packages/common/src/core/__tests__/slickGrid.spec.ts index a7ed24948..fa6143ae0 100644 --- a/packages/common/src/core/__tests__/slickGrid.spec.ts +++ b/packages/common/src/core/__tests__/slickGrid.spec.ts @@ -5718,6 +5718,20 @@ describe('SlickGrid core file', () => { expect(frozenCanvas?.classList.contains('grid-canvas-left')).toBeTruthy(); }); + it('should find the first column intersecting the horizontal render range', () => { + const wideColumns = Array.from({ length: 100 }, (_, index) => ({ + id: `column${index}`, + field: `column${index}`, + name: `Column ${index}`, + width: 80, + })) as Column[]; + grid = new SlickGrid(container, [{}], wideColumns, defaultOptions); + + expect((grid as any).getFirstColumnIndexAtOrAfter(0)).toBe(0); + expect((grid as any).getFirstColumnIndexAtOrAfter(800)).toBe(10); + expect((grid as any).getFirstColumnIndexAtOrAfter(8000)).toBe(100); + }); + it('should use bottom-right and top-right scroll containers when frozen columns with frozen-bottom rows are enabled', () => { const dataWithThreeRows = [ { id: 0, firstName: 'John', lastName: 'Doe', age: 30 }, diff --git a/packages/common/src/core/slickGrid.ts b/packages/common/src/core/slickGrid.ts index f8c37413b..1dd83b352 100755 --- a/packages/common/src/core/slickGrid.ts +++ b/packages/common/src/core/slickGrid.ts @@ -5482,6 +5482,14 @@ export class SlickGrid = Column, O e let colspan: number | string; let columnData: ColumnMetadata | null; const columnCount = this.columns.length; + const hasAlwaysRenderColumn = this.columns.some((column) => column?.alwaysRenderColumn); + let firstColumnIndex = 0; + + // columnPosRight is monotonic only when there are no frozen columns, so use a lower-bound lookup + // to avoid scanning columns that are entirely left of the rendered range in the common case. + if (!this.hasFrozenColumns() && !hasAlwaysRenderColumn) { + firstColumnIndex = this.getFirstColumnIndexAtOrAfter(range.leftPx); + } for (let row = range.top as number, btm = range.bottom as number; row <= btm; row++) { cacheEntry = this.rowsCache[row]; @@ -5501,9 +5509,9 @@ export class SlickGrid = Column, O e const d = this.getDataItem(row); let isFullColspan = false; + const startColumnIndex = metadataCol || metadata?.isGroup ? 0 : firstColumnIndex; - // TODO: shorten this loop (index? heuristics? binary search?) - for (let i = 0, ii = columnCount; i < ii; i++) { + for (let i = startColumnIndex, ii = columnCount; i < ii; i++) { if (this.columns[i] && (!this.columns[i].hidden || metadata?.isGroup)) { // Cells to the right are outside the range. if (this.columnPosLeft[i] > range.rightPx) { @@ -5579,6 +5587,20 @@ export class SlickGrid = Column, O e } } + protected getFirstColumnIndexAtOrAfter(leftPx: number): number { + let low = 0; + let high = this.columnPosRight.length; + while (low < high) { + const mid = low + Math.floor((high - low) / 2); + if (this.columnPosRight[mid] <= leftPx) { + low = mid + 1; + } else { + high = mid; + } + } + return low; + } + protected createEmptyCachingRow(): RowCaching { return { rowNode: null,