@vaadin/grid 25.3.0-alpha7 → 25.3.0-alpha8

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.
@@ -93,8 +93,8 @@ export const ColumnBaseMixin = (superClass) =>
93
93
 
94
94
  /**
95
95
  * Aligns the columns cell content horizontally.
96
- * Supported values: "start", "center" and "end".
97
- * @attr {start|center|end} text-align
96
+ *
97
+ * @attr {start|center|end|left|right} text-align
98
98
  */
99
99
  textAlign: {
100
100
  type: String,
@@ -261,11 +261,11 @@ export const ColumnBaseMixin = (superClass) =>
261
261
 
262
262
  static get observers() {
263
263
  return [
264
- '_widthChanged(width, _headerCell, _footerCell, _cells)',
264
+ '_widthChanged(width, _cells)',
265
265
  '_frozenChanged(frozen, _headerCell, _footerCell, _cells)',
266
266
  '_frozenToEndChanged(frozenToEnd, _headerCell, _footerCell, _cells)',
267
- '_flexGrowChanged(flexGrow, _headerCell, _footerCell, _cells)',
268
- '_textAlignChanged(textAlign, _cells, _headerCell, _footerCell)',
267
+ '_flexGrowChanged(flexGrow, _cells)',
268
+ '_textAlignChanged(textAlign, _cells)',
269
269
  '_lastFrozenChanged(_lastFrozen)',
270
270
  '_firstFrozenToEndChanged(_firstFrozenToEnd)',
271
271
  '_onRendererOrBindingChanged(_renderer, _cells, _bodyContentHidden, path)',
@@ -275,7 +275,7 @@ export const ColumnBaseMixin = (superClass) =>
275
275
  '_reorderStatusChanged(_reorderStatus, _headerCell, _footerCell, _cells)',
276
276
  '_hiddenChanged(hidden, _headerCell, _footerCell, _cells)',
277
277
  '_rowHeaderChanged(rowHeader, _cells)',
278
- '__headerFooterPartNameChanged(_headerCell, _footerCell, headerPartName, footerPartName)',
278
+ '__headerFooterPartNameChanged(headerPartName, footerPartName)',
279
279
  ];
280
280
  }
281
281
 
@@ -382,7 +382,9 @@ export const ColumnBaseMixin = (superClass) =>
382
382
  this.parentElement._columnPropChanged('flexGrow');
383
383
  }
384
384
 
385
- this._allCells.forEach((cell) => {
385
+ this._grid?.__scheduleRenderHeaderFooter?.();
386
+
387
+ this._cells?.forEach((cell) => {
386
388
  cell.style.flexGrow = flexGrow;
387
389
  });
388
390
  }
@@ -393,7 +395,9 @@ export const ColumnBaseMixin = (superClass) =>
393
395
  this.parentElement._columnPropChanged('width');
394
396
  }
395
397
 
396
- this._allCells.forEach((cell) => {
398
+ this._grid?.__scheduleRenderHeaderFooter?.();
399
+
400
+ this._cells?.forEach((cell) => {
397
401
  cell.style.width = width;
398
402
  });
399
403
  }
@@ -512,12 +516,10 @@ export const ColumnBaseMixin = (superClass) =>
512
516
  if (textAlign === undefined || this._grid === undefined) {
513
517
  return;
514
518
  }
515
- if (['start', 'end', 'center'].indexOf(textAlign) === -1) {
516
- console.warn('textAlign can only be set as "start", "end" or "center"');
517
- return;
518
- }
519
519
 
520
- this._allCells.forEach((cell) => {
520
+ this._grid.__scheduleRenderHeaderFooter?.();
521
+
522
+ this._cells?.forEach((cell) => {
521
523
  cell._content.style.textAlign = textAlign;
522
524
  });
523
525
  }
@@ -641,19 +643,8 @@ export const ColumnBaseMixin = (superClass) =>
641
643
  }
642
644
 
643
645
  /** @private */
644
- __headerFooterPartNameChanged(headerCell, footerCell, headerPartName, footerPartName) {
645
- [
646
- { cell: headerCell, partName: headerPartName },
647
- { cell: footerCell, partName: footerPartName },
648
- ].forEach(({ cell, partName }) => {
649
- if (cell) {
650
- const customParts = cell.__customParts || [];
651
- cell.part.remove(...customParts);
652
-
653
- cell.__customParts = partName ? partName.trim().split(' ') : [];
654
- cell.part.add(...cell.__customParts);
655
- }
656
- });
646
+ __headerFooterPartNameChanged() {
647
+ this._grid?.__scheduleRenderHeaderFooter?.();
657
648
  }
658
649
 
659
650
  /**
@@ -457,9 +457,12 @@ export const ColumnReorderingMixin = (superClass) =>
457
457
  }
458
458
  });
459
459
 
460
+ [...this.$.items.children, this.$.sizer].forEach((row) => {
461
+ this._updateFirstAndLastColumnForRow(row);
462
+ });
463
+
460
464
  this.__scheduleRenderHeaderFooter();
461
465
  this._debounceUpdateFrozenColumn();
462
- this._updateFirstAndLastColumn();
463
466
  }
464
467
 
465
468
  /**
@@ -98,6 +98,10 @@ export const ColumnResizingMixin = (superClass) =>
98
98
  cell._column.flexGrow = 0;
99
99
  });
100
100
 
101
+ // Flush the scheduled header/footer render to apply the new widths
102
+ // to the cells before measuring the layout below
103
+ this.__renderHeaderFooterDebouncer?.flush();
104
+
101
105
  const cellFrozenToEnd = this._frozenToEndCells[0];
102
106
 
103
107
  // When handle moves below the cell frozen to end, scroll into view.
@@ -143,11 +143,6 @@ export const DynamicColumnsMixin = (superClass) =>
143
143
  });
144
144
  }
145
145
 
146
- /** @protected */
147
- _updateFirstAndLastColumn() {
148
- Array.from(this.shadowRoot.querySelectorAll('tr')).forEach((row) => this._updateFirstAndLastColumnForRow(row));
149
- }
150
-
151
146
  /**
152
147
  * @param {!HTMLElement} row
153
148
  * @protected
@@ -37,6 +37,7 @@ import { GridFilterElementMixin } from './vaadin-grid-filter-element-mixin.js';
37
37
  *
38
38
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
39
39
  *
40
+ * @attr {string} theme - The theme variants to apply to the component.
40
41
  * @customElement vaadin-grid-filter
41
42
  * @extends HTMLElement
42
43
  */
@@ -0,0 +1,296 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2016 - 2026 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { html, nothing, render } from 'lit';
7
+ import { cache } from 'lit/directives/cache.js';
8
+ import { classMap } from 'lit/directives/class-map.js';
9
+ import { ifDefined } from 'lit/directives/if-defined.js';
10
+ import { repeat } from 'lit/directives/repeat.js';
11
+ import { styleMap } from 'lit/directives/style-map.js';
12
+ import { microTask } from '@vaadin/component-base/src/async.js';
13
+ import { Debouncer } from '@vaadin/component-base/src/debounce.js';
14
+ import { partMap } from '@vaadin/component-base/src/directives/part-map.js';
15
+ import { cellContent } from './directives/cell-content-directive.js';
16
+
17
+ function isContentCell(column, level, columnTree) {
18
+ const isLastRow = level === columnTree.length - 1;
19
+ return isLastRow || column.localName === 'vaadin-grid-column-group';
20
+ }
21
+
22
+ function isHeaderRowVisible(columns, level, columnTree) {
23
+ return columns.some((column) => {
24
+ if (column.hidden || !isContentCell(column, level, columnTree)) {
25
+ return false;
26
+ }
27
+
28
+ if (column.headerRenderer) {
29
+ // The column has a header renderer -> row should be visible
30
+ return true;
31
+ }
32
+
33
+ if (column.header === null) {
34
+ // The column header is explicitly set to null -> doesn't block hiding the row
35
+ return false;
36
+ }
37
+
38
+ return column.path || column.header !== undefined;
39
+ });
40
+ }
41
+
42
+ function isFooterRowVisible(columns, level, columnTree) {
43
+ return columns.some((column) => {
44
+ if (column.hidden || !isContentCell(column, level, columnTree)) {
45
+ return false;
46
+ }
47
+
48
+ return column.footerRenderer;
49
+ });
50
+ }
51
+
52
+ /**
53
+ * Converts a whitespace separated list of custom part names
54
+ * into an object accepted by the `partMap` directive.
55
+ */
56
+ function getCustomParts(partName) {
57
+ return Object.fromEntries(
58
+ (partName ?? '')
59
+ .split(' ')
60
+ .filter((name) => name !== '')
61
+ .map((name) => [name, true]),
62
+ );
63
+ }
64
+
65
+ /**
66
+ * A mixin providing rendering of header and footer rows based on the column tree.
67
+ */
68
+ export const HeaderFooterRenderingMixin = (superClass) =>
69
+ class HeaderFooterRenderingMixin extends superClass {
70
+ /** @private */
71
+ __scheduleRenderHeaderFooter() {
72
+ this.__renderHeaderFooterDebouncer = Debouncer.debounce(this.__renderHeaderFooterDebouncer, microTask, () => {
73
+ this.__renderHeaderFooter();
74
+ });
75
+ }
76
+
77
+ /** @private */
78
+ __renderHeaderFooter() {
79
+ this.__renderHeaderFooterDebouncer?.cancel();
80
+
81
+ const sortedColumnTree = (this._columnTree ?? []).map((columns) => {
82
+ return columns.toSorted((a, b) => a._order - b._order);
83
+ });
84
+
85
+ sortedColumnTree.flat().forEach((column) => {
86
+ column._emptyCells = [];
87
+ });
88
+
89
+ this.#renderHeader(sortedColumnTree);
90
+ this.#renderFooter(sortedColumnTree);
91
+
92
+ this._resetKeyboardNavigation();
93
+ this.__a11yUpdateGridSize(this.size, this._columnTree, this.__emptyState);
94
+ }
95
+
96
+ #renderHeader(columnTree) {
97
+ const rows = this.#getRows(columnTree, 'header');
98
+ render(rows.map(this.#renderHeaderRow), this.$.header, { host: this });
99
+
100
+ this.$.table.toggleAttribute('has-header', !!this.$.header.querySelector('tr:not([hidden])'));
101
+
102
+ this.$.header.querySelectorAll('.header-cell').forEach((cell) => {
103
+ const column = cell._column;
104
+ const isColumnRow = cell.parentElement === this.$.header.lastElementChild;
105
+ if (isColumnRow || column.localName === 'vaadin-grid-column-group') {
106
+ column._headerCell = cell;
107
+ } else {
108
+ column._emptyCells.push(cell);
109
+ }
110
+ });
111
+ }
112
+
113
+ #renderHeaderRow = ({ level, cells, isLastRow, isFirstRow, isRowVisible }) => {
114
+ const rowParts = {
115
+ 'first-header-row': isFirstRow,
116
+ 'last-header-row': isLastRow,
117
+ };
118
+
119
+ return html`
120
+ <tr
121
+ role="row"
122
+ part="row header-row${partMap(rowParts)}"
123
+ class="row header-row${classMap(rowParts)}"
124
+ tabindex="-1"
125
+ ?hidden=${!isRowVisible}
126
+ >
127
+ ${repeat(
128
+ cells,
129
+ ({ column }) => column._id,
130
+ ({ column, isFirstCell, isLastCell, isContentCell }) => {
131
+ // `cache` keeps the cell and its rendered content when the
132
+ // column gets hidden, so it can be restored as-is when the
133
+ // column is shown again.
134
+ if (column.hidden) {
135
+ return cache(nothing);
136
+ }
137
+
138
+ const cellParts = {
139
+ 'first-header-row-cell': isFirstRow,
140
+ 'last-header-row-cell': isLastRow,
141
+ 'first-column-cell': isFirstCell,
142
+ 'last-column-cell': isLastCell,
143
+ };
144
+
145
+ const customCellParts = isContentCell ? getCustomParts(column.headerPartName) : {};
146
+
147
+ return cache(html`
148
+ <th
149
+ role="columnheader"
150
+ part="cell header-cell${partMap({ ...cellParts, ...customCellParts })}"
151
+ class="cell header-cell${classMap(cellParts)}"
152
+ style="${styleMap({
153
+ width: column.width,
154
+ 'flex-grow': column.flexGrow,
155
+ })}"
156
+ ?first-column="${isFirstCell}"
157
+ ?last-column="${isLastCell}"
158
+ @keydown="${this.__onCellKeyDown}"
159
+ @mousedown=${this.__onCellMouseDown}
160
+ @mouseenter=${this.__onCellMouseEnter}
161
+ @mouseleave=${this.__onCellMouseLeave}
162
+ colspan="${ifDefined(column._colSpan)}"
163
+ aria-colspan="${ifDefined(column._colSpan)}"
164
+ tabindex="-1"
165
+ ._column=${column}
166
+ >
167
+ ${cellContent(this, `vaadin-grid-header-cell-content-${level}-${column._id}`, {
168
+ textAlign: column.textAlign,
169
+ })}
170
+ ${column.resizable ? html`<div part="resize-handle" class="resize-handle"></div>` : nothing}
171
+ </th>
172
+ `);
173
+ },
174
+ )}
175
+ </tr>
176
+ `;
177
+ };
178
+
179
+ #renderFooter(columnTree) {
180
+ const rows = this.#getRows(columnTree, 'footer');
181
+ render(rows.map(this.#renderFooterRow), this.$.footer, { host: this });
182
+
183
+ this.$.table.toggleAttribute('has-footer', !!this.$.footer.querySelector('tr:not([hidden])'));
184
+
185
+ this.$.footer.querySelectorAll('.footer-cell').forEach((cell) => {
186
+ const column = cell._column;
187
+ const isColumnRow = cell.parentElement === this.$.footer.firstElementChild;
188
+ if (isColumnRow || column.localName === 'vaadin-grid-column-group') {
189
+ column._footerCell = cell;
190
+ } else {
191
+ column._emptyCells.push(cell);
192
+ }
193
+ });
194
+ }
195
+
196
+ #renderFooterRow = ({ level, cells, isLastRow, isFirstRow, isRowVisible }) => {
197
+ const rowParts = {
198
+ 'first-footer-row': isFirstRow,
199
+ 'last-footer-row': isLastRow,
200
+ };
201
+
202
+ return html`
203
+ <tr
204
+ role="row"
205
+ part="row footer-row${partMap(rowParts)}"
206
+ class="row footer-row${classMap(rowParts)}"
207
+ tabindex="-1"
208
+ ?hidden=${!isRowVisible}
209
+ >
210
+ ${repeat(
211
+ cells,
212
+ ({ column }) => column._id,
213
+ ({ column, isFirstCell, isLastCell, isContentCell }) => {
214
+ // `cache` keeps the cell and its rendered content when the
215
+ // column gets hidden, so it can be restored as-is when the
216
+ // column is shown again.
217
+ if (column.hidden) {
218
+ return cache(nothing);
219
+ }
220
+
221
+ const cellParts = {
222
+ 'first-footer-row-cell': isFirstRow,
223
+ 'last-footer-row-cell': isLastRow,
224
+ 'first-column-cell': isFirstCell,
225
+ 'last-column-cell': isLastCell,
226
+ };
227
+
228
+ const customCellParts = isContentCell ? getCustomParts(column.footerPartName) : {};
229
+
230
+ return cache(html`
231
+ <td
232
+ role="gridcell"
233
+ part="cell footer-cell${partMap({ ...cellParts, ...customCellParts })}"
234
+ class="cell footer-cell${classMap(cellParts)}"
235
+ style="${styleMap({
236
+ width: column.width,
237
+ 'flex-grow': column.flexGrow,
238
+ })}"
239
+ ?first-column="${isFirstCell}"
240
+ ?last-column="${isLastCell}"
241
+ @keydown="${this.__onCellKeyDown}"
242
+ @mousedown=${this.__onCellMouseDown}
243
+ @mouseenter=${this.__onCellMouseEnter}
244
+ @mouseleave=${this.__onCellMouseLeave}
245
+ colspan="${ifDefined(column._colSpan)}"
246
+ aria-colspan="${ifDefined(column._colSpan)}"
247
+ tabindex="-1"
248
+ ._column=${column}
249
+ >
250
+ ${cellContent(this, `vaadin-grid-footer-cell-content-${level}-${column._id}`, {
251
+ textAlign: column.textAlign,
252
+ })}
253
+ </td>
254
+ `);
255
+ },
256
+ )}
257
+ </tr>
258
+ `;
259
+ };
260
+
261
+ #getRows(columnTree, section) {
262
+ let rows = columnTree.map((columns, level) => {
263
+ const visibleColumns = columns.filter((column) => !column.hidden);
264
+
265
+ return {
266
+ level,
267
+ cells: columns.map((column) => {
268
+ return {
269
+ column,
270
+ isFirstCell: column === visibleColumns.at(0),
271
+ isLastCell: column === visibleColumns.at(-1),
272
+ isContentCell: isContentCell(column, level, columnTree),
273
+ };
274
+ }),
275
+ isRowVisible:
276
+ section === 'header'
277
+ ? isHeaderRowVisible(columns, level, columnTree)
278
+ : isFooterRowVisible(columns, level, columnTree),
279
+ };
280
+ });
281
+
282
+ if (section === 'footer') {
283
+ rows = rows.toReversed();
284
+ }
285
+
286
+ const visibleRows = rows.filter((row) => row.isRowVisible);
287
+
288
+ return rows.map((row) => {
289
+ return {
290
+ ...row,
291
+ isFirstRow: row === visibleRows.at(0),
292
+ isLastRow: row === visibleRows.at(-1),
293
+ };
294
+ });
295
+ }
296
+ };
@@ -13,7 +13,6 @@ import { A11yMixin } from './vaadin-grid-a11y-mixin.js';
13
13
  import { ActiveItemMixin } from './vaadin-grid-active-item-mixin.js';
14
14
  import { ArrayDataProviderMixin } from './vaadin-grid-array-data-provider-mixin.js';
15
15
  import { ColumnAutoWidthMixin } from './vaadin-grid-column-auto-width-mixin.js';
16
- import { ColumnRenderingMixin } from './vaadin-grid-column-rendering-mixin.js';
17
16
  import { ColumnReorderingMixin } from './vaadin-grid-column-reordering-mixin.js';
18
17
  import { ColumnResizingMixin } from './vaadin-grid-column-resizing-mixin.js';
19
18
  import { DataProviderMixin } from './vaadin-grid-data-provider-mixin.js';
@@ -21,6 +20,7 @@ import { DragAndDropMixin } from './vaadin-grid-drag-and-drop-mixin.js';
21
20
  import { DynamicColumnsMixin } from './vaadin-grid-dynamic-columns-mixin.js';
22
21
  import { EventContextMixin } from './vaadin-grid-event-context-mixin.js';
23
22
  import { FilterMixin } from './vaadin-grid-filter-mixin.js';
23
+ import { HeaderFooterRenderingMixin } from './vaadin-grid-header-footer-rendering-mixin.js';
24
24
  import {
25
25
  getBodyRowCells,
26
26
  getClosestCell,
@@ -47,7 +47,7 @@ export const GridMixin = (superClass) =>
47
47
  ArrayDataProviderMixin(
48
48
  DataProviderMixin(
49
49
  DynamicColumnsMixin(
50
- ColumnRenderingMixin(
50
+ HeaderFooterRenderingMixin(
51
51
  ActiveItemMixin(
52
52
  ScrollMixin(
53
53
  SelectionMixin(
@@ -597,7 +597,6 @@ export const GridMixin = (superClass) =>
597
597
 
598
598
  this._resizeHandler();
599
599
  this._frozenCellsChanged();
600
- this._updateFirstAndLastColumn();
601
600
  this._resetKeyboardNavigation();
602
601
  this.__a11yUpdateHeaderRows();
603
602
  this.__a11yUpdateFooterRows();
@@ -605,22 +604,6 @@ export const GridMixin = (superClass) =>
605
604
  this.__updateHeaderAndFooter();
606
605
  }
607
606
 
608
- /** @private */
609
- __updateHeaderFooterRowParts(section) {
610
- const visibleRows = [...this.$[section].querySelectorAll('tr:not([hidden])')];
611
- [...this.$[section].children].forEach((row) => {
612
- updatePart(row, `first-${section}-row`, row === visibleRows.at(0));
613
- updatePart(row, `last-${section}-row`, row === visibleRows.at(-1));
614
-
615
- getBodyRowCells(row).forEach((cell) => {
616
- updatePart(cell, `first-${section}-row-cell`, row === visibleRows.at(0));
617
- updatePart(cell, `last-${section}-row-cell`, row === visibleRows.at(-1));
618
- });
619
-
620
- this._updateFirstAndLastColumnForRow(row);
621
- });
622
- }
623
-
624
607
  /**
625
608
  * @param {!HTMLElement} row
626
609
  * @param {boolean} loading
@@ -51,6 +51,7 @@ import { GridSorterMixin } from './vaadin-grid-sorter-mixin.js';
51
51
  * @fires {CustomEvent} direction-changed - Fired when the `direction` property changes.
52
52
  * @fires {CustomEvent} sorter-changed - Fired when the `path` or `direction` property changes.
53
53
  *
54
+ * @attr {string} theme - The theme variants to apply to the component.
54
55
  * @customElement vaadin-grid-sorter
55
56
  * @extends HTMLElement
56
57
  */
@@ -59,6 +59,7 @@ import { GridTreeToggleMixin } from './vaadin-grid-tree-toggle-mixin.js';
59
59
  *
60
60
  * @fires {CustomEvent} expanded-changed - Fired when the `expanded` property changes.
61
61
  *
62
+ * @attr {string} theme - The theme variants to apply to the component.
62
63
  * @customElement vaadin-grid-tree-toggle
63
64
  * @extends HTMLElement
64
65
  */
@@ -274,6 +274,7 @@ const DEFAULT_I18N = {
274
274
  * @fires {CustomEvent} size-changed - Fired when the `size` property changes.
275
275
  * @fires {CustomEvent} item-toggle - Fired when the user selects or deselects an item through the selection column.
276
276
  *
277
+ * @attr {string} theme - The theme variants to apply to the component.
277
278
  * @customElement vaadin-grid
278
279
  * @extends HTMLElement
279
280
  */