@vaadin/grid 22.0.0-alpha7
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/LICENSE +190 -0
- package/README.md +79 -0
- package/all-imports.js +1 -0
- package/package.json +58 -0
- package/src/all-imports.js +14 -0
- package/src/array-data-provider.js +145 -0
- package/src/interfaces.d.ts +75 -0
- package/src/vaadin-grid-a11y-mixin.js +158 -0
- package/src/vaadin-grid-active-item-mixin.d.ts +19 -0
- package/src/vaadin-grid-active-item-mixin.js +117 -0
- package/src/vaadin-grid-array-data-provider-mixin.d.ts +16 -0
- package/src/vaadin-grid-array-data-provider-mixin.js +75 -0
- package/src/vaadin-grid-column-group.d.ts +54 -0
- package/src/vaadin-grid-column-group.js +320 -0
- package/src/vaadin-grid-column-reordering-mixin.d.ts +19 -0
- package/src/vaadin-grid-column-reordering-mixin.js +387 -0
- package/src/vaadin-grid-column-resizing-mixin.js +111 -0
- package/src/vaadin-grid-column.d.ts +133 -0
- package/src/vaadin-grid-column.js +745 -0
- package/src/vaadin-grid-data-provider-mixin.d.ts +108 -0
- package/src/vaadin-grid-data-provider-mixin.js +520 -0
- package/src/vaadin-grid-drag-and-drop-mixin.d.ts +69 -0
- package/src/vaadin-grid-drag-and-drop-mixin.js +433 -0
- package/src/vaadin-grid-dynamic-columns-mixin.js +180 -0
- package/src/vaadin-grid-event-context-mixin.d.ts +33 -0
- package/src/vaadin-grid-event-context-mixin.js +57 -0
- package/src/vaadin-grid-filter-column.d.ts +35 -0
- package/src/vaadin-grid-filter-column.js +120 -0
- package/src/vaadin-grid-filter-mixin.js +76 -0
- package/src/vaadin-grid-filter.d.ts +67 -0
- package/src/vaadin-grid-filter.js +125 -0
- package/src/vaadin-grid-helpers.js +23 -0
- package/src/vaadin-grid-keyboard-navigation-mixin.js +891 -0
- package/src/vaadin-grid-row-details-mixin.d.ts +44 -0
- package/src/vaadin-grid-row-details-mixin.js +200 -0
- package/src/vaadin-grid-scroll-mixin.d.ts +18 -0
- package/src/vaadin-grid-scroll-mixin.js +202 -0
- package/src/vaadin-grid-selection-column.d.ts +71 -0
- package/src/vaadin-grid-selection-column.js +285 -0
- package/src/vaadin-grid-selection-mixin.d.ts +30 -0
- package/src/vaadin-grid-selection-mixin.js +93 -0
- package/src/vaadin-grid-sort-column.d.ts +63 -0
- package/src/vaadin-grid-sort-column.js +118 -0
- package/src/vaadin-grid-sort-mixin.d.ts +15 -0
- package/src/vaadin-grid-sort-mixin.js +139 -0
- package/src/vaadin-grid-sorter.d.ts +94 -0
- package/src/vaadin-grid-sorter.js +230 -0
- package/src/vaadin-grid-styles.js +297 -0
- package/src/vaadin-grid-styling-mixin.d.ts +37 -0
- package/src/vaadin-grid-styling-mixin.js +71 -0
- package/src/vaadin-grid-tree-column.d.ts +36 -0
- package/src/vaadin-grid-tree-column.js +119 -0
- package/src/vaadin-grid-tree-toggle.d.ts +104 -0
- package/src/vaadin-grid-tree-toggle.js +205 -0
- package/src/vaadin-grid.d.ts +397 -0
- package/src/vaadin-grid.js +1004 -0
- package/theme/lumo/all-imports.js +11 -0
- package/theme/lumo/vaadin-grid-column-group.js +1 -0
- package/theme/lumo/vaadin-grid-column.js +1 -0
- package/theme/lumo/vaadin-grid-filter-column.js +2 -0
- package/theme/lumo/vaadin-grid-filter.js +2 -0
- package/theme/lumo/vaadin-grid-selection-column.js +2 -0
- package/theme/lumo/vaadin-grid-sort-column.js +2 -0
- package/theme/lumo/vaadin-grid-sorter-styles.js +53 -0
- package/theme/lumo/vaadin-grid-sorter.js +2 -0
- package/theme/lumo/vaadin-grid-styles.js +378 -0
- package/theme/lumo/vaadin-grid-tree-column.js +2 -0
- package/theme/lumo/vaadin-grid-tree-toggle-styles.js +112 -0
- package/theme/lumo/vaadin-grid-tree-toggle.js +2 -0
- package/theme/lumo/vaadin-grid.js +9 -0
- package/theme/material/all-imports.js +11 -0
- package/theme/material/vaadin-grid-column-group.js +1 -0
- package/theme/material/vaadin-grid-column.js +1 -0
- package/theme/material/vaadin-grid-filter-column.js +2 -0
- package/theme/material/vaadin-grid-filter.js +2 -0
- package/theme/material/vaadin-grid-selection-column.js +2 -0
- package/theme/material/vaadin-grid-sort-column.js +2 -0
- package/theme/material/vaadin-grid-sorter-styles.js +72 -0
- package/theme/material/vaadin-grid-sorter.js +2 -0
- package/theme/material/vaadin-grid-styles.js +252 -0
- package/theme/material/vaadin-grid-tree-column.js +2 -0
- package/theme/material/vaadin-grid-tree-toggle-styles.js +42 -0
- package/theme/material/vaadin-grid-tree-toggle.js +2 -0
- package/theme/material/vaadin-grid.js +2 -0
- package/vaadin-grid-column-group.d.ts +1 -0
- package/vaadin-grid-column-group.js +3 -0
- package/vaadin-grid-column.d.ts +1 -0
- package/vaadin-grid-column.js +3 -0
- package/vaadin-grid-filter-column.d.ts +1 -0
- package/vaadin-grid-filter-column.js +3 -0
- package/vaadin-grid-filter.d.ts +1 -0
- package/vaadin-grid-filter.js +3 -0
- package/vaadin-grid-selection-column.d.ts +1 -0
- package/vaadin-grid-selection-column.js +3 -0
- package/vaadin-grid-sort-column.d.ts +1 -0
- package/vaadin-grid-sort-column.js +3 -0
- package/vaadin-grid-sorter.d.ts +1 -0
- package/vaadin-grid-sorter.js +3 -0
- package/vaadin-grid-tree-column.d.ts +1 -0
- package/vaadin-grid-tree-column.js +3 -0
- package/vaadin-grid-tree-toggle.d.ts +1 -0
- package/vaadin-grid-tree-toggle.js +3 -0
- package/vaadin-grid.d.ts +3 -0
- package/vaadin-grid.js +4 -0
|
@@ -0,0 +1,1004 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2021 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
import { PolymerElement, html } from '@polymer/polymer/polymer-element.js';
|
|
7
|
+
import { beforeNextRender } from '@polymer/polymer/lib/utils/render-status.js';
|
|
8
|
+
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
|
|
9
|
+
import { processTemplates } from '@vaadin/component-base/src/templates.js';
|
|
10
|
+
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
11
|
+
import { Virtualizer } from '@vaadin/virtual-list/src/virtualizer.js';
|
|
12
|
+
import { A11yMixin } from './vaadin-grid-a11y-mixin.js';
|
|
13
|
+
import { ActiveItemMixin } from './vaadin-grid-active-item-mixin.js';
|
|
14
|
+
import { ArrayDataProviderMixin } from './vaadin-grid-array-data-provider-mixin.js';
|
|
15
|
+
import { ColumnResizingMixin } from './vaadin-grid-column-resizing-mixin.js';
|
|
16
|
+
import { DataProviderMixin } from './vaadin-grid-data-provider-mixin.js';
|
|
17
|
+
import { DynamicColumnsMixin } from './vaadin-grid-dynamic-columns-mixin.js';
|
|
18
|
+
import { EventContextMixin } from './vaadin-grid-event-context-mixin.js';
|
|
19
|
+
import { FilterMixin } from './vaadin-grid-filter-mixin.js';
|
|
20
|
+
import { RowDetailsMixin } from './vaadin-grid-row-details-mixin.js';
|
|
21
|
+
import { ScrollMixin } from './vaadin-grid-scroll-mixin.js';
|
|
22
|
+
import { SelectionMixin } from './vaadin-grid-selection-mixin.js';
|
|
23
|
+
import { SortMixin } from './vaadin-grid-sort-mixin.js';
|
|
24
|
+
import { StylingMixin } from './vaadin-grid-styling-mixin.js';
|
|
25
|
+
import { DragAndDropMixin } from './vaadin-grid-drag-and-drop-mixin.js';
|
|
26
|
+
import { KeyboardNavigationMixin } from './vaadin-grid-keyboard-navigation-mixin.js';
|
|
27
|
+
import { ColumnReorderingMixin } from './vaadin-grid-column-reordering-mixin.js';
|
|
28
|
+
import './vaadin-grid-column.js';
|
|
29
|
+
import './vaadin-grid-styles.js';
|
|
30
|
+
|
|
31
|
+
const TOUCH_DEVICE = (() => {
|
|
32
|
+
try {
|
|
33
|
+
document.createEvent('TouchEvent');
|
|
34
|
+
return true;
|
|
35
|
+
} catch (e) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
})();
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* `<vaadin-grid>` is a free, high quality data grid / data table Web Component. The content of the
|
|
42
|
+
* the grid can be populated by using renderer callback function.
|
|
43
|
+
*
|
|
44
|
+
* ### Quick Start
|
|
45
|
+
*
|
|
46
|
+
* Start with an assigning an array to the [`items`](#/elements/vaadin-grid#property-items) property to visualize your data.
|
|
47
|
+
*
|
|
48
|
+
* Use the [`<vaadin-grid-column>`](#/elements/vaadin-grid-column) element to configure the grid columns. Set `path` and `header`
|
|
49
|
+
* shorthand properties for the columns to define what gets rendered in the cells of the column.
|
|
50
|
+
*
|
|
51
|
+
* #### Example:
|
|
52
|
+
* ```html
|
|
53
|
+
* <vaadin-grid>
|
|
54
|
+
* <vaadin-grid-column path="name.first" header="First name"></vaadin-grid-column>
|
|
55
|
+
* <vaadin-grid-column path="name.last" header="Last name"></vaadin-grid-column>
|
|
56
|
+
* <vaadin-grid-column path="email"></vaadin-grid-column>
|
|
57
|
+
* </vaadin-grid>
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* For custom content `vaadin-grid-column` element provides you with three types of `renderer` callback functions: `headerRenderer`,
|
|
61
|
+
* `renderer` and `footerRenderer`.
|
|
62
|
+
*
|
|
63
|
+
* Each of those renderer functions provides `root`, `column`, `model` arguments when applicable.
|
|
64
|
+
* Generate DOM content, append it to the `root` element and control the state
|
|
65
|
+
* of the host element by accessing `column`. Before generating new content,
|
|
66
|
+
* users are able to check if there is already content in `root` for reusing it.
|
|
67
|
+
*
|
|
68
|
+
* Renderers are called on initialization of new column cells and each time the
|
|
69
|
+
* related row model is updated. DOM generated during the renderer call can be reused
|
|
70
|
+
* in the next renderer call and will be provided with the `root` argument.
|
|
71
|
+
* On first call it will be empty.
|
|
72
|
+
*
|
|
73
|
+
* #### Example:
|
|
74
|
+
* ```html
|
|
75
|
+
* <vaadin-grid>
|
|
76
|
+
* <vaadin-grid-column></vaadin-grid-column>
|
|
77
|
+
* <vaadin-grid-column></vaadin-grid-column>
|
|
78
|
+
* <vaadin-grid-column></vaadin-grid-column>
|
|
79
|
+
* </vaadin-grid>
|
|
80
|
+
* ```
|
|
81
|
+
* ```js
|
|
82
|
+
* const grid = document.querySelector('vaadin-grid');
|
|
83
|
+
* grid.items = [{'name': 'John', 'surname': 'Lennon', 'role': 'singer'},
|
|
84
|
+
* {'name': 'Ringo', 'surname': 'Starr', 'role': 'drums'}];
|
|
85
|
+
*
|
|
86
|
+
* const columns = grid.querySelectorAll('vaadin-grid-column');
|
|
87
|
+
*
|
|
88
|
+
* columns[0].headerRenderer = function(root) {
|
|
89
|
+
* root.textContent = 'Name';
|
|
90
|
+
* };
|
|
91
|
+
* columns[0].renderer = function(root, column, model) {
|
|
92
|
+
* root.textContent = model.item.name;
|
|
93
|
+
* };
|
|
94
|
+
*
|
|
95
|
+
* columns[1].headerRenderer = function(root) {
|
|
96
|
+
* root.textContent = 'Surname';
|
|
97
|
+
* };
|
|
98
|
+
* columns[1].renderer = function(root, column, model) {
|
|
99
|
+
* root.textContent = model.item.surname;
|
|
100
|
+
* };
|
|
101
|
+
*
|
|
102
|
+
* columns[2].headerRenderer = function(root) {
|
|
103
|
+
* root.textContent = 'Role';
|
|
104
|
+
* };
|
|
105
|
+
* columns[2].renderer = function(root, column, model) {
|
|
106
|
+
* root.textContent = model.item.role;
|
|
107
|
+
* };
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* The following properties are available in the `model` argument:
|
|
111
|
+
*
|
|
112
|
+
* Property name | Type | Description
|
|
113
|
+
* --------------|------|------------
|
|
114
|
+
* `index`| Number | The index of the item.
|
|
115
|
+
* `item` | String or Object | The item.
|
|
116
|
+
* `level` | Number | Number of the item's tree sublevel, starts from 0.
|
|
117
|
+
* `expanded` | Boolean | True if the item's tree sublevel is expanded.
|
|
118
|
+
* `selected` | Boolean | True if the item is selected.
|
|
119
|
+
* `detailsOpened` | Boolean | True if the item's row details are open.
|
|
120
|
+
*
|
|
121
|
+
* The following helper elements can be used for further customization:
|
|
122
|
+
* - [`<vaadin-grid-column-group>`](#/elements/vaadin-grid-column-group)
|
|
123
|
+
* - [`<vaadin-grid-filter>`](#/elements/vaadin-grid-filter)
|
|
124
|
+
* - [`<vaadin-grid-sorter>`](#/elements/vaadin-grid-sorter)
|
|
125
|
+
* - [`<vaadin-grid-selection-column>`](#/elements/vaadin-grid-selection-column)
|
|
126
|
+
* - [`<vaadin-grid-tree-toggle>`](#/elements/vaadin-grid-tree-toggle)
|
|
127
|
+
*
|
|
128
|
+
* __Note that the helper elements must be explicitly imported.__
|
|
129
|
+
* If you want to import everything at once you can use the `all-imports.html` bundle.
|
|
130
|
+
*
|
|
131
|
+
* ### Lazy Loading with Function Data Provider
|
|
132
|
+
*
|
|
133
|
+
* In addition to assigning an array to the items property, you can alternatively
|
|
134
|
+
* provide the `<vaadin-grid>` data through the
|
|
135
|
+
* [`dataProvider`](#/elements/vaadin-grid#property-dataProvider) function property.
|
|
136
|
+
* The `<vaadin-grid>` calls this function lazily, only when it needs more data
|
|
137
|
+
* to be displayed.
|
|
138
|
+
*
|
|
139
|
+
* See the [`dataProvider`](#/elements/vaadin-grid#property-dataProvider) in
|
|
140
|
+
* the API reference below for the detailed data provider arguments description,
|
|
141
|
+
* and the “Assigning Data” page in the demos.
|
|
142
|
+
*
|
|
143
|
+
* __Note that expanding the tree grid's item will trigger a call to the `dataProvider`.__
|
|
144
|
+
*
|
|
145
|
+
* __Also, note that when using function data providers, the total number of items
|
|
146
|
+
* needs to be set manually. The total number of items can be returned
|
|
147
|
+
* in the second argument of the data provider callback:__
|
|
148
|
+
*
|
|
149
|
+
* ```javascript
|
|
150
|
+
* grid.dataProvider = function(params, callback) {
|
|
151
|
+
* const url = 'https://api.example/data' +
|
|
152
|
+
* '?page=' + params.page + // the requested page index
|
|
153
|
+
* '&per_page=' + params.pageSize; // number of items on the page
|
|
154
|
+
* const xhr = new XMLHttpRequest();
|
|
155
|
+
* xhr.onload = function() {
|
|
156
|
+
* const response = JSON.parse(xhr.responseText);
|
|
157
|
+
* callback(
|
|
158
|
+
* response.employees, // requested page of items
|
|
159
|
+
* response.totalSize // total number of items
|
|
160
|
+
* );
|
|
161
|
+
* };
|
|
162
|
+
* xhr.open('GET', url, true);
|
|
163
|
+
* xhr.send();
|
|
164
|
+
* };
|
|
165
|
+
* ```
|
|
166
|
+
*
|
|
167
|
+
* __Alternatively, you can use the `size` property to set the total number of items:__
|
|
168
|
+
*
|
|
169
|
+
* ```javascript
|
|
170
|
+
* grid.size = 200; // The total number of items
|
|
171
|
+
* grid.dataProvider = function(params, callback) {
|
|
172
|
+
* const url = 'https://api.example/data' +
|
|
173
|
+
* '?page=' + params.page + // the requested page index
|
|
174
|
+
* '&per_page=' + params.pageSize; // number of items on the page
|
|
175
|
+
* const xhr = new XMLHttpRequest();
|
|
176
|
+
* xhr.onload = function() {
|
|
177
|
+
* const response = JSON.parse(xhr.responseText);
|
|
178
|
+
* callback(response.employees);
|
|
179
|
+
* };
|
|
180
|
+
* xhr.open('GET', url, true);
|
|
181
|
+
* xhr.send();
|
|
182
|
+
* };
|
|
183
|
+
* ```
|
|
184
|
+
*
|
|
185
|
+
* ### Styling
|
|
186
|
+
*
|
|
187
|
+
* The following shadow DOM parts are available for styling:
|
|
188
|
+
*
|
|
189
|
+
* Part name | Description
|
|
190
|
+
* ----------------|----------------
|
|
191
|
+
* `row` | Row in the internal table
|
|
192
|
+
* `cell` | Cell in the internal table
|
|
193
|
+
* `header-cell` | Header cell in the internal table
|
|
194
|
+
* `body-cell` | Body cell in the internal table
|
|
195
|
+
* `footer-cell` | Footer cell in the internal table
|
|
196
|
+
* `details-cell` | Row details cell in the internal table
|
|
197
|
+
* `resize-handle` | Handle for resizing the columns
|
|
198
|
+
* `reorder-ghost` | Ghost element of the header cell being dragged
|
|
199
|
+
*
|
|
200
|
+
* The following state attributes are available for styling:
|
|
201
|
+
*
|
|
202
|
+
* Attribute | Description | Part name
|
|
203
|
+
* -------------|-------------|------------
|
|
204
|
+
* `loading` | Set when the grid is loading data from data provider | :host
|
|
205
|
+
* `interacting` | Keyboard navigation in interaction mode | :host
|
|
206
|
+
* `navigating` | Keyboard navigation in navigation mode | :host
|
|
207
|
+
* `overflow` | Set when rows are overflowing the grid viewport. Possible values: `top`, `bottom`, `left`, `right` | :host
|
|
208
|
+
* `reordering` | Set when the grid's columns are being reordered | :host
|
|
209
|
+
* `dragover` | Set when the grid (not a specific row) is dragged over | :host
|
|
210
|
+
* `dragging-rows` : Set when grid rows are dragged | :host
|
|
211
|
+
* `reorder-status` | Reflects the status of a cell while columns are being reordered | cell
|
|
212
|
+
* `frozen` | Frozen cell | cell
|
|
213
|
+
* `last-frozen` | Last frozen cell | cell
|
|
214
|
+
* * `first-column` | First visible cell on a row | cell
|
|
215
|
+
* `last-column` | Last visible cell on a row | cell
|
|
216
|
+
* `selected` | Selected row | row
|
|
217
|
+
* `expanded` | Expanded row | row
|
|
218
|
+
* `details-opened` | Row with details open | row
|
|
219
|
+
* `loading` | Row that is waiting for data from data provider | row
|
|
220
|
+
* `odd` | Odd row | row
|
|
221
|
+
* `first` | The first body row | row
|
|
222
|
+
* `dragstart` | Set for one frame when drag of a row is starting. The value is a number when multiple rows are dragged | row
|
|
223
|
+
* `dragover` | Set when the row is dragged over | row
|
|
224
|
+
* `drag-disabled` | Set to a row that isn't available for dragging | row
|
|
225
|
+
* `drop-disabled` | Set to a row that can't be dropped on top of | row
|
|
226
|
+
*
|
|
227
|
+
* See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
|
|
228
|
+
*
|
|
229
|
+
* @fires {CustomEvent} active-item-changed - Fired when the `activeItem` property changes.
|
|
230
|
+
* @fires {CustomEvent} cell-activate - Fired when the cell is activated with click or keyboard.
|
|
231
|
+
* @fires {CustomEvent} cell-focus - Fired when a cell is focused with click or keyboard navigation.
|
|
232
|
+
* @fires {CustomEvent} column-reorder - Fired when the columns in the grid are reordered.
|
|
233
|
+
* @fires {CustomEvent} column-resize - Fired when the grid column resize is finished.
|
|
234
|
+
* @fires {CustomEvent} expanded-items-changed - Fired when the `expandedItems` property changes.
|
|
235
|
+
* @fires {CustomEvent} grid-dragstart - Fired when starting to drag grid rows.
|
|
236
|
+
* @fires {CustomEvent} grid-dragend - Fired when the dragging of the rows ends.
|
|
237
|
+
* @fires {CustomEvent} grid-drop - Fired when a drop occurs on top of the grid.
|
|
238
|
+
* @fires {CustomEvent} loading-changed - Fired when the `loading` property changes.
|
|
239
|
+
* @fires {CustomEvent} selected-items-changed - Fired when the `selectedItems` property changes.
|
|
240
|
+
*
|
|
241
|
+
* @extends HTMLElement
|
|
242
|
+
* @mixes ElementMixin
|
|
243
|
+
* @mixes ThemableMixin
|
|
244
|
+
* @mixes A11yMixin
|
|
245
|
+
* @mixes ActiveItemMixin
|
|
246
|
+
* @mixes ArrayDataProviderMixin
|
|
247
|
+
* @mixes ColumnResizingMixin
|
|
248
|
+
* @mixes DataProviderMixin
|
|
249
|
+
* @mixes DynamicColumnsMixin
|
|
250
|
+
* @mixes FilterMixin
|
|
251
|
+
* @mixes RowDetailsMixin
|
|
252
|
+
* @mixes ScrollMixin
|
|
253
|
+
* @mixes SelectionMixin
|
|
254
|
+
* @mixes SortMixin
|
|
255
|
+
* @mixes KeyboardNavigationMixin
|
|
256
|
+
* @mixes ColumnReorderingMixin
|
|
257
|
+
* @mixes EventContextMixin
|
|
258
|
+
* @mixes StylingMixin
|
|
259
|
+
* @mixes DragAndDropMixin
|
|
260
|
+
*/
|
|
261
|
+
class Grid extends ElementMixin(
|
|
262
|
+
ThemableMixin(
|
|
263
|
+
DataProviderMixin(
|
|
264
|
+
ArrayDataProviderMixin(
|
|
265
|
+
DynamicColumnsMixin(
|
|
266
|
+
ActiveItemMixin(
|
|
267
|
+
ScrollMixin(
|
|
268
|
+
SelectionMixin(
|
|
269
|
+
SortMixin(
|
|
270
|
+
RowDetailsMixin(
|
|
271
|
+
KeyboardNavigationMixin(
|
|
272
|
+
A11yMixin(
|
|
273
|
+
FilterMixin(
|
|
274
|
+
ColumnReorderingMixin(
|
|
275
|
+
ColumnResizingMixin(EventContextMixin(DragAndDropMixin(StylingMixin(PolymerElement))))
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
)
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
)
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
) {
|
|
290
|
+
static get template() {
|
|
291
|
+
return html`
|
|
292
|
+
<div
|
|
293
|
+
id="scroller"
|
|
294
|
+
safari$="[[_safari]]"
|
|
295
|
+
ios$="[[_ios]]"
|
|
296
|
+
loading$="[[loading]]"
|
|
297
|
+
column-reordering-allowed$="[[columnReorderingAllowed]]"
|
|
298
|
+
>
|
|
299
|
+
<table id="table" role="grid" aria-multiselectable="true" tabindex="0">
|
|
300
|
+
<caption id="sizer" part="row"></caption>
|
|
301
|
+
<thead id="header" role="rowgroup"></thead>
|
|
302
|
+
<tbody id="items" role="rowgroup"></tbody>
|
|
303
|
+
<tfoot id="footer" role="rowgroup"></tfoot>
|
|
304
|
+
</table>
|
|
305
|
+
|
|
306
|
+
<div part="reorder-ghost"></div>
|
|
307
|
+
</div>
|
|
308
|
+
|
|
309
|
+
<div id="focusexit" tabindex="0"></div>
|
|
310
|
+
`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
static get is() {
|
|
314
|
+
return 'vaadin-grid';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
static get observers() {
|
|
318
|
+
return [
|
|
319
|
+
'_columnTreeChanged(_columnTree, _columnTree.*)',
|
|
320
|
+
'_effectiveSizeChanged(_effectiveSize, __virtualizer, _hasData, _columnTree)'
|
|
321
|
+
];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
static get properties() {
|
|
325
|
+
return {
|
|
326
|
+
/** @private */
|
|
327
|
+
_safari: {
|
|
328
|
+
type: Boolean,
|
|
329
|
+
value: /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
/** @private */
|
|
333
|
+
_ios: {
|
|
334
|
+
type: Boolean,
|
|
335
|
+
value:
|
|
336
|
+
(/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) ||
|
|
337
|
+
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
/** @private */
|
|
341
|
+
_firefox: {
|
|
342
|
+
type: Boolean,
|
|
343
|
+
value: navigator.userAgent.toLowerCase().indexOf('firefox') > -1
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
/** @private */
|
|
347
|
+
_android: {
|
|
348
|
+
type: Boolean,
|
|
349
|
+
value: /android/i.test(navigator.userAgent)
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
/** @private */
|
|
353
|
+
_touchDevice: {
|
|
354
|
+
type: Boolean,
|
|
355
|
+
value: TOUCH_DEVICE
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* If true, the grid's height is defined by its rows.
|
|
360
|
+
*
|
|
361
|
+
* Effectively, this disables the grid's virtual scrolling so that all the rows are rendered in the DOM at once.
|
|
362
|
+
* If the grid has a large number of items, using the feature is discouraged to avoid performance issues.
|
|
363
|
+
* @attr {boolean} all-rows-visible
|
|
364
|
+
* @type {boolean}
|
|
365
|
+
*/
|
|
366
|
+
allRowsVisible: {
|
|
367
|
+
type: Boolean,
|
|
368
|
+
value: false,
|
|
369
|
+
reflectToAttribute: true
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
/** @private */
|
|
373
|
+
_recalculateColumnWidthOnceLoadingFinished: {
|
|
374
|
+
type: Boolean,
|
|
375
|
+
value: true
|
|
376
|
+
},
|
|
377
|
+
|
|
378
|
+
/** @private */
|
|
379
|
+
isAttached: {
|
|
380
|
+
value: false
|
|
381
|
+
},
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* An internal property that is mainly used by `vaadin-template-renderer`
|
|
385
|
+
* to identify grid elements.
|
|
386
|
+
*
|
|
387
|
+
* @private
|
|
388
|
+
*/
|
|
389
|
+
__gridElement: {
|
|
390
|
+
type: Boolean,
|
|
391
|
+
value: true
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
constructor() {
|
|
397
|
+
super();
|
|
398
|
+
this.addEventListener('animationend', this._onAnimationEnd);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** @protected */
|
|
402
|
+
connectedCallback() {
|
|
403
|
+
super.connectedCallback();
|
|
404
|
+
this.isAttached = true;
|
|
405
|
+
this.recalculateColumnWidths();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** @protected */
|
|
409
|
+
disconnectedCallback() {
|
|
410
|
+
super.disconnectedCallback();
|
|
411
|
+
this.isAttached = false;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** @private */
|
|
415
|
+
__getFirstVisibleItem() {
|
|
416
|
+
return this._getVisibleRows().find((row) => this._isInViewport(row));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** @private */
|
|
420
|
+
get _firstVisibleIndex() {
|
|
421
|
+
const firstVisibleItem = this.__getFirstVisibleItem();
|
|
422
|
+
return firstVisibleItem ? firstVisibleItem.index : undefined;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/** @private */
|
|
426
|
+
__getLastVisibleItem() {
|
|
427
|
+
return this._getVisibleRows()
|
|
428
|
+
.reverse()
|
|
429
|
+
.find((row) => this._isInViewport(row));
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** @private */
|
|
433
|
+
get _lastVisibleIndex() {
|
|
434
|
+
const lastVisibleItem = this.__getLastVisibleItem();
|
|
435
|
+
return lastVisibleItem ? lastVisibleItem.index : undefined;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** @private */
|
|
439
|
+
_isInViewport(item) {
|
|
440
|
+
const scrollTargetRect = this.$.table.getBoundingClientRect();
|
|
441
|
+
const itemRect = item.getBoundingClientRect();
|
|
442
|
+
const headerHeight = this.$.header.getBoundingClientRect().height;
|
|
443
|
+
const footerHeight = this.$.footer.getBoundingClientRect().height;
|
|
444
|
+
return (
|
|
445
|
+
itemRect.bottom > scrollTargetRect.top + headerHeight && itemRect.top < scrollTargetRect.bottom - footerHeight
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** @private */
|
|
450
|
+
_getVisibleRows() {
|
|
451
|
+
return Array.from(this.$.items.children)
|
|
452
|
+
.filter((item) => !item.hidden)
|
|
453
|
+
.sort((a, b) => a.index - b.index);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** @protected */
|
|
457
|
+
ready() {
|
|
458
|
+
super.ready();
|
|
459
|
+
|
|
460
|
+
this.__virtualizer = new Virtualizer({
|
|
461
|
+
createElements: this._createScrollerRows.bind(this),
|
|
462
|
+
updateElement: this._updateScrollerItem.bind(this),
|
|
463
|
+
scrollContainer: this.$.items,
|
|
464
|
+
scrollTarget: this.$.table,
|
|
465
|
+
reorderElements: true
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
new ResizeObserver(() => setTimeout(() => this.__updateFooterPositioning())).observe(this.$.footer);
|
|
469
|
+
|
|
470
|
+
processTemplates(this);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* @param {string} name
|
|
475
|
+
* @param {?string} oldValue
|
|
476
|
+
* @param {?string} newValue
|
|
477
|
+
* @protected
|
|
478
|
+
*/
|
|
479
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
480
|
+
super.attributeChangedCallback(name, oldValue, newValue);
|
|
481
|
+
if (name === 'dir') {
|
|
482
|
+
this.__isRTL = newValue === 'rtl';
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/** @private */
|
|
487
|
+
__getBodyCellCoordinates(cell) {
|
|
488
|
+
if (this.$.items.contains(cell) && cell.localName === 'td') {
|
|
489
|
+
return {
|
|
490
|
+
item: cell.parentElement._item,
|
|
491
|
+
column: cell._column
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/** @private */
|
|
497
|
+
__focusBodyCell({ item, column }) {
|
|
498
|
+
const row = this._getVisibleRows().find((row) => row._item === item);
|
|
499
|
+
const cell = row && [...row.children].find((cell) => cell._column === column);
|
|
500
|
+
cell && cell.focus();
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** @private */
|
|
504
|
+
_effectiveSizeChanged(effectiveSize, virtualizer, hasData, columnTree) {
|
|
505
|
+
if (virtualizer && hasData && columnTree) {
|
|
506
|
+
// Changing the virtualizer size may result in the row with focus getting hidden
|
|
507
|
+
const cell = this.shadowRoot.activeElement;
|
|
508
|
+
const cellCoordinates = this.__getBodyCellCoordinates(cell);
|
|
509
|
+
|
|
510
|
+
virtualizer.size = effectiveSize;
|
|
511
|
+
virtualizer.flush();
|
|
512
|
+
|
|
513
|
+
// If the focused cell's parent row got hidden by the size change, focus the corresponding new cell
|
|
514
|
+
cellCoordinates && cell.parentElement.hidden && this.__focusBodyCell(cellCoordinates);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/** @private */
|
|
519
|
+
__hasRowsWithClientHeight() {
|
|
520
|
+
return !!Array.from(this.$.items.children).filter((row) => row.clientHeight).length;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/** @protected */
|
|
524
|
+
__itemsReceived() {
|
|
525
|
+
if (
|
|
526
|
+
this._recalculateColumnWidthOnceLoadingFinished &&
|
|
527
|
+
!this._cache.isLoading() &&
|
|
528
|
+
this.__hasRowsWithClientHeight()
|
|
529
|
+
) {
|
|
530
|
+
this._recalculateColumnWidthOnceLoadingFinished = false;
|
|
531
|
+
this.recalculateColumnWidths();
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* @param {!Array<!GridColumn>} cols the columns to auto size based on their content width
|
|
537
|
+
* @private
|
|
538
|
+
*/
|
|
539
|
+
_recalculateColumnWidths(cols) {
|
|
540
|
+
// Note: The `cols.forEach()` loops below could be implemented as a single loop but this has been
|
|
541
|
+
// split for performance reasons to batch these similar actions [write/read] together to avoid
|
|
542
|
+
// unnecessary layout trashing.
|
|
543
|
+
|
|
544
|
+
// [write] Set automatic width for all cells (breaks column alignment)
|
|
545
|
+
cols.forEach((col) => {
|
|
546
|
+
col.width = 'auto';
|
|
547
|
+
col._origFlexGrow = col.flexGrow;
|
|
548
|
+
col.flexGrow = 0;
|
|
549
|
+
});
|
|
550
|
+
// [read] Measure max cell width in each column
|
|
551
|
+
cols.forEach((col) => {
|
|
552
|
+
col._currentWidth = 0;
|
|
553
|
+
// Note: _allCells only contains cells which are currently rendered in DOM
|
|
554
|
+
col._allCells
|
|
555
|
+
.filter((c) => {
|
|
556
|
+
// Exclude body cells that are out of the visible viewport
|
|
557
|
+
return !this.$.items.contains(c) || this._isInViewport(c.parentElement);
|
|
558
|
+
})
|
|
559
|
+
.forEach((c) => {
|
|
560
|
+
// Add 1px buffer to the offset width to avoid too narrow columns (sub-pixel rendering)
|
|
561
|
+
const cellWidth = c.offsetWidth + 1;
|
|
562
|
+
col._currentWidth = Math.max(col._currentWidth, cellWidth);
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
// [write] Set column widths to fit widest measured content
|
|
566
|
+
cols.forEach((col) => {
|
|
567
|
+
col.width = `${col._currentWidth}px`;
|
|
568
|
+
col.flexGrow = col._origFlexGrow;
|
|
569
|
+
col._currentWidth = undefined;
|
|
570
|
+
col._origFlexGrow = undefined;
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Updates the `width` of all columns which have `autoWidth` set to `true`.
|
|
576
|
+
*/
|
|
577
|
+
recalculateColumnWidths() {
|
|
578
|
+
if (!this._columnTree) {
|
|
579
|
+
return; // No columns
|
|
580
|
+
}
|
|
581
|
+
if (this._cache.isLoading()) {
|
|
582
|
+
this._recalculateColumnWidthOnceLoadingFinished = true;
|
|
583
|
+
} else {
|
|
584
|
+
const cols = this._getColumns().filter((col) => !col.hidden && col.autoWidth);
|
|
585
|
+
this._recalculateColumnWidths(cols);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/** @private */
|
|
590
|
+
_createScrollerRows(count) {
|
|
591
|
+
const rows = [];
|
|
592
|
+
for (let i = 0; i < count; i++) {
|
|
593
|
+
const row = document.createElement('tr');
|
|
594
|
+
row.setAttribute('part', 'row');
|
|
595
|
+
row.setAttribute('role', 'row');
|
|
596
|
+
row.setAttribute('tabindex', '-1');
|
|
597
|
+
if (this._columnTree) {
|
|
598
|
+
this._updateRow(row, this._columnTree[this._columnTree.length - 1], 'body', false, true);
|
|
599
|
+
}
|
|
600
|
+
rows.push(row);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (this._columnTree) {
|
|
604
|
+
this._columnTree[this._columnTree.length - 1].forEach(
|
|
605
|
+
(c) => c.isConnected && c.notifyPath && c.notifyPath('_cells.*', c._cells)
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
beforeNextRender(this, () => {
|
|
610
|
+
this._updateFirstAndLastColumn();
|
|
611
|
+
this._resetKeyboardNavigation();
|
|
612
|
+
this._afterScroll();
|
|
613
|
+
this.__itemsReceived();
|
|
614
|
+
});
|
|
615
|
+
return rows;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/** @private */
|
|
619
|
+
_createCell(tagName) {
|
|
620
|
+
const contentId = (this._contentIndex = this._contentIndex + 1 || 0);
|
|
621
|
+
const slotName = 'vaadin-grid-cell-content-' + contentId;
|
|
622
|
+
|
|
623
|
+
const cellContent = document.createElement('vaadin-grid-cell-content');
|
|
624
|
+
cellContent.setAttribute('slot', slotName);
|
|
625
|
+
|
|
626
|
+
const cell = document.createElement(tagName);
|
|
627
|
+
cell.id = slotName.replace('-content-', '-');
|
|
628
|
+
cell.setAttribute('tabindex', '-1');
|
|
629
|
+
cell.setAttribute('role', tagName === 'td' ? 'gridcell' : 'columnheader');
|
|
630
|
+
|
|
631
|
+
const slot = document.createElement('slot');
|
|
632
|
+
slot.setAttribute('name', slotName);
|
|
633
|
+
|
|
634
|
+
cell.appendChild(slot);
|
|
635
|
+
|
|
636
|
+
cell._content = cellContent;
|
|
637
|
+
|
|
638
|
+
// With native Shadow DOM, mousedown on slotted element does not focus
|
|
639
|
+
// focusable slot wrapper, that is why cells are not focused with
|
|
640
|
+
// mousedown. Workaround: listen for mousedown and focus manually.
|
|
641
|
+
cellContent.addEventListener('mousedown', () => {
|
|
642
|
+
if (window.chrome) {
|
|
643
|
+
// Chrome bug: focusing before mouseup prevents text selection, see http://crbug.com/771903
|
|
644
|
+
const mouseUpListener = () => {
|
|
645
|
+
if (!cellContent.contains(this.getRootNode().activeElement)) {
|
|
646
|
+
cell.focus();
|
|
647
|
+
}
|
|
648
|
+
// If focus is in the cell content — respect it, do not change.
|
|
649
|
+
document.removeEventListener('mouseup', mouseUpListener, true);
|
|
650
|
+
};
|
|
651
|
+
document.addEventListener('mouseup', mouseUpListener, true);
|
|
652
|
+
} else {
|
|
653
|
+
// Focus on mouseup, on the other hand, removes selection on Safari.
|
|
654
|
+
// Watch out sync focus removal issue, only async focus works here.
|
|
655
|
+
setTimeout(() => {
|
|
656
|
+
if (!cellContent.contains(this.getRootNode().activeElement)) {
|
|
657
|
+
cell.focus();
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
return cell;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* @param {!HTMLTableRowElement} row
|
|
668
|
+
* @param {!Array<!GridColumn>} columns
|
|
669
|
+
* @param {?string} section
|
|
670
|
+
* @param {boolean} isColumnRow
|
|
671
|
+
* @param {boolean} noNotify
|
|
672
|
+
* @protected
|
|
673
|
+
*/
|
|
674
|
+
_updateRow(row, columns, section, isColumnRow, noNotify) {
|
|
675
|
+
section = section || 'body';
|
|
676
|
+
|
|
677
|
+
const contentsFragment = document.createDocumentFragment();
|
|
678
|
+
|
|
679
|
+
Array.from(row.children).forEach((cell) => (cell._vacant = true));
|
|
680
|
+
row.innerHTML = '';
|
|
681
|
+
|
|
682
|
+
columns
|
|
683
|
+
.filter((column) => !column.hidden)
|
|
684
|
+
.forEach((column, index, cols) => {
|
|
685
|
+
let cell;
|
|
686
|
+
|
|
687
|
+
if (section === 'body') {
|
|
688
|
+
// Body
|
|
689
|
+
column._cells = column._cells || [];
|
|
690
|
+
cell = column._cells.filter((cell) => cell._vacant)[0];
|
|
691
|
+
if (!cell) {
|
|
692
|
+
cell = this._createCell('td');
|
|
693
|
+
column._cells.push(cell);
|
|
694
|
+
}
|
|
695
|
+
cell.setAttribute('part', 'cell body-cell');
|
|
696
|
+
row.appendChild(cell);
|
|
697
|
+
|
|
698
|
+
if (index === cols.length - 1 && this.rowDetailsRenderer) {
|
|
699
|
+
// Add details cell as last cell to body rows
|
|
700
|
+
this._detailsCells = this._detailsCells || [];
|
|
701
|
+
const detailsCell = this._detailsCells.filter((cell) => cell._vacant)[0] || this._createCell('td');
|
|
702
|
+
if (this._detailsCells.indexOf(detailsCell) === -1) {
|
|
703
|
+
this._detailsCells.push(detailsCell);
|
|
704
|
+
}
|
|
705
|
+
if (!detailsCell._content.parentElement) {
|
|
706
|
+
contentsFragment.appendChild(detailsCell._content);
|
|
707
|
+
}
|
|
708
|
+
this._configureDetailsCell(detailsCell);
|
|
709
|
+
row.appendChild(detailsCell);
|
|
710
|
+
this._a11ySetRowDetailsCell(row, detailsCell);
|
|
711
|
+
detailsCell._vacant = false;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (column.notifyPath && !noNotify) {
|
|
715
|
+
column.notifyPath('_cells.*', column._cells);
|
|
716
|
+
}
|
|
717
|
+
} else {
|
|
718
|
+
// Header & footer
|
|
719
|
+
const tagName = section === 'header' ? 'th' : 'td';
|
|
720
|
+
if (isColumnRow || column.localName === 'vaadin-grid-column-group') {
|
|
721
|
+
cell = column[`_${section}Cell`] || this._createCell(tagName);
|
|
722
|
+
cell._column = column;
|
|
723
|
+
row.appendChild(cell);
|
|
724
|
+
column[`_${section}Cell`] = cell;
|
|
725
|
+
} else {
|
|
726
|
+
column._emptyCells = column._emptyCells || [];
|
|
727
|
+
cell = column._emptyCells.filter((cell) => cell._vacant)[0] || this._createCell(tagName);
|
|
728
|
+
cell._column = column;
|
|
729
|
+
row.appendChild(cell);
|
|
730
|
+
if (column._emptyCells.indexOf(cell) === -1) {
|
|
731
|
+
column._emptyCells.push(cell);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
cell.setAttribute('part', `cell ${section}-cell`);
|
|
735
|
+
this.__updateHeaderFooterRowVisibility(row);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (!cell._content.parentElement) {
|
|
739
|
+
contentsFragment.appendChild(cell._content);
|
|
740
|
+
}
|
|
741
|
+
cell._vacant = false;
|
|
742
|
+
cell._column = column;
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
// Might be empty if only cache was used
|
|
746
|
+
this.appendChild(contentsFragment);
|
|
747
|
+
|
|
748
|
+
this._frozenCellsChanged();
|
|
749
|
+
this._updateFirstAndLastColumnForRow(row);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* @param {HTMLTableRowElement} row
|
|
754
|
+
* @protected
|
|
755
|
+
*/
|
|
756
|
+
__updateHeaderFooterRowVisibility(row) {
|
|
757
|
+
if (!row) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const visibleRowCells = Array.from(row.children).filter((cell) => {
|
|
762
|
+
const column = cell._column;
|
|
763
|
+
if (column._emptyCells && column._emptyCells.indexOf(cell) > -1) {
|
|
764
|
+
// The cell is an "empty cell" -> doesn't block hiding the row
|
|
765
|
+
return false;
|
|
766
|
+
}
|
|
767
|
+
if (row.parentElement === this.$.header) {
|
|
768
|
+
if (column.headerRenderer) {
|
|
769
|
+
// The cell is the header cell of a column that has a header renderer
|
|
770
|
+
// -> row should be visible
|
|
771
|
+
return true;
|
|
772
|
+
}
|
|
773
|
+
if (column.header === null) {
|
|
774
|
+
// The column header is explicilty set to null -> doesn't block hiding the row
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
777
|
+
if (column.path || column.header !== undefined) {
|
|
778
|
+
// The column has an explicit non-null header or a path that generates a header
|
|
779
|
+
// -> row should be visible
|
|
780
|
+
return true;
|
|
781
|
+
}
|
|
782
|
+
} else {
|
|
783
|
+
if (column.footerRenderer) {
|
|
784
|
+
// The cell is the footer cell of a column that has a footer renderer
|
|
785
|
+
// -> row should be visible
|
|
786
|
+
return true;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
if (row.hidden !== !visibleRowCells.length) {
|
|
792
|
+
row.hidden = !visibleRowCells.length;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/** @private */
|
|
797
|
+
_updateScrollerItem(row, index) {
|
|
798
|
+
this._preventScrollerRotatingCellFocus(row, index);
|
|
799
|
+
|
|
800
|
+
if (!this._columnTree) {
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
row.toggleAttribute('first', index === 0);
|
|
805
|
+
row.toggleAttribute('odd', index % 2);
|
|
806
|
+
this._a11yUpdateRowRowindex(row, index);
|
|
807
|
+
this._getItem(index, row);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/** @private */
|
|
811
|
+
_columnTreeChanged(columnTree) {
|
|
812
|
+
this._renderColumnTree(columnTree);
|
|
813
|
+
this.recalculateColumnWidths();
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* @param {!Array<!GridColumn>} columnTree
|
|
818
|
+
* @protected
|
|
819
|
+
*/
|
|
820
|
+
_renderColumnTree(columnTree) {
|
|
821
|
+
Array.from(this.$.items.children).forEach((row) =>
|
|
822
|
+
this._updateRow(row, columnTree[columnTree.length - 1], null, false, true)
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
while (this.$.header.children.length < columnTree.length) {
|
|
826
|
+
const headerRow = document.createElement('tr');
|
|
827
|
+
headerRow.setAttribute('part', 'row');
|
|
828
|
+
headerRow.setAttribute('role', 'row');
|
|
829
|
+
headerRow.setAttribute('tabindex', '-1');
|
|
830
|
+
this.$.header.appendChild(headerRow);
|
|
831
|
+
|
|
832
|
+
const footerRow = document.createElement('tr');
|
|
833
|
+
footerRow.setAttribute('part', 'row');
|
|
834
|
+
footerRow.setAttribute('role', 'row');
|
|
835
|
+
footerRow.setAttribute('tabindex', '-1');
|
|
836
|
+
this.$.footer.appendChild(footerRow);
|
|
837
|
+
}
|
|
838
|
+
while (this.$.header.children.length > columnTree.length) {
|
|
839
|
+
this.$.header.removeChild(this.$.header.firstElementChild);
|
|
840
|
+
this.$.footer.removeChild(this.$.footer.firstElementChild);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
Array.from(this.$.header.children).forEach((headerRow, index) =>
|
|
844
|
+
this._updateRow(headerRow, columnTree[index], 'header', index === columnTree.length - 1)
|
|
845
|
+
);
|
|
846
|
+
|
|
847
|
+
Array.from(this.$.footer.children).forEach((footerRow, index) =>
|
|
848
|
+
this._updateRow(footerRow, columnTree[columnTree.length - 1 - index], 'footer', index === 0)
|
|
849
|
+
);
|
|
850
|
+
|
|
851
|
+
// Sizer rows
|
|
852
|
+
this._updateRow(this.$.sizer, columnTree[columnTree.length - 1]);
|
|
853
|
+
|
|
854
|
+
this._resizeHandler();
|
|
855
|
+
this._frozenCellsChanged();
|
|
856
|
+
this._updateFirstAndLastColumn();
|
|
857
|
+
this._resetKeyboardNavigation();
|
|
858
|
+
this._a11yUpdateHeaderRows();
|
|
859
|
+
this._a11yUpdateFooterRows();
|
|
860
|
+
this.__updateFooterPositioning();
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
__updateFooterPositioning() {
|
|
864
|
+
if (this._firefox) {
|
|
865
|
+
// Sticky (or translated) footer in a flexbox host doesn't get included in
|
|
866
|
+
// the scroll height calculation on FF. This is a workaround for the issue.
|
|
867
|
+
this.$.items.style.paddingBottom = 0;
|
|
868
|
+
if (!this.allRowsVisible) {
|
|
869
|
+
this.$.items.style.paddingBottom = `${this.$.footer.offsetHeight}px`;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* @param {!HTMLElement} row
|
|
876
|
+
* @param {GridItem} item
|
|
877
|
+
* @protected
|
|
878
|
+
*/
|
|
879
|
+
_updateItem(row, item) {
|
|
880
|
+
row._item = item;
|
|
881
|
+
const model = this.__getRowModel(row);
|
|
882
|
+
|
|
883
|
+
this._toggleDetailsCell(row, model.detailsOpened);
|
|
884
|
+
|
|
885
|
+
this._a11yUpdateRowLevel(row, model.level);
|
|
886
|
+
this._a11yUpdateRowSelected(row, model.selected);
|
|
887
|
+
this._a11yUpdateRowExpanded(row, model.expanded);
|
|
888
|
+
this._a11yUpdateRowDetailsOpened(row, model.detailsOpened);
|
|
889
|
+
|
|
890
|
+
row.toggleAttribute('expanded', model.expanded);
|
|
891
|
+
row.toggleAttribute('selected', model.selected);
|
|
892
|
+
row.toggleAttribute('details-opened', model.detailsOpened);
|
|
893
|
+
|
|
894
|
+
this._generateCellClassNames(row, model);
|
|
895
|
+
this._filterDragAndDrop(row, model);
|
|
896
|
+
|
|
897
|
+
Array.from(row.children).forEach((cell) => {
|
|
898
|
+
if (cell._renderer) {
|
|
899
|
+
const owner = cell._column || this;
|
|
900
|
+
cell._renderer.call(owner, cell._content, owner, model);
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
this._updateDetailsCellHeight(row);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/** @private */
|
|
908
|
+
_resizeHandler() {
|
|
909
|
+
this._updateDetailsCellHeights();
|
|
910
|
+
this.__updateFooterPositioning();
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/** @private */
|
|
914
|
+
_onAnimationEnd(e) {
|
|
915
|
+
// ShadyCSS applies scoping suffixes to animation names
|
|
916
|
+
if (e.animationName.indexOf('vaadin-grid-appear') === 0) {
|
|
917
|
+
e.stopPropagation();
|
|
918
|
+
this.__itemsReceived();
|
|
919
|
+
|
|
920
|
+
requestAnimationFrame(() => {
|
|
921
|
+
this.__scrollToPendingIndex();
|
|
922
|
+
// This needs to be set programmatically in order to avoid an iOS 10 bug (disappearing grid)
|
|
923
|
+
this.$.table.style.webkitOverflowScrolling = 'touch';
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* @param {!HTMLTableRowElement} row
|
|
930
|
+
* @return {!GridItemModel}
|
|
931
|
+
* @protected
|
|
932
|
+
*/
|
|
933
|
+
__getRowModel(row) {
|
|
934
|
+
return {
|
|
935
|
+
index: row.index,
|
|
936
|
+
item: row._item,
|
|
937
|
+
level: this._getIndexLevel(row.index),
|
|
938
|
+
expanded: this._isExpanded(row._item),
|
|
939
|
+
selected: this._isSelected(row._item),
|
|
940
|
+
detailsOpened: !!this.rowDetailsRenderer && this._isDetailsOpened(row._item)
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/**
|
|
945
|
+
* Requests an update for the content of cells.
|
|
946
|
+
*
|
|
947
|
+
* While performing the update, the following renderers are invoked:
|
|
948
|
+
* - `Grid.rowDetailsRenderer`
|
|
949
|
+
* - `GridColumn.renderer`
|
|
950
|
+
* - `GridColumn.headerRenderer`
|
|
951
|
+
* - `GridColumn.footerRenderer`
|
|
952
|
+
*
|
|
953
|
+
* It is not guaranteed that the update happens immediately (synchronously) after it is requested.
|
|
954
|
+
*/
|
|
955
|
+
requestContentUpdate() {
|
|
956
|
+
if (this._columnTree) {
|
|
957
|
+
// header and footer renderers
|
|
958
|
+
this._columnTree.forEach((level) => {
|
|
959
|
+
level.forEach((column) => {
|
|
960
|
+
column._renderHeaderAndFooter();
|
|
961
|
+
});
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
// body and row details renderers
|
|
965
|
+
this.__updateVisibleRows();
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Manually invoke existing renderers for all the columns
|
|
971
|
+
* (header, footer and body cells) and opened row details.
|
|
972
|
+
*
|
|
973
|
+
* @deprecated Since Vaadin 21, `render()` is deprecated. Please use `requestContentUpdate()` instead.
|
|
974
|
+
*/
|
|
975
|
+
render() {
|
|
976
|
+
console.warn('WARNING: Since Vaadin 21, render() is deprecated. Please use requestContentUpdate() instead.');
|
|
977
|
+
|
|
978
|
+
this.requestContentUpdate();
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/** @protected */
|
|
982
|
+
__updateVisibleRows(start, end) {
|
|
983
|
+
this.__virtualizer && this.__virtualizer.update(start, end);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Updates the computed metrics and positioning of internal grid parts
|
|
988
|
+
* (row/details cell positioning etc). Needs to be invoked whenever the sizing of grid
|
|
989
|
+
* content changes asynchronously to ensure consistent appearance (e.g. when a
|
|
990
|
+
* contained image whose bounds aren't known beforehand finishes loading).
|
|
991
|
+
*
|
|
992
|
+
* @deprecated Since Vaadin 22, `notifyResize()` is deprecated. The component uses a
|
|
993
|
+
* ResizeObserver internally and doesn't need to be explicitly notified of resizes.
|
|
994
|
+
*/
|
|
995
|
+
notifyResize() {
|
|
996
|
+
console.warn(
|
|
997
|
+
`WARNING: Since Vaadin 22, notifyResize() is deprecated. The component uses a ResizeObserver internally and doesn't need to be explicitly notified of resizes.`
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
customElements.define(Grid.is, Grid);
|
|
1003
|
+
|
|
1004
|
+
export { Grid };
|