@vaadin/grid 25.2.7 → 25.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.
- package/custom-elements.json +248 -128
- package/package.json +12 -12
- package/src/directives/cell-content-directive.js +33 -0
- package/src/styles/vaadin-grid-base-styles.js +0 -4
- package/src/vaadin-grid-a11y-mixin.js +20 -31
- package/src/vaadin-grid-active-item-mixin.js +1 -1
- package/src/vaadin-grid-column-auto-width-mixin.js +4 -13
- package/src/vaadin-grid-column-group-mixin.js +5 -20
- package/src/vaadin-grid-column-mixin.d.ts +3 -3
- package/src/vaadin-grid-column-mixin.js +57 -98
- package/src/vaadin-grid-column-reordering-mixin.js +10 -5
- package/src/vaadin-grid-column-resizing-mixin.js +4 -0
- package/src/vaadin-grid-data-provider-mixin.js +1 -1
- package/src/vaadin-grid-drag-and-drop-mixin.js +2 -5
- package/src/vaadin-grid-dynamic-columns-mixin.js +0 -5
- package/src/vaadin-grid-filter-column-mixin.js +1 -1
- package/src/vaadin-grid-filter-element-mixin.js +1 -3
- package/src/vaadin-grid-filter.js +1 -0
- package/src/vaadin-grid-header-footer-rendering-mixin.js +296 -0
- package/src/vaadin-grid-helpers.js +13 -0
- package/src/vaadin-grid-keyboard-navigation-mixin.js +3 -7
- package/src/vaadin-grid-mixin.js +69 -228
- package/src/vaadin-grid-row-details-mixin.js +1 -1
- package/src/vaadin-grid-scroll-mixin.js +2 -11
- package/src/vaadin-grid-selection-column-base-mixin.js +66 -21
- package/src/vaadin-grid-sort-column-mixin.js +11 -2
- package/src/vaadin-grid-sorter-mixin.js +22 -2
- package/src/vaadin-grid-sorter.js +2 -1
- package/src/vaadin-grid-tree-toggle.js +1 -0
- package/src/vaadin-grid.d.ts +63 -1
- package/src/vaadin-grid.js +51 -2
- package/web-types.json +29 -94
- package/web-types.lit.json +17 -10
|
@@ -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
|
+
};
|
|
@@ -5,6 +5,19 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { microTask } from '@vaadin/component-base/src/async.js';
|
|
7
7
|
import { Debouncer } from '@vaadin/component-base/src/debounce.js';
|
|
8
|
+
import { getClosestElement } from '@vaadin/component-base/src/dom-utils.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns the grid cell that contains the given node, or `undefined` if the
|
|
12
|
+
* node is not inside a cell's content.
|
|
13
|
+
*
|
|
14
|
+
* @param {Node} node
|
|
15
|
+
* @return {HTMLElement | undefined}
|
|
16
|
+
*/
|
|
17
|
+
export function getClosestCell(node) {
|
|
18
|
+
const content = getClosestElement('vaadin-grid-cell-content', node);
|
|
19
|
+
return content?.assignedSlot?.parentElement;
|
|
20
|
+
}
|
|
8
21
|
|
|
9
22
|
/**
|
|
10
23
|
* Returns the cells of the given row, excluding the details cell.
|
|
@@ -154,9 +154,7 @@ export const KeyboardNavigationMixin = (superClass) =>
|
|
|
154
154
|
|
|
155
155
|
/** @private */
|
|
156
156
|
_focusableChanged(focusable, oldFocusable) {
|
|
157
|
-
|
|
158
|
-
oldFocusable.setAttribute('tabindex', '-1');
|
|
159
|
-
}
|
|
157
|
+
oldFocusable?.setAttribute('tabindex', '-1');
|
|
160
158
|
if (focusable) {
|
|
161
159
|
this._updateGridSectionFocusTarget(focusable);
|
|
162
160
|
}
|
|
@@ -415,9 +413,7 @@ export const KeyboardNavigationMixin = (superClass) =>
|
|
|
415
413
|
_onRowNavigation(activeRow, dy) {
|
|
416
414
|
const { dstRow } = this.__navigateRows(dy, activeRow);
|
|
417
415
|
|
|
418
|
-
|
|
419
|
-
dstRow.focus();
|
|
420
|
-
}
|
|
416
|
+
dstRow?.focus();
|
|
421
417
|
}
|
|
422
418
|
|
|
423
419
|
/** @private */
|
|
@@ -765,7 +761,7 @@ export const KeyboardNavigationMixin = (superClass) =>
|
|
|
765
761
|
// When clicking a cell with only text nodes, skip activating the cell
|
|
766
762
|
// on click, since that is already handled on keydown.
|
|
767
763
|
const cell = e.composedPath()[0];
|
|
768
|
-
const target =
|
|
764
|
+
const target = cell._content?.firstElementChild || cell;
|
|
769
765
|
const wasNavigating = this.hasAttribute('navigating');
|
|
770
766
|
const clickEvent = new MouseEvent('click', {
|
|
771
767
|
shiftKey: e.shiftKey,
|