@vaadin/grid 24.3.0-alpha1 → 24.3.0-alpha10

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 (61) hide show
  1. package/package.json +18 -13
  2. package/src/vaadin-grid-a11y-mixin.js +1 -1
  3. package/src/vaadin-grid-active-item-mixin.js +1 -0
  4. package/src/vaadin-grid-array-data-provider-mixin.js +14 -17
  5. package/src/vaadin-grid-column-group-mixin.d.ts +20 -0
  6. package/src/vaadin-grid-column-group-mixin.js +364 -0
  7. package/src/vaadin-grid-column-group.d.ts +4 -14
  8. package/src/vaadin-grid-column-group.js +5 -355
  9. package/src/vaadin-grid-column-mixin.d.ts +170 -0
  10. package/src/vaadin-grid-column-mixin.js +958 -0
  11. package/src/vaadin-grid-column.d.ts +11 -138
  12. package/src/vaadin-grid-column.js +5 -876
  13. package/src/vaadin-grid-data-provider-mixin.d.ts +6 -5
  14. package/src/vaadin-grid-data-provider-mixin.js +58 -11
  15. package/src/vaadin-grid-drag-and-drop-mixin.js +17 -5
  16. package/src/vaadin-grid-dynamic-columns-mixin.js +22 -17
  17. package/src/vaadin-grid-filter-column-mixin.d.ts +22 -0
  18. package/src/vaadin-grid-filter-column-mixin.js +106 -0
  19. package/src/vaadin-grid-filter-column.d.ts +9 -11
  20. package/src/vaadin-grid-filter-column.js +3 -90
  21. package/src/vaadin-grid-filter-element-mixin.d.ts +34 -0
  22. package/src/vaadin-grid-filter-element-mixin.js +108 -0
  23. package/src/vaadin-grid-filter-mixin.js +4 -4
  24. package/src/vaadin-grid-filter.d.ts +4 -21
  25. package/src/vaadin-grid-filter.js +5 -84
  26. package/src/vaadin-grid-helpers.js +94 -0
  27. package/src/vaadin-grid-keyboard-navigation-mixin.js +26 -6
  28. package/src/vaadin-grid-mixin.js +37 -46
  29. package/src/vaadin-grid-row-details-mixin.js +14 -8
  30. package/src/vaadin-grid-scroll-mixin.js +9 -3
  31. package/src/vaadin-grid-selection-column-base-mixin.js +12 -4
  32. package/src/vaadin-grid-selection-column-mixin.d.ts +24 -0
  33. package/src/vaadin-grid-selection-column-mixin.js +194 -0
  34. package/src/vaadin-grid-selection-column.d.ts +13 -17
  35. package/src/vaadin-grid-selection-column.js +4 -186
  36. package/src/vaadin-grid-selection-mixin.js +4 -3
  37. package/src/vaadin-grid-sort-column-mixin.d.ts +36 -0
  38. package/src/vaadin-grid-sort-column-mixin.js +101 -0
  39. package/src/vaadin-grid-sort-column.d.ts +8 -26
  40. package/src/vaadin-grid-sort-column.js +3 -87
  41. package/src/vaadin-grid-sorter-mixin.d.ts +44 -0
  42. package/src/vaadin-grid-sorter-mixin.js +200 -0
  43. package/src/vaadin-grid-sorter.d.ts +3 -32
  44. package/src/vaadin-grid-sorter.js +5 -181
  45. package/src/vaadin-grid-styles.js +341 -345
  46. package/src/vaadin-grid-styling-mixin.js +8 -2
  47. package/src/vaadin-grid-tree-column-mixin.d.ts +18 -0
  48. package/src/vaadin-grid-tree-column-mixin.js +99 -0
  49. package/src/vaadin-grid-tree-column.d.ts +9 -7
  50. package/src/vaadin-grid-tree-column.js +3 -82
  51. package/src/vaadin-grid-tree-toggle-mixin.d.ts +39 -0
  52. package/src/vaadin-grid-tree-toggle-mixin.js +153 -0
  53. package/src/vaadin-grid-tree-toggle.d.ts +4 -27
  54. package/src/vaadin-grid-tree-toggle.js +9 -141
  55. package/src/vaadin-grid.d.ts +3 -0
  56. package/src/vaadin-grid.js +7 -2
  57. package/theme/lumo/vaadin-grid-sorter-styles.js +1 -1
  58. package/theme/lumo/vaadin-grid-styles.js +15 -14
  59. package/theme/material/vaadin-grid-styles.js +15 -10
  60. package/web-types.json +331 -126
  61. package/web-types.lit.json +114 -58
@@ -0,0 +1,108 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2016 - 2023 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { timeOut } from '@vaadin/component-base/src/async.js';
7
+ import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
8
+ import { Debouncer } from '@vaadin/component-base/src/debounce.js';
9
+ import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
10
+ import { css, registerStyles } from '@vaadin/vaadin-themable-mixin';
11
+
12
+ registerStyles(
13
+ 'vaadin-grid-filter',
14
+ css`
15
+ :host {
16
+ display: inline-flex;
17
+ max-width: 100%;
18
+ }
19
+
20
+ ::slotted(*) {
21
+ width: 100%;
22
+ box-sizing: border-box;
23
+ }
24
+ `,
25
+ { moduleId: 'vaadin-grid-filter-styles' },
26
+ );
27
+
28
+ /**
29
+ * @polymerMixin
30
+ *
31
+ * @mixes ControllerMixin
32
+ */
33
+ export const GridFilterElementMixin = (superClass) =>
34
+ class extends ControllerMixin(superClass) {
35
+ static get properties() {
36
+ return {
37
+ /**
38
+ * JS Path of the property in the item used for filtering the data.
39
+ */
40
+ path: {
41
+ type: String,
42
+ sync: true,
43
+ },
44
+
45
+ /**
46
+ * Current filter value.
47
+ */
48
+ value: {
49
+ type: String,
50
+ notify: true,
51
+ sync: true,
52
+ },
53
+
54
+ /** @private */
55
+ _textField: {
56
+ type: Object,
57
+ sync: true,
58
+ },
59
+ };
60
+ }
61
+
62
+ static get observers() {
63
+ return ['_filterChanged(path, value, _textField)'];
64
+ }
65
+
66
+ /** @protected */
67
+ ready() {
68
+ super.ready();
69
+
70
+ this._filterController = new SlotController(this, '', 'vaadin-text-field', {
71
+ initializer: (field) => {
72
+ field.addEventListener('value-changed', (e) => {
73
+ if (field.__previousValue === undefined && e.detail.value === '') {
74
+ field.__previousValue = e.detail.value;
75
+ return;
76
+ }
77
+ this.value = e.detail.value;
78
+ });
79
+
80
+ this._textField = field;
81
+ },
82
+ });
83
+ this.addController(this._filterController);
84
+ }
85
+
86
+ /** @private */
87
+ _filterChanged(path, value, textField) {
88
+ if (path === undefined || value === undefined || !textField) {
89
+ return;
90
+ }
91
+ if (this._previousValue === undefined && value === '') {
92
+ return;
93
+ }
94
+
95
+ textField.value = value;
96
+ this._previousValue = value;
97
+
98
+ this._debouncerFilterChanged = Debouncer.debounce(this._debouncerFilterChanged, timeOut.after(200), () => {
99
+ this.dispatchEvent(new CustomEvent('filter-changed', { bubbles: true }));
100
+ });
101
+ }
102
+
103
+ focus() {
104
+ if (this._textField) {
105
+ this._textField.focus();
106
+ }
107
+ }
108
+ };
@@ -19,10 +19,10 @@ export const FilterMixin = (superClass) =>
19
19
  };
20
20
  }
21
21
 
22
- /** @protected */
23
- ready() {
24
- super.ready();
25
- this.addEventListener('filter-changed', this._filterChanged.bind(this));
22
+ constructor() {
23
+ super();
24
+ this._filterChanged = this._filterChanged.bind(this);
25
+ this.addEventListener('filter-changed', this._filterChanged);
26
26
  }
27
27
 
28
28
  /** @private */
@@ -3,18 +3,11 @@
3
3
  * Copyright (c) 2016 - 2023 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
- import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
7
6
 
8
- /**
9
- * Fired when the `value` property changes.
10
- */
11
- export type GridFilterValueChangedEvent = CustomEvent<{ value: string }>;
12
-
13
- export interface GridFilterCustomEventMap {
14
- 'value-changed': GridFilterValueChangedEvent;
15
- }
7
+ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin';
8
+ import { GridFilterElementMixin, type GridFilterEventMap } from './vaadin-grid-filter-element-mixin.js';
16
9
 
17
- export interface GridFilterEventMap extends HTMLElementEventMap, GridFilterCustomEventMap {}
10
+ export * from './vaadin-grid-filter-element-mixin.js';
18
11
 
19
12
  /**
20
13
  * `<vaadin-grid-filter>` is a helper element for the `<vaadin-grid>` that provides out-of-the-box UI controls,
@@ -41,17 +34,7 @@ export interface GridFilterEventMap extends HTMLElementEventMap, GridFilterCusto
41
34
  *
42
35
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
43
36
  */
44
- declare class GridFilter extends ControllerMixin(HTMLElement) {
45
- /**
46
- * JS Path of the property in the item used for filtering the data.
47
- */
48
- path: string | null | undefined;
49
-
50
- /**
51
- * Current filter value.
52
- */
53
- value: string | null | undefined;
54
-
37
+ declare class GridFilter extends GridFilterElementMixin(ThemableMixin(HTMLElement)) {
55
38
  addEventListener<K extends keyof GridFilterEventMap>(
56
39
  type: K,
57
40
  listener: (this: GridFilter, ev: GridFilterEventMap[K]) => void,
@@ -5,11 +5,9 @@
5
5
  */
6
6
  import '@vaadin/text-field/src/vaadin-text-field.js';
7
7
  import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
8
- import { timeOut } from '@vaadin/component-base/src/async.js';
9
- import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
10
- import { Debouncer } from '@vaadin/component-base/src/debounce.js';
11
8
  import { defineCustomElement } from '@vaadin/component-base/src/define.js';
12
- import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
9
+ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin';
10
+ import { GridFilterElementMixin } from './vaadin-grid-filter-element-mixin.js';
13
11
 
14
12
  /**
15
13
  * `<vaadin-grid-filter>` is a helper element for the `<vaadin-grid>` that provides out-of-the-box UI controls,
@@ -38,93 +36,16 @@ import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
38
36
  *
39
37
  * @customElement
40
38
  * @extends HTMLElement
39
+ * @mixes GridFilterElementMixin
41
40
  */
42
- class GridFilter extends ControllerMixin(PolymerElement) {
41
+ class GridFilter extends GridFilterElementMixin(ThemableMixin(PolymerElement)) {
43
42
  static get template() {
44
- return html`
45
- <style>
46
- :host {
47
- display: inline-flex;
48
- max-width: 100%;
49
- }
50
-
51
- ::slotted(*) {
52
- width: 100%;
53
- box-sizing: border-box;
54
- }
55
- </style>
56
- <slot></slot>
57
- `;
43
+ return html`<slot></slot>`;
58
44
  }
59
45
 
60
46
  static get is() {
61
47
  return 'vaadin-grid-filter';
62
48
  }
63
-
64
- static get properties() {
65
- return {
66
- /**
67
- * JS Path of the property in the item used for filtering the data.
68
- */
69
- path: String,
70
-
71
- /**
72
- * Current filter value.
73
- */
74
- value: {
75
- type: String,
76
- notify: true,
77
- },
78
-
79
- /** @private */
80
- _textField: {
81
- type: Object,
82
- },
83
- };
84
- }
85
-
86
- static get observers() {
87
- return ['_filterChanged(path, value, _textField)'];
88
- }
89
-
90
- /** @protected */
91
- ready() {
92
- super.ready();
93
-
94
- this._filterController = new SlotController(this, '', 'vaadin-text-field', {
95
- initializer: (field) => {
96
- field.addEventListener('value-changed', (e) => {
97
- this.value = e.detail.value;
98
- });
99
-
100
- this._textField = field;
101
- },
102
- });
103
- this.addController(this._filterController);
104
- }
105
-
106
- /** @private */
107
- _filterChanged(path, value, textField) {
108
- if (path === undefined || value === undefined || !textField) {
109
- return;
110
- }
111
- if (this._previousValue === undefined && value === '') {
112
- return;
113
- }
114
-
115
- textField.value = value;
116
- this._previousValue = value;
117
-
118
- this._debouncerFilterChanged = Debouncer.debounce(this._debouncerFilterChanged, timeOut.after(200), () => {
119
- this.dispatchEvent(new CustomEvent('filter-changed', { bubbles: true }));
120
- });
121
- }
122
-
123
- focus() {
124
- if (this._textField) {
125
- this._textField.focus();
126
- }
127
- }
128
49
  }
129
50
 
130
51
  defineCustomElement(GridFilter);
@@ -3,6 +3,8 @@
3
3
  * Copyright (c) 2016 - 2023 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
+ import { microTask } from '@vaadin/component-base/src/async.js';
7
+ import { Debouncer } from '@vaadin/component-base/src/debounce.js';
6
8
  import { addValueToAttribute, removeValueFromAttribute } from '@vaadin/component-base/src/dom-utils.js';
7
9
 
8
10
  /**
@@ -170,3 +172,95 @@ export function updateCellState(cell, attribute, value, part, oldPart) {
170
172
  // Add new part to the cell attribute
171
173
  updatePart(cell, value, part || `${attribute}-cell`);
172
174
  }
175
+
176
+ /**
177
+ * A helper for observing flattened child column list of an element.
178
+ */
179
+ export class ColumnObserver {
180
+ constructor(host, callback) {
181
+ this.__host = host;
182
+ this.__callback = callback;
183
+ this.__currentSlots = [];
184
+
185
+ this.__onMutation = this.__onMutation.bind(this);
186
+ this.__observer = new MutationObserver(this.__onMutation);
187
+ this.__observer.observe(host, {
188
+ childList: true,
189
+ });
190
+
191
+ // The observer callback is invoked once initially.
192
+ this.__initialCallDebouncer = Debouncer.debounce(this.__initialCallDebouncer, microTask, () => this.__onMutation());
193
+ }
194
+
195
+ disconnect() {
196
+ this.__observer.disconnect();
197
+ this.__initialCallDebouncer.cancel();
198
+ this.__toggleSlotChangeListeners(false);
199
+ }
200
+
201
+ flush() {
202
+ this.__onMutation();
203
+ }
204
+
205
+ __toggleSlotChangeListeners(add) {
206
+ this.__currentSlots.forEach((slot) => {
207
+ if (add) {
208
+ slot.addEventListener('slotchange', this.__onMutation);
209
+ } else {
210
+ slot.removeEventListener('slotchange', this.__onMutation);
211
+ }
212
+ });
213
+ }
214
+
215
+ __onMutation() {
216
+ // Detect if this is the initial call
217
+ const initialCall = !this.__currentColumns;
218
+ this.__currentColumns ||= [];
219
+
220
+ // Detect added and removed columns or if the columns order has changed
221
+ const columns = ColumnObserver.getColumns(this.__host);
222
+ const addedColumns = columns.filter((column) => !this.__currentColumns.includes(column));
223
+ const removedColumns = this.__currentColumns.filter((column) => !columns.includes(column));
224
+ const orderChanged = this.__currentColumns.some((column, index) => column !== columns[index]);
225
+ this.__currentColumns = columns;
226
+
227
+ // Update the list of child slots and toggle their slotchange listeners
228
+ this.__toggleSlotChangeListeners(false);
229
+ this.__currentSlots = [...this.__host.children].filter((child) => child instanceof HTMLSlotElement);
230
+ this.__toggleSlotChangeListeners(true);
231
+
232
+ // Invoke the callback if there are changes in the child columns or if this is the initial call
233
+ const invokeCallback = initialCall || addedColumns.length || removedColumns.length || orderChanged;
234
+ if (invokeCallback) {
235
+ this.__callback(addedColumns, removedColumns);
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Default filter for column elements.
241
+ */
242
+ static __isColumnElement(node) {
243
+ return node.nodeType === Node.ELEMENT_NODE && /\bcolumn\b/u.test(node.localName);
244
+ }
245
+
246
+ static getColumns(host) {
247
+ const columns = [];
248
+
249
+ // A temporary workaround for backwards compatibility
250
+ const isColumnElement = host._isColumnElement || ColumnObserver.__isColumnElement;
251
+
252
+ [...host.children].forEach((child) => {
253
+ if (isColumnElement(child)) {
254
+ // The child is a column element, add it to the list
255
+ columns.push(child);
256
+ } else if (child instanceof HTMLSlotElement) {
257
+ // The child is a slot, add all assigned column elements to the list
258
+ [...child.assignedElements({ flatten: true })]
259
+ .filter((assignedElement) => isColumnElement(assignedElement))
260
+ .forEach((assignedElement) => columns.push(assignedElement));
261
+ }
262
+ });
263
+
264
+ return columns;
265
+ }
266
+ }
@@ -18,6 +18,7 @@ export const KeyboardNavigationMixin = (superClass) =>
18
18
  _headerFocusable: {
19
19
  type: Object,
20
20
  observer: '_focusableChanged',
21
+ sync: true,
21
22
  },
22
23
 
23
24
  /**
@@ -27,12 +28,14 @@ export const KeyboardNavigationMixin = (superClass) =>
27
28
  _itemsFocusable: {
28
29
  type: Object,
29
30
  observer: '_focusableChanged',
31
+ sync: true,
30
32
  },
31
33
 
32
34
  /** @private */
33
35
  _footerFocusable: {
34
36
  type: Object,
35
37
  observer: '_focusableChanged',
38
+ sync: true,
36
39
  },
37
40
 
38
41
  /** @private */
@@ -54,6 +57,7 @@ export const KeyboardNavigationMixin = (superClass) =>
54
57
  _focusedCell: {
55
58
  type: Object,
56
59
  observer: '_focusedCellChanged',
60
+ sync: true,
57
61
  },
58
62
 
59
63
  /**
@@ -267,7 +271,7 @@ export const KeyboardNavigationMixin = (superClass) =>
267
271
  __isRowExpandable(row) {
268
272
  if (this.itemHasChildrenPath) {
269
273
  const item = row._item;
270
- return item && get(this.itemHasChildrenPath, item) && !this._isExpanded(item);
274
+ return !!(item && get(this.itemHasChildrenPath, item) && !this._isExpanded(item));
271
275
  }
272
276
  }
273
277
 
@@ -443,7 +447,7 @@ export const KeyboardNavigationMixin = (superClass) =>
443
447
  __navigateRows(dy, activeRow, activeCell) {
444
448
  const currentRowIndex = this.__getIndexInGroup(activeRow, this._focusedItemIndex);
445
449
  const activeRowGroup = activeRow.parentNode;
446
- const maxRowIndex = (activeRowGroup === this.$.items ? this._effectiveSize : activeRowGroup.children.length) - 1;
450
+ const maxRowIndex = (activeRowGroup === this.$.items ? this._flatSize : activeRowGroup.children.length) - 1;
447
451
 
448
452
  // Index of the destination row
449
453
  let dstRowIndex = Math.max(0, Math.min(currentRowIndex + dy, maxRowIndex));
@@ -687,7 +691,7 @@ export const KeyboardNavigationMixin = (superClass) =>
687
691
  // If the target focusable is tied to a column that is not visible,
688
692
  // find the first visible column and update the target in order to
689
693
  // prevent scrolling to the start of the row.
690
- if (focusStepTarget && focusStepTarget._column && !this.__isColumnInViewport(focusStepTarget._column)) {
694
+ if (focusStepTarget && !this.__isHorizontallyInViewport(focusStepTarget)) {
691
695
  const firstVisibleColumn = this._getColumnsInOrder().find((column) => this.__isColumnInViewport(column));
692
696
  if (firstVisibleColumn) {
693
697
  if (focusStepTarget === this._headerFocusable) {
@@ -842,9 +846,12 @@ export const KeyboardNavigationMixin = (superClass) =>
842
846
  }
843
847
 
844
848
  if (cell) {
845
- // Fire a public event for cell.
846
849
  const context = this.getEventContext(e);
847
- cell.dispatchEvent(new CustomEvent('cell-focus', { bubbles: true, composed: true, detail: { context } }));
850
+ this.__pendingBodyCellFocus = this.loading && context.section === 'body';
851
+ if (!this.__pendingBodyCellFocus) {
852
+ // Fire a cell-focus event for the cell
853
+ cell.dispatchEvent(new CustomEvent('cell-focus', { bubbles: true, composed: true, detail: { context } }));
854
+ }
848
855
  this._focusedCell = cell._focusButton || cell;
849
856
 
850
857
  if (isKeyboardActive() && e.target === cell) {
@@ -858,6 +865,16 @@ export const KeyboardNavigationMixin = (superClass) =>
858
865
  this._detectFocusedItemIndex(e);
859
866
  }
860
867
 
868
+ /**
869
+ * @private
870
+ */
871
+ __dispatchPendingBodyCellFocus() {
872
+ // If the body cell focus was pending, dispatch the event once loading is done
873
+ if (this.__pendingBodyCellFocus && this.shadowRoot.activeElement === this._itemsFocusable) {
874
+ this._itemsFocusable.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
875
+ }
876
+ }
877
+
861
878
  /**
862
879
  * Get the focusable element depending on the current focus mode.
863
880
  * It can be a row, a cell, or a focusable div inside a cell.
@@ -954,6 +971,9 @@ export const KeyboardNavigationMixin = (superClass) =>
954
971
 
955
972
  /** @protected */
956
973
  _resetKeyboardNavigation() {
974
+ if (!this.$ && this.performUpdate) {
975
+ this.performUpdate();
976
+ }
957
977
  // Header / footer
958
978
  ['header', 'footer'].forEach((section) => {
959
979
  if (!this.__isValidFocusable(this[`_${section}Focusable`])) {
@@ -972,7 +992,7 @@ export const KeyboardNavigationMixin = (superClass) =>
972
992
 
973
993
  if (firstVisibleCell && firstVisibleRow) {
974
994
  // Reset memoized column
975
- delete this._focusedColumnOrder;
995
+ this._focusedColumnOrder = undefined;
976
996
  this._itemsFocusable = this.__getFocusable(firstVisibleRow, firstVisibleCell);
977
997
  }
978
998
  } else {
@@ -86,10 +86,7 @@ export const GridMixin = (superClass) =>
86
86
  ),
87
87
  ) {
88
88
  static get observers() {
89
- return [
90
- '_columnTreeChanged(_columnTree, _columnTree.*)',
91
- '_effectiveSizeChanged(_effectiveSize, __virtualizer, _hasData, _columnTree)',
92
- ];
89
+ return ['_columnTreeChanged(_columnTree)', '_flatSizeChanged(_flatSize, __virtualizer, _hasData, _columnTree)'];
93
90
  }
94
91
 
95
92
  static get properties() {
@@ -254,7 +251,6 @@ export const GridMixin = (superClass) =>
254
251
 
255
252
  new ResizeObserver(() =>
256
253
  setTimeout(() => {
257
- this.__updateFooterPositioning();
258
254
  this.__updateColumnsBodyContentHidden();
259
255
  this.__tryToRecalculateColumnWidthsIfPending();
260
256
  }),
@@ -294,20 +290,20 @@ export const GridMixin = (superClass) =>
294
290
  }
295
291
 
296
292
  /** @private */
297
- _effectiveSizeChanged(effectiveSize, virtualizer, hasData, columnTree) {
293
+ _flatSizeChanged(flatSize, virtualizer, hasData, columnTree) {
298
294
  if (virtualizer && hasData && columnTree) {
299
295
  // Changing the virtualizer size may result in the row with focus getting hidden
300
296
  const cell = this.shadowRoot.activeElement;
301
297
  const cellCoordinates = this.__getBodyCellCoordinates(cell);
302
298
 
303
299
  const previousSize = virtualizer.size || 0;
304
- virtualizer.size = effectiveSize;
300
+ virtualizer.size = flatSize;
305
301
 
306
302
  // Request an update for the previous last row to have the "last" state removed
307
303
  virtualizer.update(previousSize - 1, previousSize - 1);
308
- if (effectiveSize < previousSize) {
304
+ if (flatSize < previousSize) {
309
305
  // Size was decreased, so the new last row requires an explicit update
310
- virtualizer.update(effectiveSize - 1, effectiveSize - 1);
306
+ virtualizer.update(flatSize - 1, flatSize - 1);
311
307
  }
312
308
 
313
309
  // If the focused cell's parent row got hidden by the size change, focus the corresponding new cell
@@ -339,7 +335,10 @@ export const GridMixin = (superClass) =>
339
335
  return 0;
340
336
  }
341
337
 
342
- const columnWidth = Math.max(this.__getIntrinsicWidth(col), this.__getDistributedWidth(col.parentElement, col));
338
+ const columnWidth = Math.max(
339
+ this.__getIntrinsicWidth(col),
340
+ this.__getDistributedWidth((col.assignedSlot || col).parentElement, col),
341
+ );
343
342
 
344
343
  // We're processing a regular grid-column and not a grid-column-group
345
344
  if (!innerColumn) {
@@ -498,7 +497,7 @@ export const GridMixin = (superClass) =>
498
497
  const rows = [];
499
498
  for (let i = 0; i < count; i++) {
500
499
  const row = document.createElement('tr');
501
- row.setAttribute('part', 'row');
500
+ row.setAttribute('part', 'row body-row');
502
501
  row.setAttribute('role', 'row');
503
502
  row.setAttribute('tabindex', '-1');
504
503
  if (this._columnTree) {
@@ -508,9 +507,11 @@ export const GridMixin = (superClass) =>
508
507
  }
509
508
 
510
509
  if (this._columnTree) {
511
- this._columnTree[this._columnTree.length - 1].forEach(
512
- (c) => c.isConnected && c.notifyPath && c.notifyPath('_cells.*', c._cells),
513
- );
510
+ this._columnTree[this._columnTree.length - 1].forEach((c) => {
511
+ if (c.isConnected && c._cells) {
512
+ c._cells = [...c._cells];
513
+ }
514
+ });
514
515
  }
515
516
 
516
517
  this.__afterCreateScrollerRowsDebouncer = Debouncer.debounce(
@@ -675,8 +676,8 @@ export const GridMixin = (superClass) =>
675
676
  detailsCell._vacant = false;
676
677
  }
677
678
 
678
- if (column.notifyPath && !noNotify) {
679
- column.notifyPath('_cells.*', column._cells);
679
+ if (!noNotify) {
680
+ column._cells = [...column._cells];
680
681
  }
681
682
  } else {
682
683
  // Header & footer
@@ -697,7 +698,7 @@ export const GridMixin = (superClass) =>
697
698
  column._emptyCells.push(cell);
698
699
  }
699
700
  }
700
- cell.setAttribute('part', `cell ${section}-cell`);
701
+ cell.part.add('cell', `${section}-cell`);
701
702
  }
702
703
 
703
704
  if (!cell._content.parentElement) {
@@ -801,7 +802,7 @@ export const GridMixin = (superClass) =>
801
802
  _updateRowOrderParts(row, index = row.index) {
802
803
  updateBooleanRowStates(row, {
803
804
  first: index === 0,
804
- last: index === this._effectiveSize - 1,
805
+ last: index === this._flatSize - 1,
805
806
  odd: index % 2 !== 0,
806
807
  even: index % 2 === 0,
807
808
  });
@@ -811,6 +812,7 @@ export const GridMixin = (superClass) =>
811
812
  _updateRowStateParts(row, { expanded, selected, detailsOpened }) {
812
813
  updateBooleanRowStates(row, {
813
814
  expanded,
815
+ collapsed: this.__isRowExpandable(row),
814
816
  selected,
815
817
  'details-opened': detailsOpened,
816
818
  });
@@ -873,22 +875,9 @@ export const GridMixin = (superClass) =>
873
875
  this._resetKeyboardNavigation();
874
876
  this._a11yUpdateHeaderRows();
875
877
  this._a11yUpdateFooterRows();
876
- this.__updateFooterPositioning();
877
878
  this.generateCellClassNames();
878
879
  this.generateCellPartNames();
879
- }
880
-
881
- /** @private */
882
- __updateFooterPositioning() {
883
- // TODO: fixed in Firefox 99, remove when we can drop Firefox ESR 91 support
884
- if (this._firefox && parseFloat(navigator.userAgent.match(/Firefox\/(\d{2,3}.\d)/u)[1]) < 99) {
885
- // Sticky (or translated) footer in a flexbox host doesn't get included in
886
- // the scroll height calculation on FF. This is a workaround for the issue.
887
- this.$.items.style.paddingBottom = 0;
888
- if (!this.allRowsVisible) {
889
- this.$.items.style.paddingBottom = `${this.$.footer.offsetHeight}px`;
890
- }
891
- }
880
+ this.__updateHeaderAndFooter();
892
881
  }
893
882
 
894
883
  /**
@@ -926,7 +915,6 @@ export const GridMixin = (superClass) =>
926
915
  /** @private */
927
916
  _resizeHandler() {
928
917
  this._updateDetailsCellHeights();
929
- this.__updateFooterPositioning();
930
918
  this.__updateHorizontalScrollPosition();
931
919
  }
932
920
 
@@ -980,7 +968,7 @@ export const GridMixin = (superClass) =>
980
968
 
981
969
  /** @protected */
982
970
  _hideTooltip(immediate) {
983
- const tooltip = this._tooltipController.node;
971
+ const tooltip = this._tooltipController && this._tooltipController.node;
984
972
  if (tooltip) {
985
973
  tooltip._stateController.close(immediate);
986
974
  }
@@ -998,19 +986,22 @@ export const GridMixin = (superClass) =>
998
986
  * It is not guaranteed that the update happens immediately (synchronously) after it is requested.
999
987
  */
1000
988
  requestContentUpdate() {
1001
- if (this._columnTree) {
1002
- // Header and footer renderers
1003
- this._columnTree.forEach((level) => {
1004
- level.forEach((column) => {
1005
- if (column._renderHeaderAndFooter) {
1006
- column._renderHeaderAndFooter();
1007
- }
1008
- });
1009
- });
989
+ // Header and footer renderers
990
+ this.__updateHeaderAndFooter();
1010
991
 
1011
- // Body and row details renderers
1012
- this.__updateVisibleRows();
1013
- }
992
+ // Body and row details renderers
993
+ this.__updateVisibleRows();
994
+ }
995
+
996
+ /** @private */
997
+ __updateHeaderAndFooter() {
998
+ (this._columnTree || []).forEach((level) => {
999
+ level.forEach((column) => {
1000
+ if (column._renderHeaderAndFooter) {
1001
+ column._renderHeaderAndFooter();
1002
+ }
1003
+ });
1004
+ });
1014
1005
  }
1015
1006
 
1016
1007
  /** @protected */