From 0ba4986e1562e0f6e221e141e0e85995db640891 Mon Sep 17 00:00:00 2001 From: Sergey Vinogradov Date: Tue, 5 Mar 2024 12:43:52 +0200 Subject: [PATCH] fix: delay column width recalculation until all rows have index (#7165) (#7168) --- packages/grid/src/vaadin-grid.js | 30 ++++++++++------ packages/grid/test/column-auto-width.test.js | 36 ++++++++++++++++++-- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/packages/grid/src/vaadin-grid.js b/packages/grid/src/vaadin-grid.js index f974af6c69..294f94a882 100644 --- a/packages/grid/src/vaadin-grid.js +++ b/packages/grid/src/vaadin-grid.js @@ -535,18 +535,28 @@ class Grid extends ElementMixin( } } - /** @private */ - __hasRowsWithClientHeight() { - return !!Array.from(this.$.items.children).filter((row) => row.clientHeight).length; - } - /** @protected */ __itemsReceived() { - if ( - this._recalculateColumnWidthOnceLoadingFinished && - !this._cache.isLoading() && - this.__hasRowsWithClientHeight() - ) { + if (!this._recalculateColumnWidthOnceLoadingFinished || this._cache.isLoading()) { + return; + } + + // Delay recalculation if any rows are missing an index. + // This can happen during the grid's initialization if the recalculation is triggered + // as a result of the data provider responding synchronously to a page request created + // in the middle of the virtualizer update loop. In this case, rows after the one that + // triggered the page request may not have an index property yet. The lack of index + // prevents _onDataProviderPageReceived from requesting children for these rows, + // resulting in loading state being set to false and the recalculation beginning + // before all the data is loaded. Note, rows without index get updated in later iterations + // of the virtualizer update loop, ensuring the grid eventually reaches a stable state. + const hasRowsWithUndefinedIndex = [...this.$.items.children].some((row) => row.index === undefined); + if (hasRowsWithUndefinedIndex) { + return; + } + + const hasRowsWithClientHeight = [...this.$.items.children].some((row) => row.clientHeight > 0); + if (hasRowsWithClientHeight) { this._recalculateColumnWidthOnceLoadingFinished = false; this.recalculateColumnWidths(); } diff --git a/packages/grid/test/column-auto-width.test.js b/packages/grid/test/column-auto-width.test.js index e50f71e18a..9098fdbf0b 100644 --- a/packages/grid/test/column-auto-width.test.js +++ b/packages/grid/test/column-auto-width.test.js @@ -1,5 +1,5 @@ import { expect } from '@esm-bundle/chai'; -import { fixtureSync, nextFrame, oneEvent } from '@vaadin/testing-helpers'; +import { aTimeout, fixtureSync, nextFrame, oneEvent } from '@vaadin/testing-helpers'; import sinon from 'sinon'; import '../vaadin-grid.js'; import '../vaadin-grid-column-group.js'; @@ -240,7 +240,7 @@ describe('async recalculateWidth columns', () => { `); }); - it('should recalculate column widths when child items loaded', () => { + it('should recalculate column widths when child items are loaded synchronously', () => { const data = [ { name: 'foo', @@ -264,6 +264,38 @@ describe('async recalculateWidth columns', () => { flushGrid(grid); expect(grid._recalculateColumnWidths.called).to.be.true; }); + + describe('initially empty grid', () => { + let recalculateColumnWidthsSpy, dataProvider; + + beforeEach(() => { + recalculateColumnWidthsSpy = sinon.spy(grid, 'recalculateColumnWidths'); + dataProvider = (_params, callback) => callback([], 0); + grid.dataProvider = (params, callback) => dataProvider(params, callback); + flushGrid(grid); + recalculateColumnWidthsSpy.resetHistory(); + }); + + it('should recalculate column widths when child items are loaded asynchonously', async () => { + const items = [{ name: 'Item-0' }, { name: 'Item-1', children: [{ name: 'Item-1-0' }] }]; + + dataProvider = ({ parentItem }, callback) => { + if (parentItem) { + setTimeout(() => callback(parentItem.children, parentItem.children.length)); + } else { + callback(items.slice(0, grid.size), grid.size); + } + }; + + grid.expandItem(items[1]); + grid.size = 2; + flushGrid(grid); + + expect(recalculateColumnWidthsSpy.called).to.be.false; + await aTimeout(0); + expect(recalculateColumnWidthsSpy.calledOnce).to.be.true; + }); + }); }); describe('column group', () => {