Skip to content

Commit 90c64d9

Browse files
committed
fix: ignore grid row clicks while item data is still loading
Return early from onItemActivate when the clicked row has no loaded item, so a click on a still-loading row no longer triggers selection or details toggling. Deselect the clicked item instead of all selected items, so the decision uses the item's current data rather than stale state.
1 parent dbe6902 commit 90c64d9

3 files changed

Lines changed: 48 additions & 17 deletions

File tree

vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/test/grid-connector-row-details.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,16 @@ describe('grid connector - row details', () => {
111111
setRootItems(grid.$connector, [{ key: '0', name: 'foo', selected: true }]);
112112
expect(grid.$server.setDetailsVisible).not.to.be.calledWith('0');
113113
});
114+
115+
it('should not set details visible when clicking a still-loading row', async () => {
116+
// Reset to a single row whose data hasn't loaded yet, so it renders in a
117+
// loading state with no item in its model
118+
grid.$connector.reset();
119+
grid.$connector.updateSize(1);
120+
await nextFrame();
121+
122+
getBodyCellContent(grid, 0, 0)!.click();
123+
124+
expect(grid.$server.setDetailsVisible).not.to.be.called;
125+
});
114126
});

vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/test/grid-connector-selection.test.ts

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,20 @@ describe('grid connector - selection', () => {
6262
expect(grid.selectedItems[0].key).to.equal('1');
6363
});
6464

65+
it('should not select when clicking a still-loading row', async () => {
66+
// Reset to a single row whose data hasn't loaded yet, so it renders in a
67+
// loading state with no item in its model
68+
grid.$connector.reset();
69+
grid.$connector.updateSize(1);
70+
await nextFrame();
71+
72+
getBodyCellContent(grid, 0, 0)!.click();
73+
74+
expect(grid.selectedItems).to.be.empty;
75+
expect(grid.$server.select).not.to.be.called;
76+
expect(grid.$server.deselect).not.to.be.called;
77+
});
78+
6579
it('should mark the item deselected', () => {
6680
getBodyCellContent(grid, 0, 0)!.click();
6781
const item = grid.selectedItems[0];
@@ -209,6 +223,13 @@ describe('grid connector - selection', () => {
209223
expect(grid.$server.select).to.be.calledWith(items[2].key);
210224
});
211225

226+
it('should prevent selection of items on click after updateFlatData makes them non-selectable', () => {
227+
grid.$connector.updateFlatData([{ ...items[2], selectable: false }]);
228+
getBodyCellContent(grid, 2, 0)!.click();
229+
expect(grid.selectedItems).to.be.empty;
230+
expect(grid.$server.select).to.not.be.called;
231+
});
232+
212233
it('should prevent deselection of non-selectable items on click', () => {
213234
grid.$connector.doSelection([items[0]], false);
214235
getBodyCellContent(grid, 0, 0)!.click();
@@ -223,25 +244,19 @@ describe('grid connector - selection', () => {
223244
expect(grid.$server.deselect).to.not.be.called;
224245
});
225246

226-
it('should prevent deselection of non-selectable items on row click when active item data is stale', () => {
227-
// item is selectable initially and is selected
247+
it('should allow deselection of selectable items on row click', () => {
228248
grid.$connector.doSelection([items[2]], false);
229-
230-
// update grid items to make the item non-selectable
231-
const updatedItems = items.map((item) => ({ ...item, selectable: false }));
232-
setRootItems(grid.$connector, updatedItems);
233-
234-
// however clicking the row should not deselect the item
235249
getBodyCellContent(grid, 2, 0)!.click();
236-
expect(grid.selectedItems).to.deep.equal([updatedItems[2]]);
237-
expect(grid.$server.deselect).to.not.be.called;
250+
expect(grid.selectedItems).to.be.empty;
251+
expect(grid.$server.deselect).to.be.calledWith(items[2].key);
238252
});
239253

240-
it('should allow deselection of selectable items on row click', () => {
254+
it('should prevent deselection of items on click after updateFlatData makes them non-selectable', () => {
241255
grid.$connector.doSelection([items[2]], false);
256+
grid.$connector.updateFlatData([{ ...items[2], selectable: false }]);
242257
getBodyCellContent(grid, 2, 0)!.click();
243-
expect(grid.selectedItems).to.be.empty;
244-
expect(grid.$server.deselect).to.be.calledWith(items[2].key);
258+
expect(grid.selectedItems).to.deep.equal([items[2]]);
259+
expect(grid.$server.deselect).to.not.be.called;
245260
});
246261

247262
it('should always allow selection from server', () => {

vaadin-grid-flow-parent/vaadin-grid-flow/src/main/resources/META-INF/frontend/gridConnector.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,18 +110,22 @@ window.Vaadin.Flow.gridConnector.initLazy = (grid) => {
110110
};
111111

112112
function onItemActivate(event) {
113-
const item = event.detail.model?.item;
113+
const { item } = event.detail.model;
114+
if (!item) {
115+
// Item isn't loaded yet
116+
return;
117+
}
114118

115119
if (selectionMode === 'SINGLE') {
116-
if (item && !item.selected) {
120+
if (!item.selected) {
117121
grid.$connector.doSelection([item], true);
118122
} else if (!grid.__deselectDisallowed) {
119-
grid.$connector.doDeselection([...grid.selectedItems], true);
123+
grid.$connector.doDeselection([item], true);
120124
}
121125
}
122126

123127
if (!grid.__disallowDetailsOnClick) {
124-
if (item && !item.detailsOpened) {
128+
if (!item.detailsOpened) {
125129
grid.$server.setDetailsVisible(item.key);
126130
} else {
127131
grid.$server.setDetailsVisible(null);

0 commit comments

Comments
 (0)