diff --git a/packages/grid/src/vaadin-grid-mixin.js b/packages/grid/src/vaadin-grid-mixin.js index 4f80e1abb7..be84d25aa5 100644 --- a/packages/grid/src/vaadin-grid-mixin.js +++ b/packages/grid/src/vaadin-grid-mixin.js @@ -316,11 +316,6 @@ export const GridMixin = (superClass) => } } - /** @private */ - __hasRowsWithClientHeight() { - return !!Array.from(this.$.items.children).filter((row) => row.clientHeight).length; - } - /** @private */ __getIntrinsicWidth(col) { if (!this.__intrinsicWidthCache.has(col)) { @@ -498,12 +493,26 @@ export const GridMixin = (superClass) => /** @private */ __tryToRecalculateColumnWidthsIfPending() { - if ( - this.__pendingRecalculateColumnWidths && - !isElementHidden(this) && - !this._dataProviderController.isLoading() && - this.__hasRowsWithClientHeight() - ) { + if (!this.__pendingRecalculateColumnWidths || isElementHidden(this) || this._dataProviderController.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.__pendingRecalculateColumnWidths = false; this.recalculateColumnWidths(); } diff --git a/packages/grid/test/column-auto-width.common.js b/packages/grid/test/column-auto-width.common.js index b6f661a370..bd4c5488a9 100644 --- a/packages/grid/test/column-auto-width.common.js +++ b/packages/grid/test/column-auto-width.common.js @@ -314,7 +314,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', @@ -339,6 +339,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).to.be.not.called; + await aTimeout(0); + expect(recalculateColumnWidthsSpy).to.be.calledOnce; + }); + }); }); describe('column group', () => {