@vaadin/grid 24.3.0-alpha4 → 24.3.0-alpha6

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 (32) hide show
  1. package/package.json +24 -12
  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 +13 -16
  5. package/src/vaadin-grid-column-group-mixin.js +16 -21
  6. package/src/vaadin-grid-column-mixin.js +54 -18
  7. package/src/vaadin-grid-column.js +2 -0
  8. package/src/vaadin-grid-data-provider-mixin.js +20 -4
  9. package/src/vaadin-grid-drag-and-drop-mixin.js +16 -4
  10. package/src/vaadin-grid-dynamic-columns-mixin.js +22 -17
  11. package/src/vaadin-grid-filter-column-mixin.js +8 -2
  12. package/src/vaadin-grid-filter-element-mixin.js +10 -1
  13. package/src/vaadin-grid-filter-mixin.js +4 -4
  14. package/src/vaadin-grid-helpers.js +94 -0
  15. package/src/vaadin-grid-keyboard-navigation-mixin.js +8 -1
  16. package/src/vaadin-grid-mixin.js +25 -38
  17. package/src/vaadin-grid-row-details-mixin.js +7 -8
  18. package/src/vaadin-grid-scroll-mixin.js +1 -0
  19. package/src/vaadin-grid-selection-column-base-mixin.js +12 -4
  20. package/src/vaadin-grid-selection-mixin.js +4 -3
  21. package/src/vaadin-grid-sort-column-mixin.js +5 -1
  22. package/src/vaadin-grid-sorter-mixin.js +2 -0
  23. package/src/vaadin-grid-styles.js +341 -345
  24. package/src/vaadin-grid-styling-mixin.js +8 -2
  25. package/src/vaadin-grid-tree-column-mixin.js +8 -1
  26. package/src/vaadin-grid-tree-toggle-mixin.js +2 -0
  27. package/src/vaadin-grid.js +4 -2
  28. package/theme/lumo/vaadin-grid-sorter-styles.js +1 -1
  29. package/theme/lumo/vaadin-grid-styles.js +14 -13
  30. package/theme/material/vaadin-grid-styles.js +5 -3
  31. package/web-types.json +4 -4
  32. package/web-types.lit.json +4 -4
@@ -14,12 +14,18 @@ export const GridFilterColumnMixin = (superClass) =>
14
14
  /**
15
15
  * JS Path of the property in the item used for filtering the data.
16
16
  */
17
- path: String,
17
+ path: {
18
+ type: String,
19
+ sync: true,
20
+ },
18
21
 
19
22
  /**
20
23
  * Text to display as the label of the column filter text-field.
21
24
  */
22
- header: String,
25
+ header: {
26
+ type: String,
27
+ sync: true,
28
+ },
23
29
  };
24
30
  }
25
31
 
@@ -37,7 +37,10 @@ export const GridFilterElementMixin = (superClass) =>
37
37
  /**
38
38
  * JS Path of the property in the item used for filtering the data.
39
39
  */
40
- path: String,
40
+ path: {
41
+ type: String,
42
+ sync: true,
43
+ },
41
44
 
42
45
  /**
43
46
  * Current filter value.
@@ -45,11 +48,13 @@ export const GridFilterElementMixin = (superClass) =>
45
48
  value: {
46
49
  type: String,
47
50
  notify: true,
51
+ sync: true,
48
52
  },
49
53
 
50
54
  /** @private */
51
55
  _textField: {
52
56
  type: Object,
57
+ sync: true,
53
58
  },
54
59
  };
55
60
  }
@@ -65,6 +70,10 @@ export const GridFilterElementMixin = (superClass) =>
65
70
  this._filterController = new SlotController(this, '', 'vaadin-text-field', {
66
71
  initializer: (field) => {
67
72
  field.addEventListener('value-changed', (e) => {
73
+ if (field.__previousValue === undefined && e.detail.value === '') {
74
+ field.__previousValue = e.detail.value;
75
+ return;
76
+ }
68
77
  this.value = e.detail.value;
69
78
  });
70
79
 
@@ -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,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
  /**
@@ -967,6 +971,9 @@ export const KeyboardNavigationMixin = (superClass) =>
967
971
 
968
972
  /** @protected */
969
973
  _resetKeyboardNavigation() {
974
+ if (!this.$ && this.performUpdate) {
975
+ this.performUpdate();
976
+ }
970
977
  // Header / footer
971
978
  ['header', 'footer'].forEach((section) => {
972
979
  if (!this.__isValidFocusable(this[`_${section}Focusable`])) {
@@ -985,7 +992,7 @@ export const KeyboardNavigationMixin = (superClass) =>
985
992
 
986
993
  if (firstVisibleCell && firstVisibleRow) {
987
994
  // Reset memoized column
988
- delete this._focusedColumnOrder;
995
+ this._focusedColumnOrder = undefined;
989
996
  this._itemsFocusable = this.__getFocusable(firstVisibleRow, firstVisibleCell);
990
997
  }
991
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
- '_flatSizeChanged(_flatSize, __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
  }),
@@ -511,9 +507,11 @@ export const GridMixin = (superClass) =>
511
507
  }
512
508
 
513
509
  if (this._columnTree) {
514
- this._columnTree[this._columnTree.length - 1].forEach(
515
- (c) => c.isConnected && c.notifyPath && c.notifyPath('_cells.*', c._cells),
516
- );
510
+ this._columnTree[this._columnTree.length - 1].forEach((c) => {
511
+ if (c.isConnected && c._cells) {
512
+ c._cells = [...c._cells];
513
+ }
514
+ });
517
515
  }
518
516
 
519
517
  this.__afterCreateScrollerRowsDebouncer = Debouncer.debounce(
@@ -678,8 +676,8 @@ export const GridMixin = (superClass) =>
678
676
  detailsCell._vacant = false;
679
677
  }
680
678
 
681
- if (column.notifyPath && !noNotify) {
682
- column.notifyPath('_cells.*', column._cells);
679
+ if (!noNotify) {
680
+ column._cells = [...column._cells];
683
681
  }
684
682
  } else {
685
683
  // Header & footer
@@ -876,22 +874,9 @@ export const GridMixin = (superClass) =>
876
874
  this._resetKeyboardNavigation();
877
875
  this._a11yUpdateHeaderRows();
878
876
  this._a11yUpdateFooterRows();
879
- this.__updateFooterPositioning();
880
877
  this.generateCellClassNames();
881
878
  this.generateCellPartNames();
882
- }
883
-
884
- /** @private */
885
- __updateFooterPositioning() {
886
- // TODO: fixed in Firefox 99, remove when we can drop Firefox ESR 91 support
887
- if (this._firefox && parseFloat(navigator.userAgent.match(/Firefox\/(\d{2,3}.\d)/u)[1]) < 99) {
888
- // Sticky (or translated) footer in a flexbox host doesn't get included in
889
- // the scroll height calculation on FF. This is a workaround for the issue.
890
- this.$.items.style.paddingBottom = 0;
891
- if (!this.allRowsVisible) {
892
- this.$.items.style.paddingBottom = `${this.$.footer.offsetHeight}px`;
893
- }
894
- }
879
+ this.__updateHeaderAndFooter();
895
880
  }
896
881
 
897
882
  /**
@@ -929,7 +914,6 @@ export const GridMixin = (superClass) =>
929
914
  /** @private */
930
915
  _resizeHandler() {
931
916
  this._updateDetailsCellHeights();
932
- this.__updateFooterPositioning();
933
917
  this.__updateHorizontalScrollPosition();
934
918
  }
935
919
 
@@ -983,7 +967,7 @@ export const GridMixin = (superClass) =>
983
967
 
984
968
  /** @protected */
985
969
  _hideTooltip(immediate) {
986
- const tooltip = this._tooltipController.node;
970
+ const tooltip = this._tooltipController && this._tooltipController.node;
987
971
  if (tooltip) {
988
972
  tooltip._stateController.close(immediate);
989
973
  }
@@ -1001,19 +985,22 @@ export const GridMixin = (superClass) =>
1001
985
  * It is not guaranteed that the update happens immediately (synchronously) after it is requested.
1002
986
  */
1003
987
  requestContentUpdate() {
1004
- if (this._columnTree) {
1005
- // Header and footer renderers
1006
- this._columnTree.forEach((level) => {
1007
- level.forEach((column) => {
1008
- if (column._renderHeaderAndFooter) {
1009
- column._renderHeaderAndFooter();
1010
- }
1011
- });
1012
- });
988
+ // Header and footer renderers
989
+ this.__updateHeaderAndFooter();
1013
990
 
1014
- // Body and row details renderers
1015
- this.__updateVisibleRows();
1016
- }
991
+ // Body and row details renderers
992
+ this.__updateVisibleRows();
993
+ }
994
+
995
+ /** @private */
996
+ __updateHeaderAndFooter() {
997
+ (this._columnTree || []).forEach((level) => {
998
+ level.forEach((column) => {
999
+ if (column._renderHeaderAndFooter) {
1000
+ column._renderHeaderAndFooter();
1001
+ }
1002
+ });
1003
+ });
1017
1004
  }
1018
1005
 
1019
1006
  /** @protected */
@@ -19,6 +19,7 @@ export const RowDetailsMixin = (superClass) =>
19
19
  detailsOpenedItems: {
20
20
  type: Array,
21
21
  value: () => [],
22
+ sync: true,
22
23
  },
23
24
 
24
25
  /**
@@ -37,7 +38,10 @@ export const RowDetailsMixin = (superClass) =>
37
38
  *
38
39
  * @type {GridRowDetailsRenderer | null | undefined}
39
40
  */
40
- rowDetailsRenderer: Function,
41
+ rowDetailsRenderer: {
42
+ type: Function,
43
+ sync: true,
44
+ },
41
45
 
42
46
  /**
43
47
  * @type {!Array<!HTMLElement> | undefined}
@@ -51,7 +55,7 @@ export const RowDetailsMixin = (superClass) =>
51
55
 
52
56
  static get observers() {
53
57
  return [
54
- '_detailsOpenedItemsChanged(detailsOpenedItems.*, rowDetailsRenderer)',
58
+ '_detailsOpenedItemsChanged(detailsOpenedItems, rowDetailsRenderer)',
55
59
  '_rowDetailsRendererChanged(rowDetailsRenderer)',
56
60
  ];
57
61
  }
@@ -90,12 +94,7 @@ export const RowDetailsMixin = (superClass) =>
90
94
  }
91
95
 
92
96
  /** @private */
93
- _detailsOpenedItemsChanged(changeRecord, rowDetailsRenderer) {
94
- // Skip to avoid duplicate work of both `.splices` and `.length` updates.
95
- if (changeRecord.path === 'detailsOpenedItems.length' || !changeRecord.value) {
96
- return;
97
- }
98
-
97
+ _detailsOpenedItemsChanged(detailsOpenedItems, rowDetailsRenderer) {
99
98
  iterateChildren(this.$.items, (row) => {
100
99
  // Re-renders the row to possibly close the previously opened details.
101
100
  if (row.hasAttribute('details-opened')) {
@@ -61,6 +61,7 @@ export const ScrollMixin = (superClass) =>
61
61
  columnRendering: {
62
62
  type: String,
63
63
  value: 'eager',
64
+ sync: true,
64
65
  },
65
66
 
66
67
  /**
@@ -28,6 +28,7 @@ export const GridSelectionColumnBaseMixin = (superClass) =>
28
28
  width: {
29
29
  type: String,
30
30
  value: '58px',
31
+ sync: true,
31
32
  },
32
33
 
33
34
  /**
@@ -38,6 +39,7 @@ export const GridSelectionColumnBaseMixin = (superClass) =>
38
39
  flexGrow: {
39
40
  type: Number,
40
41
  value: 0,
42
+ sync: true,
41
43
  },
42
44
 
43
45
  /**
@@ -49,6 +51,7 @@ export const GridSelectionColumnBaseMixin = (superClass) =>
49
51
  type: Boolean,
50
52
  value: false,
51
53
  notify: true,
54
+ sync: true,
52
55
  },
53
56
 
54
57
  /**
@@ -59,6 +62,7 @@ export const GridSelectionColumnBaseMixin = (superClass) =>
59
62
  autoSelect: {
60
63
  type: Boolean,
61
64
  value: false,
65
+ sync: true,
62
66
  },
63
67
 
64
68
  /**
@@ -69,10 +73,14 @@ export const GridSelectionColumnBaseMixin = (superClass) =>
69
73
  dragSelect: {
70
74
  type: Boolean,
71
75
  value: false,
76
+ sync: true,
72
77
  },
73
78
 
74
79
  /** @protected */
75
- _indeterminate: Boolean,
80
+ _indeterminate: {
81
+ type: Boolean,
82
+ sync: true,
83
+ },
76
84
 
77
85
  /** @protected */
78
86
  _selectAllHidden: Boolean,
@@ -181,7 +189,7 @@ export const GridSelectionColumnBaseMixin = (superClass) =>
181
189
  // Get the row where the drag started
182
190
  const dragStartRow = renderedRows.find((row) => row.contains(event.currentTarget.assignedSlot));
183
191
  // Whether to select or deselect the items on drag
184
- this.__dragSelect = !this._grid._isSelected(dragStartRow._item);
192
+ this.__selectOnDrag = !this._grid._isSelected(dragStartRow._item);
185
193
  // Store the index of the row where the drag started
186
194
  this.__dragStartIndex = dragStartRow.index;
187
195
  // Store the item of the row where the drag started
@@ -191,7 +199,7 @@ export const GridSelectionColumnBaseMixin = (superClass) =>
191
199
  } else if (event.detail.state === 'end') {
192
200
  // if drag start and end stays within the same item, then toggle its state
193
201
  if (this.__dragStartItem) {
194
- if (this.__dragSelect) {
202
+ if (this.__selectOnDrag) {
195
203
  this._selectItem(this.__dragStartItem);
196
204
  } else {
197
205
  this._deselectItem(this.__dragStartItem);
@@ -254,7 +262,7 @@ export const GridSelectionColumnBaseMixin = (superClass) =>
254
262
  (hoveredIndex > this.__dragStartIndex && row.index >= this.__dragStartIndex && row.index <= hoveredIndex) ||
255
263
  (hoveredIndex < this.__dragStartIndex && row.index <= this.__dragStartIndex && row.index >= hoveredIndex)
256
264
  ) {
257
- if (this.__dragSelect) {
265
+ if (this.__selectOnDrag) {
258
266
  this._selectItem(row._item);
259
267
  } else {
260
268
  this._deselectItem(row._item);
@@ -19,6 +19,7 @@ export const SelectionMixin = (superClass) =>
19
19
  type: Object,
20
20
  notify: true,
21
21
  value: () => [],
22
+ sync: true,
22
23
  },
23
24
 
24
25
  /**
@@ -27,13 +28,13 @@ export const SelectionMixin = (superClass) =>
27
28
  */
28
29
  __selectedKeys: {
29
30
  type: Object,
30
- computed: '__computeSelectedKeys(itemIdPath, selectedItems.*)',
31
+ computed: '__computeSelectedKeys(itemIdPath, selectedItems)',
31
32
  },
32
33
  };
33
34
  }
34
35
 
35
36
  static get observers() {
36
- return ['__selectedItemsChanged(itemIdPath, selectedItems.*)'];
37
+ return ['__selectedItemsChanged(itemIdPath, selectedItems)'];
37
38
  }
38
39
 
39
40
  /**
@@ -91,7 +92,7 @@ export const SelectionMixin = (superClass) =>
91
92
 
92
93
  /** @private */
93
94
  __computeSelectedKeys(itemIdPath, selectedItems) {
94
- const selected = selectedItems.base || [];
95
+ const selected = selectedItems || [];
95
96
  const selectedKeys = new Set();
96
97
  selected.forEach((item) => {
97
98
  selectedKeys.add(this.getItemId(item));
@@ -14,7 +14,10 @@ export const GridSortColumnMixin = (superClass) =>
14
14
  /**
15
15
  * JS Path of the property in the item used for sorting the data.
16
16
  */
17
- path: String,
17
+ path: {
18
+ type: String,
19
+ sync: true,
20
+ },
18
21
 
19
22
  /**
20
23
  * How to sort the data.
@@ -25,6 +28,7 @@ export const GridSortColumnMixin = (superClass) =>
25
28
  direction: {
26
29
  type: String,
27
30
  notify: true,
31
+ sync: true,
28
32
  },
29
33
  };
30
34
  }
@@ -89,6 +89,7 @@ export const GridSorterMixin = (superClass) =>
89
89
  reflectToAttribute: true,
90
90
  notify: true,
91
91
  value: null,
92
+ sync: true,
92
93
  },
93
94
 
94
95
  /**
@@ -98,6 +99,7 @@ export const GridSorterMixin = (superClass) =>
98
99
  _order: {
99
100
  type: Number,
100
101
  value: null,
102
+ sync: true,
101
103
  },
102
104
 
103
105
  /** @private */