@vaadin/grid 25.0.0-alpha1 → 25.0.0-alpha11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/all-imports.js +1 -1
  2. package/package.json +12 -13
  3. package/src/array-data-provider.js +6 -0
  4. package/src/lit/column-renderer-directives.d.ts +0 -1
  5. package/src/styles/vaadin-grid-base-styles.d.ts +8 -0
  6. package/src/styles/vaadin-grid-base-styles.js +510 -0
  7. package/src/styles/vaadin-grid-filter-base-styles.d.ts +8 -0
  8. package/src/styles/vaadin-grid-filter-base-styles.js +18 -0
  9. package/src/styles/vaadin-grid-sorter-base-styles.d.ts +8 -0
  10. package/src/styles/vaadin-grid-sorter-base-styles.js +63 -0
  11. package/src/styles/vaadin-grid-tree-toggle-base-styles.d.ts +8 -0
  12. package/src/styles/vaadin-grid-tree-toggle-base-styles.js +76 -0
  13. package/src/vaadin-grid-a11y-mixin.js +14 -8
  14. package/src/vaadin-grid-column-auto-width-mixin.js +8 -2
  15. package/src/vaadin-grid-data-provider-mixin.js +19 -76
  16. package/src/vaadin-grid-drag-and-drop-mixin.js +3 -0
  17. package/src/vaadin-grid-filter-element-mixin.js +0 -17
  18. package/src/vaadin-grid-filter.js +7 -1
  19. package/src/vaadin-grid-keyboard-navigation-mixin.js +1 -1
  20. package/src/vaadin-grid-mixin.js +51 -28
  21. package/src/vaadin-grid-row-details-mixin.js +4 -4
  22. package/src/vaadin-grid-scroll-mixin.js +52 -21
  23. package/src/vaadin-grid-sorter-mixin.js +0 -60
  24. package/src/vaadin-grid-sorter.js +7 -1
  25. package/src/vaadin-grid-tree-toggle-mixin.js +0 -77
  26. package/src/vaadin-grid-tree-toggle.js +9 -1
  27. package/src/vaadin-grid.d.ts +1 -1
  28. package/src/vaadin-grid.js +4 -3
  29. package/vaadin-grid-column-group.js +1 -1
  30. package/vaadin-grid-column.js +1 -1
  31. package/vaadin-grid-filter-column.js +1 -1
  32. package/vaadin-grid-filter.js +1 -1
  33. package/vaadin-grid-selection-column.js +1 -1
  34. package/vaadin-grid-sort-column.js +1 -1
  35. package/vaadin-grid-sorter.js +1 -1
  36. package/vaadin-grid-tree-column.js +1 -1
  37. package/vaadin-grid-tree-toggle.js +1 -1
  38. package/vaadin-grid.js +1 -1
  39. package/web-types.json +4 -4
  40. package/web-types.lit.json +4 -4
  41. package/src/vaadin-grid-styles.js +0 -389
  42. package/theme/lumo/all-imports.d.ts +0 -11
  43. package/theme/lumo/all-imports.js +0 -11
  44. package/theme/lumo/vaadin-grid-column-group.d.ts +0 -1
  45. package/theme/lumo/vaadin-grid-column-group.js +0 -1
  46. package/theme/lumo/vaadin-grid-column.d.ts +0 -1
  47. package/theme/lumo/vaadin-grid-column.js +0 -1
  48. package/theme/lumo/vaadin-grid-filter-column.d.ts +0 -2
  49. package/theme/lumo/vaadin-grid-filter-column.js +0 -2
  50. package/theme/lumo/vaadin-grid-filter.d.ts +0 -2
  51. package/theme/lumo/vaadin-grid-filter.js +0 -2
  52. package/theme/lumo/vaadin-grid-selection-column.d.ts +0 -2
  53. package/theme/lumo/vaadin-grid-selection-column.js +0 -2
  54. package/theme/lumo/vaadin-grid-sort-column.d.ts +0 -2
  55. package/theme/lumo/vaadin-grid-sort-column.js +0 -2
  56. package/theme/lumo/vaadin-grid-sorter-styles.d.ts +0 -3
  57. package/theme/lumo/vaadin-grid-sorter-styles.js +0 -52
  58. package/theme/lumo/vaadin-grid-sorter.d.ts +0 -2
  59. package/theme/lumo/vaadin-grid-sorter.js +0 -2
  60. package/theme/lumo/vaadin-grid-styles.d.ts +0 -6
  61. package/theme/lumo/vaadin-grid-styles.js +0 -405
  62. package/theme/lumo/vaadin-grid-tree-column.d.ts +0 -2
  63. package/theme/lumo/vaadin-grid-tree-column.js +0 -2
  64. package/theme/lumo/vaadin-grid-tree-toggle-styles.d.ts +0 -3
  65. package/theme/lumo/vaadin-grid-tree-toggle-styles.js +0 -81
  66. package/theme/lumo/vaadin-grid-tree-toggle.d.ts +0 -2
  67. package/theme/lumo/vaadin-grid-tree-toggle.js +0 -2
  68. package/theme/lumo/vaadin-grid.d.ts +0 -2
  69. package/theme/lumo/vaadin-grid.js +0 -2
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2016 - 2025 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import '@vaadin/component-base/src/styles/style-props.js';
7
+ import { css } from 'lit';
8
+
9
+ export const gridTreeToggleStyles = css`
10
+ :host {
11
+ display: flex;
12
+ max-width: 100%;
13
+ pointer-events: none;
14
+ }
15
+
16
+ /* Don't expand/collapse when clicking #level-spacer */
17
+ [part] {
18
+ pointer-events: auto;
19
+ }
20
+
21
+ :host([hidden]) {
22
+ display: none !important;
23
+ }
24
+
25
+ :host(:not([leaf])) {
26
+ cursor: var(--vaadin-clickable-cursor);
27
+ }
28
+
29
+ #level-spacer,
30
+ [part='toggle'] {
31
+ flex: none;
32
+ }
33
+
34
+ #level-spacer {
35
+ width: calc(var(--_level, 0) * var(--vaadin-grid-tree-toggle-level-offset, 16px));
36
+ }
37
+
38
+ [part='toggle'] {
39
+ margin-inline-end: var(--vaadin-gap-container-inline);
40
+ }
41
+
42
+ [part='toggle']::before {
43
+ content: '';
44
+ display: block;
45
+ width: var(--vaadin-icon-size, 1lh);
46
+ height: var(--vaadin-icon-size, 1lh);
47
+ background: currentColor;
48
+ mask-image: var(--_vaadin-icon-chevron-down);
49
+ }
50
+
51
+ :host(:not([expanded])) [part='toggle']::before {
52
+ rotate: -90deg;
53
+ }
54
+
55
+ @media (prefers-reduced-motion: no-preference) {
56
+ [part='toggle']::before {
57
+ transition: rotate 120ms;
58
+ }
59
+ }
60
+
61
+ :host([leaf]) [part='toggle'] {
62
+ visibility: hidden;
63
+ }
64
+
65
+ slot {
66
+ display: block;
67
+ overflow: hidden;
68
+ text-overflow: ellipsis;
69
+ }
70
+
71
+ @media (forced-colors: active) {
72
+ [part='toggle']::before {
73
+ background: CanvasText;
74
+ }
75
+ }
76
+ `;
@@ -22,7 +22,7 @@ export const A11yMixin = (superClass) =>
22
22
  };
23
23
  }
24
24
  static get observers() {
25
- return ['_a11yUpdateGridSize(size, _columnTree)'];
25
+ return ['_a11yUpdateGridSize(size, _columnTree, __emptyState)'];
26
26
  }
27
27
 
28
28
  /** @private */
@@ -34,21 +34,27 @@ export const A11yMixin = (superClass) =>
34
34
 
35
35
  /** @private */
36
36
  _a11yGetFooterRowCount(_columnTree) {
37
- return _columnTree.filter((level) => level.some((col) => col.headerRenderer)).length;
37
+ return _columnTree.filter((level) => level.some((col) => col.footerRenderer)).length;
38
38
  }
39
39
 
40
40
  /** @private */
41
- _a11yUpdateGridSize(size, _columnTree) {
41
+ _a11yUpdateGridSize(size, _columnTree, emptyState) {
42
42
  if (size === undefined || _columnTree === undefined) {
43
43
  return;
44
44
  }
45
45
 
46
+ const headerRowsCount = this._a11yGetHeaderRowCount(_columnTree);
47
+ const footerRowsCount = this._a11yGetFooterRowCount(_columnTree);
48
+ const bodyRowsCount = emptyState ? 1 : size;
49
+ const rowsCount = bodyRowsCount + headerRowsCount + footerRowsCount;
50
+
51
+ this.$.table.setAttribute('aria-rowcount', rowsCount);
52
+
46
53
  const bodyColumns = _columnTree[_columnTree.length - 1];
47
- this.$.table.setAttribute(
48
- 'aria-rowcount',
49
- size + this._a11yGetHeaderRowCount(_columnTree) + this._a11yGetFooterRowCount(_columnTree),
50
- );
51
- this.$.table.setAttribute('aria-colcount', (bodyColumns && bodyColumns.length) || 0);
54
+ // If no header and footer rows while the empty state is active, count as one column
55
+ // Otherwise, use the number of body columns, if present
56
+ const columnsCount = emptyState ? 1 : (rowsCount && bodyColumns && bodyColumns.length) || 0;
57
+ this.$.table.setAttribute('aria-colcount', columnsCount);
52
58
 
53
59
  this._a11yUpdateHeaderRows();
54
60
  this._a11yUpdateFooterRows();
@@ -59,9 +59,15 @@ export const ColumnAutoWidthMixin = (superClass) =>
59
59
  }
60
60
 
61
61
  /** @private */
62
- __flatSizeChangedAutoWidth() {
62
+ __flatSizeChangedAutoWidth(flatSize) {
63
63
  // Flat size changed, recalculate column widths if pending (asynchronously, to allow grid to render row elements first)
64
- requestAnimationFrame(() => this.__tryToRecalculateColumnWidthsIfPending());
64
+ requestAnimationFrame(() => {
65
+ if (!!flatSize && !this.__hasHadRenderedRowsForColumnWidthCalculation) {
66
+ this.recalculateColumnWidths();
67
+ } else {
68
+ this.__tryToRecalculateColumnWidthsIfPending();
69
+ }
70
+ });
65
71
  }
66
72
 
67
73
  /**
@@ -7,7 +7,6 @@ import { microTask, timeOut } from '@vaadin/component-base/src/async.js';
7
7
  import { DataProviderController } from '@vaadin/component-base/src/data-provider-controller/data-provider-controller.js';
8
8
  import { Debouncer } from '@vaadin/component-base/src/debounce.js';
9
9
  import { get } from '@vaadin/component-base/src/path-utils.js';
10
- import { getBodyRowCells, updateCellsPart, updateState } from './vaadin-grid-helpers.js';
11
10
 
12
11
  /**
13
12
  * @polymerMixin
@@ -166,24 +165,6 @@ export const DataProviderMixin = (superClass) =>
166
165
  this._dataProviderController.addEventListener('page-loaded', this._onDataProviderPageLoaded.bind(this));
167
166
  }
168
167
 
169
- /**
170
- * @protected
171
- * @deprecated since 24.3 and will be removed in Vaadin 25.
172
- */
173
- get _cache() {
174
- console.warn('<vaadin-grid> The `_cache` property is deprecated and will be removed in Vaadin 25.');
175
- return this._dataProviderController.rootCache;
176
- }
177
-
178
- /**
179
- * @protected
180
- * @deprecated since 24.3 and will be removed in Vaadin 25.
181
- */
182
- get _effectiveSize() {
183
- console.warn('<vaadin-grid> The `_effectiveSize` property is deprecated and will be removed in Vaadin 25.');
184
- return this._flatSize;
185
- }
186
-
187
168
  /** @private */
188
169
  _sizeChanged(size) {
189
170
  this._dataProviderController.rootCache.size = size;
@@ -200,46 +181,20 @@ export const DataProviderMixin = (superClass) =>
200
181
  this.requestContentUpdate();
201
182
  }
202
183
 
203
- /**
204
- * @param {number} index
205
- * @param {HTMLElement} el
206
- * @protected
207
- */
208
- _getItem(index, el) {
209
- el.index = index;
210
-
211
- const { item } = this._dataProviderController.getFlatIndexContext(index);
212
- if (item) {
213
- this.__updateLoading(el, false);
214
- this._updateItem(el, item);
215
- if (this._isExpanded(item)) {
216
- this._dataProviderController.ensureFlatIndexHierarchy(index);
217
- }
218
- } else {
219
- this.__updateLoading(el, true);
220
- this._dataProviderController.ensureFlatIndexLoaded(index);
221
- }
184
+ /** @private */
185
+ __getRowItem(row) {
186
+ const { item } = this._dataProviderController.getFlatIndexContext(row.index);
187
+ return item;
222
188
  }
223
189
 
224
- /**
225
- * @param {!HTMLElement} row
226
- * @param {boolean} loading
227
- * @private
228
- */
229
- __updateLoading(row, loading) {
230
- const cells = getBodyRowCells(row);
231
-
232
- // Row state attribute
233
- updateState(row, 'loading', loading);
234
-
235
- // Cells part attribute
236
- updateCellsPart(cells, 'loading-row-cell', loading);
190
+ /** @private */
191
+ __ensureRowItem(row) {
192
+ this._dataProviderController.ensureFlatIndexLoaded(row.index);
193
+ }
237
194
 
238
- if (loading) {
239
- // Run style generators for the loading row to have custom names cleared
240
- this._generateCellClassNames(row);
241
- this._generateCellPartNames(row);
242
- }
195
+ /** @private */
196
+ __ensureRowHierarchy(row) {
197
+ this._dataProviderController.ensureFlatIndexHierarchy(row.index);
243
198
  }
244
199
 
245
200
  /**
@@ -309,17 +264,6 @@ export const DataProviderMixin = (superClass) =>
309
264
  return level;
310
265
  }
311
266
 
312
- /**
313
- * @param {number} page
314
- * @param {ItemCache} cache
315
- * @protected
316
- * @deprecated since 24.3 and will be removed in Vaadin 25.
317
- */
318
- _loadPage(page, cache) {
319
- console.warn('<vaadin-grid> The `_loadPage` method is deprecated and will be removed in Vaadin 25.');
320
- this._dataProviderController.__loadCachePage(cache, page);
321
- }
322
-
323
267
  /** @protected */
324
268
  _onDataProviderPageRequested() {
325
269
  this._setLoading(true);
@@ -331,7 +275,7 @@ export const DataProviderMixin = (superClass) =>
331
275
  if (this._flatSize !== this._dataProviderController.flatSize) {
332
276
  // Schedule an update of all rendered rows by _debouncerApplyCachedData,
333
277
  // to ensure that all pages associated with the rendered rows are loaded.
334
- this._shouldUpdateAllRenderedRowsAfterPageLoad = true;
278
+ this._shouldLoadAllRenderedRowsAfterPageLoad = true;
335
279
 
336
280
  // TODO: Updating the flat size property can still result in a synchonous virtualizer update
337
281
  // if the size change requires the virtualizer to increase the amount of physical elements
@@ -341,9 +285,7 @@ export const DataProviderMixin = (superClass) =>
341
285
  }
342
286
 
343
287
  // After updating the cache, check if some of the expanded items should have sub-caches loaded
344
- this._getRenderedRows().forEach((row) => {
345
- this._dataProviderController.ensureFlatIndexHierarchy(row.index);
346
- });
288
+ this._getRenderedRows().forEach((row) => this.__ensureRowHierarchy(row));
347
289
 
348
290
  this._hasData = true;
349
291
  }
@@ -354,13 +296,14 @@ export const DataProviderMixin = (superClass) =>
354
296
  this._debouncerApplyCachedData = Debouncer.debounce(this._debouncerApplyCachedData, timeOut.after(0), () => {
355
297
  this._setLoading(false);
356
298
 
357
- const shouldUpdateAllRenderedRowsAfterPageLoad = this._shouldUpdateAllRenderedRowsAfterPageLoad;
358
- this._shouldUpdateAllRenderedRowsAfterPageLoad = false;
299
+ const shouldLoadAllRenderedRowsAfterPageLoad = this._shouldLoadAllRenderedRowsAfterPageLoad;
300
+ this._shouldLoadAllRenderedRowsAfterPageLoad = false;
359
301
 
360
302
  this._getRenderedRows().forEach((row) => {
361
- const { item } = this._dataProviderController.getFlatIndexContext(row.index);
362
- if (item || shouldUpdateAllRenderedRowsAfterPageLoad) {
363
- this._getItem(row.index, row);
303
+ this.__updateRow(row);
304
+
305
+ if (shouldLoadAllRenderedRowsAfterPageLoad) {
306
+ this.__ensureRowItem(row);
364
307
  }
365
308
  });
366
309
 
@@ -247,6 +247,9 @@ export const DragAndDropMixin = (superClass) =>
247
247
 
248
248
  let row = e.composedPath().find((node) => node.localName === 'tr');
249
249
 
250
+ // Update the horizontal scroll position property of the row being dragged over
251
+ this.__updateRowScrollPositionProperty(row);
252
+
250
253
  if (!this._flatSize || this.dropMode === DropMode.ON_GRID) {
251
254
  // The grid is empty or "on-grid" drop mode was used, always default to "empty"
252
255
  this._dropLocation = DropLocation.EMPTY;
@@ -6,23 +6,6 @@
6
6
  import { timeOut } from '@vaadin/component-base/src/async.js';
7
7
  import { Debouncer } from '@vaadin/component-base/src/debounce.js';
8
8
  import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
9
- import { css, registerStyles } from '@vaadin/vaadin-themable-mixin';
10
-
11
- registerStyles(
12
- 'vaadin-grid-filter',
13
- css`
14
- :host {
15
- display: inline-flex;
16
- max-width: 100%;
17
- }
18
-
19
- ::slotted(*) {
20
- width: 100%;
21
- box-sizing: border-box;
22
- }
23
- `,
24
- { moduleId: 'vaadin-grid-filter-styles' },
25
- );
26
9
 
27
10
  /**
28
11
  * @polymerMixin
@@ -8,6 +8,8 @@ import { html, LitElement } from 'lit';
8
8
  import { defineCustomElement } from '@vaadin/component-base/src/define.js';
9
9
  import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
10
10
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin';
11
+ import { LumoInjectionMixin } from '@vaadin/vaadin-themable-mixin/lumo-injection-mixin.js';
12
+ import { gridFilterStyles } from './styles/vaadin-grid-filter-base-styles.js';
11
13
  import { GridFilterElementMixin } from './vaadin-grid-filter-element-mixin.js';
12
14
 
13
15
  /**
@@ -39,11 +41,15 @@ import { GridFilterElementMixin } from './vaadin-grid-filter-element-mixin.js';
39
41
  * @extends HTMLElement
40
42
  * @mixes GridFilterElementMixin
41
43
  */
42
- class GridFilter extends GridFilterElementMixin(ThemableMixin(PolylitMixin(LitElement))) {
44
+ class GridFilter extends GridFilterElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(LitElement)))) {
43
45
  static get is() {
44
46
  return 'vaadin-grid-filter';
45
47
  }
46
48
 
49
+ static get styles() {
50
+ return gridFilterStyles;
51
+ }
52
+
47
53
  /** @protected */
48
54
  render() {
49
55
  return html`<slot></slot>`;
@@ -280,7 +280,7 @@ export const KeyboardNavigationMixin = (superClass) =>
280
280
  if (!targetRowInDom) {
281
281
  this._scrollToFlatIndex(index);
282
282
  } else {
283
- this.__scrollIntoViewport(index);
283
+ this.__scrollIntoViewport(targetRowInDom);
284
284
  }
285
285
  }
286
286
 
@@ -5,15 +5,7 @@
5
5
  */
6
6
  import { TabindexMixin } from '@vaadin/a11y-base/src/tabindex-mixin.js';
7
7
  import { animationFrame, microTask } from '@vaadin/component-base/src/async.js';
8
- import {
9
- isAndroid,
10
- isChrome,
11
- isFirefox,
12
- isIOS,
13
- isSafari,
14
- isTouch,
15
- supportsAdoptingStyleSheets,
16
- } from '@vaadin/component-base/src/browser-utils.js';
8
+ import { isAndroid, isChrome, isFirefox, isIOS, isSafari, isTouch } from '@vaadin/component-base/src/browser-utils.js';
17
9
  import { Debouncer } from '@vaadin/component-base/src/debounce.js';
18
10
  import { getClosestElement } from '@vaadin/component-base/src/dom-utils.js';
19
11
  import { SlotObserver } from '@vaadin/component-base/src/slot-observer.js';
@@ -36,6 +28,7 @@ import {
36
28
  iterateRowCells,
37
29
  updateBooleanRowStates,
38
30
  updateCellsPart,
31
+ updateState,
39
32
  } from './vaadin-grid-helpers.js';
40
33
  import { KeyboardNavigationMixin } from './vaadin-grid-keyboard-navigation-mixin.js';
41
34
  import { RowDetailsMixin } from './vaadin-grid-row-details-mixin.js';
@@ -353,7 +346,7 @@ export const GridMixin = (superClass) =>
353
346
  row.setAttribute('role', 'row');
354
347
  row.setAttribute('tabindex', '-1');
355
348
  if (this._columnTree) {
356
- this._updateRow(row, this._columnTree[this._columnTree.length - 1], 'body', false, true);
349
+ this.__initRow(row, this._columnTree[this._columnTree.length - 1], 'body', false, true);
357
350
  }
358
351
  rows.push(row);
359
352
  }
@@ -465,9 +458,9 @@ export const GridMixin = (superClass) =>
465
458
  * @param {?string} section
466
459
  * @param {boolean} isColumnRow
467
460
  * @param {boolean} noNotify
468
- * @protected
461
+ * @private
469
462
  */
470
- _updateRow(row, columns, section = 'body', isColumnRow = false, noNotify = false) {
463
+ __initRow(row, columns, section = 'body', isColumnRow = false, noNotify = false) {
471
464
  const contentsFragment = document.createDocumentFragment();
472
465
 
473
466
  iterateRowCells(row, (cell) => {
@@ -637,6 +630,7 @@ export const GridMixin = (superClass) =>
637
630
 
638
631
  // Make sure the section has a tabbable element
639
632
  this._resetKeyboardNavigation();
633
+ this._a11yUpdateGridSize(this.size, this._columnTree, this.__emptyState);
640
634
  }
641
635
 
642
636
  /** @private */
@@ -647,10 +641,15 @@ export const GridMixin = (superClass) =>
647
641
  return;
648
642
  }
649
643
 
644
+ row.index = index;
645
+
650
646
  this._updateRowOrderParts(row, index);
651
647
 
652
648
  this._a11yUpdateRowRowindex(row, index);
653
- this._getItem(index, row);
649
+
650
+ this.__ensureRowItem(row);
651
+ this.__ensureRowHierarchy(row);
652
+ this.__updateRow(row);
654
653
  }
655
654
 
656
655
  /** @private */
@@ -691,7 +690,7 @@ export const GridMixin = (superClass) =>
691
690
  */
692
691
  _renderColumnTree(columnTree) {
693
692
  iterateChildren(this.$.items, (row) => {
694
- this._updateRow(row, columnTree[columnTree.length - 1], 'body', false, true);
693
+ this.__initRow(row, columnTree[columnTree.length - 1], 'body', false, true);
695
694
 
696
695
  const model = this.__getRowModel(row);
697
696
  this._updateRowOrderParts(row);
@@ -718,7 +717,7 @@ export const GridMixin = (superClass) =>
718
717
  }
719
718
 
720
719
  iterateChildren(this.$.header, (headerRow, index, rows) => {
721
- this._updateRow(headerRow, columnTree[index], 'header', index === columnTree.length - 1);
720
+ this.__initRow(headerRow, columnTree[index], 'header', index === columnTree.length - 1);
722
721
 
723
722
  const cells = getBodyRowCells(headerRow);
724
723
  updateCellsPart(cells, 'first-header-row-cell', index === 0);
@@ -726,7 +725,7 @@ export const GridMixin = (superClass) =>
726
725
  });
727
726
 
728
727
  iterateChildren(this.$.footer, (footerRow, index, rows) => {
729
- this._updateRow(footerRow, columnTree[columnTree.length - 1 - index], 'footer', index === 0);
728
+ this.__initRow(footerRow, columnTree[columnTree.length - 1 - index], 'footer', index === 0);
730
729
 
731
730
  const cells = getBodyRowCells(footerRow);
732
731
  updateCellsPart(cells, 'first-footer-row-cell', index === 0);
@@ -734,7 +733,7 @@ export const GridMixin = (superClass) =>
734
733
  });
735
734
 
736
735
  // Sizer rows
737
- this._updateRow(this.$.sizer, columnTree[columnTree.length - 1]);
736
+ this.__initRow(this.$.sizer, columnTree[columnTree.length - 1]);
738
737
 
739
738
  this._resizeHandler();
740
739
  this._frozenCellsChanged();
@@ -749,10 +748,38 @@ export const GridMixin = (superClass) =>
749
748
 
750
749
  /**
751
750
  * @param {!HTMLElement} row
752
- * @param {GridItem} item
753
- * @protected
751
+ * @param {boolean} loading
752
+ * @private
754
753
  */
755
- _updateItem(row, item) {
754
+ __updateRowLoading(row, loading) {
755
+ const cells = getBodyRowCells(row);
756
+
757
+ // Row state attribute
758
+ updateState(row, 'loading', loading);
759
+
760
+ // Cells part attribute
761
+ updateCellsPart(cells, 'loading-row-cell', loading);
762
+
763
+ if (loading) {
764
+ // Run style generators for the loading row to have custom names cleared
765
+ this._generateCellClassNames(row);
766
+ this._generateCellPartNames(row);
767
+ }
768
+ }
769
+
770
+ /**
771
+ * @param {!HTMLElement} row
772
+ * @private
773
+ */
774
+ __updateRow(row) {
775
+ const item = this.__getRowItem(row);
776
+ if (item) {
777
+ this.__updateRowLoading(row, false);
778
+ } else {
779
+ this.__updateRowLoading(row, true);
780
+ return;
781
+ }
782
+
756
783
  row._item = item;
757
784
  const model = this.__getRowModel(row);
758
785
 
@@ -929,15 +956,11 @@ export const GridMixin = (superClass) =>
929
956
 
930
957
  // The style is set to host instead of the scroller so that the value can be overridden by the user with "grid { min-height: 0 }"
931
958
  // Prefer setting style in adopted style sheet to avoid the need to add a confusing inline style on the host element
932
- // If adopted style sheets are not supported, the style is set inline
933
- if (!this.__minHeightStyleSheet && supportsAdoptingStyleSheets) {
959
+ if (!this.__minHeightStyleSheet) {
934
960
  this.__minHeightStyleSheet = new CSSStyleSheet();
935
- this.shadowRoot.adoptedStyleSheets = [...this.shadowRoot.adoptedStyleSheets, this.__minHeightStyleSheet];
936
- }
937
- if (this.__minHeightStyleSheet) {
938
- this.__minHeightStyleSheet.replaceSync(`:host { --_grid-min-height: ${minHeight}px; }`);
939
- } else {
940
- this.style.setProperty('--_grid-min-height', `${minHeight}px`);
961
+ this.shadowRoot.adoptedStyleSheets.push(this.__minHeightStyleSheet);
941
962
  }
963
+
964
+ this.__minHeightStyleSheet.replaceSync(`:host { --_grid-min-height: ${minHeight}px; }`);
942
965
  }
943
966
  };
@@ -85,7 +85,7 @@ export const RowDetailsMixin = (superClass) =>
85
85
  // Only update the rows if the column tree has already been initialized
86
86
  iterateChildren(this.$.items, (row) => {
87
87
  if (!row.querySelector('[part~=details-cell]')) {
88
- this._updateRow(row, this._columnTree[this._columnTree.length - 1]);
88
+ this.__initRow(row, this._columnTree[this._columnTree.length - 1]);
89
89
  const isDetailsOpened = this._isDetailsOpened(row._item);
90
90
  this._toggleDetailsCell(row, isDetailsOpened);
91
91
  }
@@ -98,13 +98,13 @@ export const RowDetailsMixin = (superClass) =>
98
98
  iterateChildren(this.$.items, (row) => {
99
99
  // Re-renders the row to possibly close the previously opened details.
100
100
  if (row.hasAttribute('details-opened')) {
101
- this._updateItem(row, row._item);
101
+ this.__updateRow(row);
102
102
  return;
103
103
  }
104
104
 
105
105
  // Re-renders the row to open the details when a row details renderer is provided.
106
106
  if (rowDetailsRenderer && this._isDetailsOpened(row._item)) {
107
- this._updateItem(row, row._item);
107
+ this.__updateRow(row);
108
108
  }
109
109
  });
110
110
  }
@@ -140,7 +140,7 @@ export const RowDetailsMixin = (superClass) =>
140
140
  }
141
141
 
142
142
  // Assigns a renderer when the details cell is opened.
143
- // The details cell content is rendered later in the `_updateItem` method.
143
+ // The details cell content is rendered later in the `__updateRow` method.
144
144
  if (this.rowDetailsRenderer) {
145
145
  cell._renderer = this.rowDetailsRenderer;
146
146
  }
@@ -122,10 +122,23 @@ export const ScrollMixin = (superClass) =>
122
122
  const row = composedPath[composedPath.indexOf(this.$.items) - 1];
123
123
 
124
124
  if (row) {
125
- // Make sure the row with the focused element is fully inside the visible viewport
126
- // Don't change scroll position if the user is interacting with the mouse
125
+ // Don't change scroll position if the user is interacting with the mouse.
127
126
  if (!this._isMousedown) {
128
- this.__scrollIntoViewport(row.index);
127
+ // Make sure the focused element (row, cell, or focusable element inside a cell)
128
+ // is inside the viewport. If the whole row fits into the viewport, then scroll
129
+ // the row into view. This ensures that labels, helper texts and other related
130
+ // elements of focusable elements within cells also become visible. When the row
131
+ // is larger than the viewport, scroll the focus event target into the viewport.
132
+ // This works better when focusing elements within cells, which could otherwise
133
+ // still be outside the viewport when scrolling to the top or bottom of the row.
134
+ const tableHeight = this.$.table.clientHeight;
135
+ const headerHeight = this.$.header.clientHeight;
136
+ const footerHeight = this.$.footer.clientHeight;
137
+ const viewportHeight = tableHeight - headerHeight - footerHeight;
138
+ const isRowLargerThanViewport = row.clientHeight > viewportHeight;
139
+ const scrollTarget = isRowLargerThanViewport ? e.target : row;
140
+
141
+ this.__scrollIntoViewport(scrollTarget);
129
142
  }
130
143
 
131
144
  if (!this.$.table.contains(e.relatedTarget)) {
@@ -171,25 +184,27 @@ export const ScrollMixin = (superClass) =>
171
184
  _scrollToFlatIndex(index) {
172
185
  index = Math.min(this._flatSize - 1, Math.max(0, index));
173
186
  this.__virtualizer.scrollToIndex(index);
174
- this.__scrollIntoViewport(index);
187
+ const rowElement = [...this.$.items.children].find((child) => child.index === index);
188
+ this.__scrollIntoViewport(rowElement);
175
189
  }
176
190
 
177
191
  /**
178
- * Makes sure the row with the given index (if found in the DOM) is fully
179
- * inside the visible viewport, taking header/footer into account.
192
+ * Makes sure the given element is fully inside the visible viewport,
193
+ * taking header/footer into account.
180
194
  * @private
181
195
  */
182
- __scrollIntoViewport(index) {
183
- const rowElement = [...this.$.items.children].find((child) => child.index === index);
184
- if (rowElement) {
185
- const dstRect = rowElement.getBoundingClientRect();
186
- const footerTop = this.$.footer.getBoundingClientRect().top;
187
- const headerBottom = this.$.header.getBoundingClientRect().bottom;
188
- if (dstRect.bottom > footerTop) {
189
- this.$.table.scrollTop += dstRect.bottom - footerTop;
190
- } else if (dstRect.top < headerBottom) {
191
- this.$.table.scrollTop -= headerBottom - dstRect.top;
192
- }
196
+ __scrollIntoViewport(element) {
197
+ if (!element) {
198
+ return;
199
+ }
200
+
201
+ const dstRect = element.getBoundingClientRect();
202
+ const footerTop = this.$.footer.getBoundingClientRect().top;
203
+ const headerBottom = this.$.header.getBoundingClientRect().bottom;
204
+ if (dstRect.bottom > footerTop) {
205
+ this.$.table.scrollTop += dstRect.bottom - footerTop;
206
+ } else if (dstRect.top < headerBottom) {
207
+ this.$.table.scrollTop -= headerBottom - dstRect.top;
193
208
  }
194
209
  }
195
210
 
@@ -476,6 +491,7 @@ export const ScrollMixin = (superClass) =>
476
491
 
477
492
  // Position frozen cells
478
493
  const x = this.__isRTL ? normalizedScrollLeft + clientWidth - scrollWidth : scrollLeft;
494
+ this.__horizontalScrollPosition = x;
479
495
  const transformFrozen = `translate(${x}px, 0)`;
480
496
  this._frozenCells.forEach((cell) => {
481
497
  cell.style.transform = transformFrozen;
@@ -511,10 +527,25 @@ export const ScrollMixin = (superClass) =>
511
527
  }
512
528
  });
513
529
 
514
- // Only update the --_grid-horizontal-scroll-position custom property when navigating
515
- // on row focus mode to avoid performance issues.
516
- if (this.hasAttribute('navigating') && this.__rowFocusMode) {
517
- this.$.table.style.setProperty('--_grid-horizontal-scroll-position', `${-x}px`);
530
+ const focusedRow = this.shadowRoot.querySelector("[part~='row']:focus");
531
+ if (focusedRow) {
532
+ // Update the horizontal scroll position property of the focused row
533
+ this.__updateRowScrollPositionProperty(focusedRow);
534
+ }
535
+ }
536
+
537
+ /**
538
+ * Synchronizes the internal `--_grid-horizontal-scroll-position` CSS property
539
+ * of the given row with the current horizontal scroll position of the grid.
540
+ * @private
541
+ */
542
+ __updateRowScrollPositionProperty(row) {
543
+ if (row instanceof HTMLTableRowElement === false) {
544
+ return;
545
+ }
546
+ const newValue = `${this.__horizontalScrollPosition}px`;
547
+ if (row.style.getPropertyValue('--_grid-horizontal-scroll-position') !== newValue) {
548
+ row.style.setProperty('--_grid-horizontal-scroll-position', newValue);
518
549
  }
519
550
  }
520
551