@vaadin/grid 24.2.0-dev.e9803eea7 → 24.2.0-rc1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/grid",
3
- "version": "24.2.0-dev.e9803eea7",
3
+ "version": "24.2.0-rc1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -46,18 +46,18 @@
46
46
  "dependencies": {
47
47
  "@open-wc/dedupe-mixin": "^1.3.0",
48
48
  "@polymer/polymer": "^3.0.0",
49
- "@vaadin/a11y-base": "24.2.0-dev.e9803eea7",
50
- "@vaadin/checkbox": "24.2.0-dev.e9803eea7",
51
- "@vaadin/component-base": "24.2.0-dev.e9803eea7",
52
- "@vaadin/lit-renderer": "24.2.0-dev.e9803eea7",
53
- "@vaadin/text-field": "24.2.0-dev.e9803eea7",
54
- "@vaadin/vaadin-lumo-styles": "24.2.0-dev.e9803eea7",
55
- "@vaadin/vaadin-material-styles": "24.2.0-dev.e9803eea7",
56
- "@vaadin/vaadin-themable-mixin": "24.2.0-dev.e9803eea7"
49
+ "@vaadin/a11y-base": "24.2.0-rc1",
50
+ "@vaadin/checkbox": "24.2.0-rc1",
51
+ "@vaadin/component-base": "24.2.0-rc1",
52
+ "@vaadin/lit-renderer": "24.2.0-rc1",
53
+ "@vaadin/text-field": "24.2.0-rc1",
54
+ "@vaadin/vaadin-lumo-styles": "24.2.0-rc1",
55
+ "@vaadin/vaadin-material-styles": "24.2.0-rc1",
56
+ "@vaadin/vaadin-themable-mixin": "24.2.0-rc1"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@esm-bundle/chai": "^4.3.4",
60
- "@vaadin/testing-helpers": "^0.4.3",
60
+ "@vaadin/testing-helpers": "^0.5.0",
61
61
  "lit": "^2.0.0",
62
62
  "sinon": "^13.0.2"
63
63
  },
@@ -65,5 +65,5 @@
65
65
  "web-types.json",
66
66
  "web-types.lit.json"
67
67
  ],
68
- "gitHead": "a065b79b9d5a189e457fab312cc8aff0d7f2f910"
68
+ "gitHead": "012bef350bbf29865748f4c78338dd17c6f61a74"
69
69
  }
@@ -7,6 +7,7 @@ import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nod
7
7
  import { PolymerElement } from '@polymer/polymer/polymer-element.js';
8
8
  import { animationFrame } from '@vaadin/component-base/src/async.js';
9
9
  import { Debouncer } from '@vaadin/component-base/src/debounce.js';
10
+ import { defineCustomElement } from '@vaadin/component-base/src/define.js';
10
11
  import { ColumnBaseMixin } from './vaadin-grid-column.js';
11
12
  import { updateColumnOrders } from './vaadin-grid-helpers.js';
12
13
 
@@ -39,6 +40,7 @@ import { updateColumnOrders } from './vaadin-grid-helpers.js';
39
40
  * column2.renderer = (root, column, model) => { ... };
40
41
  * ```
41
42
  *
43
+ * @customElement
42
44
  * @extends HTMLElement
43
45
  * @mixes ColumnBaseMixin
44
46
  */
@@ -396,6 +398,6 @@ class GridColumnGroup extends ColumnBaseMixin(PolymerElement) {
396
398
  }
397
399
  }
398
400
 
399
- customElements.define(GridColumnGroup.is, GridColumnGroup);
401
+ defineCustomElement(GridColumnGroup);
400
402
 
401
403
  export { GridColumnGroup };
@@ -6,7 +6,9 @@
6
6
  import { PolymerElement } from '@polymer/polymer/polymer-element.js';
7
7
  import { animationFrame } from '@vaadin/component-base/src/async.js';
8
8
  import { Debouncer } from '@vaadin/component-base/src/debounce.js';
9
+ import { defineCustomElement } from '@vaadin/component-base/src/define.js';
9
10
  import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
11
+ import { get } from '@vaadin/component-base/src/path-utils.js';
10
12
  import { processTemplates } from '@vaadin/component-base/src/templates.js';
11
13
  import { updateCellState } from './vaadin-grid-helpers.js';
12
14
 
@@ -713,7 +715,7 @@ export const ColumnBaseMixin = (superClass) =>
713
715
  return;
714
716
  }
715
717
 
716
- this.__setTextContent(root, this.get(this.path, item));
718
+ this.__setTextContent(root, get(this.path, item));
717
719
  }
718
720
 
719
721
  /**
@@ -783,6 +785,7 @@ export const ColumnBaseMixin = (superClass) =>
783
785
  * See [`<vaadin-grid>`](#/elements/vaadin-grid) documentation for instructions on how
784
786
  * to configure the `<vaadin-grid-column>`.
785
787
  *
788
+ * @customElement
786
789
  * @extends HTMLElement
787
790
  * @mixes ColumnBaseMixin
788
791
  */
@@ -894,6 +897,6 @@ class GridColumn extends ColumnBaseMixin(DirMixin(PolymerElement)) {
894
897
  }
895
898
  }
896
899
 
897
- customElements.define(GridColumn.is, GridColumn);
900
+ defineCustomElement(GridColumn);
898
901
 
899
902
  export { GridColumn };
@@ -33,6 +33,31 @@ export type GridDataProvider<TItem> = (
33
33
  callback: GridDataProviderCallback<TItem>,
34
34
  ) => void;
35
35
 
36
+ export declare class ItemCache<TItem> {
37
+ grid: HTMLElement;
38
+ parentCache: ItemCache<TItem> | undefined;
39
+ parentItem: TItem | undefined;
40
+ itemCaches: object | null;
41
+ items: object | null;
42
+ effectiveSize: number;
43
+ size: number;
44
+ pendingRequests: object | null;
45
+
46
+ constructor(grid: HTMLElement, parentCache: ItemCache<TItem> | undefined, parentItem: TItem | undefined);
47
+
48
+ isLoading(): boolean;
49
+
50
+ getItemForIndex(index: number): TItem | undefined;
51
+
52
+ updateSize(): void;
53
+
54
+ ensureSubCacheForScaledIndex(scaledIndex: number): void;
55
+
56
+ getCacheAndIndex(index: number): { cache: ItemCache<TItem>; scaledIndex: number };
57
+
58
+ getFlatIndex(scaledIndex: number): number;
59
+ }
60
+
36
61
  export declare function DataProviderMixin<TItem, T extends Constructor<HTMLElement>>(
37
62
  base: T,
38
63
  ): Constructor<DataProviderMixinClass<TItem>> & T;
@@ -4,10 +4,116 @@
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { timeOut } from '@vaadin/component-base/src/async.js';
7
- import { DataProviderController } from '@vaadin/component-base/src/data-provider-controller.js';
8
7
  import { Debouncer } from '@vaadin/component-base/src/debounce.js';
8
+ import { get } from '@vaadin/component-base/src/path-utils.js';
9
9
  import { getBodyRowCells, updateCellsPart, updateState } from './vaadin-grid-helpers.js';
10
10
 
11
+ /**
12
+ * @private
13
+ */
14
+ export const ItemCache = class ItemCache {
15
+ /**
16
+ * @param {!HTMLElement} grid
17
+ * @param {!ItemCache | undefined} parentCache
18
+ * @param {!GridItem | undefined} parentItem
19
+ */
20
+ constructor(grid, parentCache, parentItem) {
21
+ /** @type {!HTMLElement} */
22
+ this.grid = grid;
23
+ /** @type {!ItemCache | undefined} */
24
+ this.parentCache = parentCache;
25
+ /** @type {!GridItem | undefined} */
26
+ this.parentItem = parentItem;
27
+ /** @type {object} */
28
+ this.itemCaches = {};
29
+ /** @type {object[]} */
30
+ this.items = [];
31
+ /** @type {number} */
32
+ this.effectiveSize = 0;
33
+ /** @type {number} */
34
+ this.size = 0;
35
+ /** @type {object} */
36
+ this.pendingRequests = {};
37
+ }
38
+
39
+ /**
40
+ * @return {boolean}
41
+ */
42
+ isLoading() {
43
+ return Boolean(
44
+ Object.keys(this.pendingRequests).length ||
45
+ Object.keys(this.itemCaches).filter((index) => {
46
+ return this.itemCaches[index].isLoading();
47
+ })[0],
48
+ );
49
+ }
50
+
51
+ /**
52
+ * @param {number} index
53
+ * @return {!GridItem | undefined}
54
+ */
55
+ getItemForIndex(index) {
56
+ const { cache, scaledIndex } = this.getCacheAndIndex(index);
57
+ return cache.items[scaledIndex];
58
+ }
59
+
60
+ updateSize() {
61
+ this.effectiveSize =
62
+ !this.parentItem || this.grid._isExpanded(this.parentItem)
63
+ ? this.size +
64
+ Object.keys(this.itemCaches).reduce((prev, curr) => {
65
+ const subCache = this.itemCaches[curr];
66
+ subCache.updateSize();
67
+ return prev + subCache.effectiveSize;
68
+ }, 0)
69
+ : 0;
70
+ }
71
+
72
+ /**
73
+ * @param {number} scaledIndex
74
+ */
75
+ ensureSubCacheForScaledIndex(scaledIndex) {
76
+ if (!this.itemCaches[scaledIndex]) {
77
+ const subCache = new ItemCache(this.grid, this, this.items[scaledIndex]);
78
+ this.itemCaches[scaledIndex] = subCache;
79
+ this.grid._loadPage(0, subCache);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * @param {number} index
85
+ * @return {{cache: !ItemCache, scaledIndex: number}}
86
+ */
87
+ getCacheAndIndex(index) {
88
+ let thisLevelIndex = index;
89
+ for (const [index, subCache] of Object.entries(this.itemCaches)) {
90
+ const numberIndex = Number(index);
91
+ if (thisLevelIndex <= numberIndex) {
92
+ return { cache: this, scaledIndex: thisLevelIndex };
93
+ } else if (thisLevelIndex <= numberIndex + subCache.effectiveSize) {
94
+ return subCache.getCacheAndIndex(thisLevelIndex - numberIndex - 1);
95
+ }
96
+ thisLevelIndex -= subCache.effectiveSize;
97
+ }
98
+ return { cache: this, scaledIndex: thisLevelIndex };
99
+ }
100
+
101
+ /**
102
+ * Gets the scaled index as flattened index on this cache level.
103
+ * In practice, this means that the effective size of any expanded
104
+ * subcaches preceding the index are added to the value.
105
+ * @param {number} scaledIndex
106
+ * @return {number} The flat index on this cache level.
107
+ */
108
+ getFlatIndex(scaledIndex) {
109
+ const clampedIndex = Math.max(0, Math.min(this.size - 1, scaledIndex));
110
+
111
+ return Object.entries(this.itemCaches).reduce((prev, [index, subCache]) => {
112
+ return clampedIndex > Number(index) ? prev + subCache.effectiveSize : prev;
113
+ }, clampedIndex);
114
+ }
115
+ };
116
+
11
117
  /**
12
118
  * @polymerMixin
13
119
  */
@@ -75,6 +181,18 @@ export const DataProviderMixin = (superClass) =>
75
181
  reflectToAttribute: true,
76
182
  },
77
183
 
184
+ /**
185
+ * @type {!ItemCache}
186
+ * @protected
187
+ */
188
+ _cache: {
189
+ type: Object,
190
+ value() {
191
+ const cache = new ItemCache(this);
192
+ return cache;
193
+ },
194
+ },
195
+
78
196
  /**
79
197
  * @protected
80
198
  */
@@ -126,32 +244,12 @@ export const DataProviderMixin = (superClass) =>
126
244
  return ['_sizeChanged(size)', '_expandedItemsChanged(expandedItems.*)'];
127
245
  }
128
246
 
129
- constructor() {
130
- super();
131
-
132
- /** @type {DataProviderController} */
133
- this._dataProviderController = new DataProviderController(this, {
134
- size: this.size,
135
- pageSize: this.pageSize,
136
- isExpanded: this._isExpanded.bind(this),
137
- dataProvider: this.dataProvider ? this.dataProvider.bind(this) : null,
138
- dataProviderParams: () => {
139
- return {
140
- sortOrders: this._mapSorters(),
141
- filters: this._mapFilters(),
142
- };
143
- },
144
- });
145
-
146
- this._dataProviderController.addEventListener('page-requested', this._onDataProviderPageRequested.bind(this));
147
- this._dataProviderController.addEventListener('page-received', this._onDataProviderPageReceived.bind(this));
148
- this._dataProviderController.addEventListener('page-loaded', this._onDataProviderPageLoaded.bind(this));
149
- }
150
-
151
247
  /** @private */
152
248
  _sizeChanged(size) {
153
- this._dataProviderController.setSize(size);
154
- this._effectiveSize = this._dataProviderController.effectiveSize;
249
+ const delta = size - this._cache.size;
250
+ this._cache.size += delta;
251
+ this._cache.effectiveSize += delta;
252
+ this._effectiveSize = this._cache.effectiveSize;
155
253
  }
156
254
 
157
255
  /** @private */
@@ -174,17 +272,17 @@ export const DataProviderMixin = (superClass) =>
174
272
  }
175
273
 
176
274
  el.index = index;
177
-
178
- const { item } = this._dataProviderController.getFlatIndexInfo(index);
275
+ const { cache, scaledIndex } = this._cache.getCacheAndIndex(index);
276
+ const item = cache.items[scaledIndex];
179
277
  if (item) {
180
278
  this.__updateLoading(el, false);
181
279
  this._updateItem(el, item);
182
280
  if (this._isExpanded(item)) {
183
- this._dataProviderController.ensureFlatIndexHierarchy(index);
281
+ cache.ensureSubCacheForScaledIndex(scaledIndex);
184
282
  }
185
283
  } else {
186
284
  this.__updateLoading(el, true);
187
- this._dataProviderController.ensureFlatIndexLoaded(index);
285
+ this._loadPage(this._getPageForIndex(scaledIndex), cache);
188
286
  }
189
287
  }
190
288
 
@@ -210,7 +308,7 @@ export const DataProviderMixin = (superClass) =>
210
308
  * @return {!GridItem | !unknown}
211
309
  */
212
310
  getItemId(item) {
213
- return this.itemIdPath ? this.get(this.itemIdPath, item) : item;
311
+ return this.itemIdPath ? get(this.itemIdPath, item) : item;
214
312
  }
215
313
 
216
314
  /**
@@ -224,8 +322,8 @@ export const DataProviderMixin = (superClass) =>
224
322
 
225
323
  /** @private */
226
324
  _expandedItemsChanged() {
227
- this._dataProviderController.recalculateEffectiveSize();
228
- this._effectiveSize = this._dataProviderController.effectiveSize;
325
+ this._cache.updateSize();
326
+ this._effectiveSize = this._cache.effectiveSize;
229
327
  this.__updateVisibleRows();
230
328
  }
231
329
 
@@ -265,68 +363,119 @@ export const DataProviderMixin = (superClass) =>
265
363
  * @return {number}
266
364
  * @protected
267
365
  */
268
- _getIndexLevel(index = 0) {
269
- const { level } = this._dataProviderController.getFlatIndexInfo(index);
366
+ _getIndexLevel(index) {
367
+ let { cache } = this._cache.getCacheAndIndex(index);
368
+ let level = 0;
369
+ while (cache.parentCache) {
370
+ cache = cache.parentCache;
371
+ level += 1;
372
+ }
270
373
  return level;
271
374
  }
272
375
 
273
- /** @protected */
274
- _onDataProviderPageRequested() {
275
- this._setLoading(true);
276
- }
277
-
278
- /** @protected */
279
- _onDataProviderPageReceived() {
280
- // With the new items added, update the cache size and the grid's effective size
281
- this._effectiveSize = this._dataProviderController.effectiveSize;
376
+ /**
377
+ * @param {number} page
378
+ * @param {ItemCache} cache
379
+ * @protected
380
+ */
381
+ _loadPage(page, cache) {
382
+ // Make sure same page isn't requested multiple times.
383
+ if (!cache.pendingRequests[page] && this.dataProvider) {
384
+ this._setLoading(true);
385
+ cache.pendingRequests[page] = true;
386
+ const params = {
387
+ page,
388
+ pageSize: this.pageSize,
389
+ sortOrders: this._mapSorters(),
390
+ filters: this._mapFilters(),
391
+ parentItem: cache.parentItem,
392
+ };
393
+
394
+ this.dataProvider(params, (items, size) => {
395
+ if (size !== undefined) {
396
+ cache.size = size;
397
+ } else if (params.parentItem) {
398
+ cache.size = items.length;
399
+ }
282
400
 
283
- // After updating the cache, check if some of the expanded items should have sub-caches loaded
284
- this._getRenderedRows().forEach((row) => {
285
- this._dataProviderController.ensureFlatIndexHierarchy(row.index);
286
- });
401
+ // Populate the cache with new items
402
+ items.forEach((item, itemsIndex) => {
403
+ const itemIndex = page * this.pageSize + itemsIndex;
404
+ cache.items[itemIndex] = item;
405
+ });
406
+
407
+ // With the new items added, update the cache size and the grid's effective size
408
+ this._cache.updateSize();
409
+ this._effectiveSize = this._cache.effectiveSize;
410
+
411
+ // After updating the cache, check if some of the expanded items should have sub-caches loaded
412
+ this._getRenderedRows().forEach((row) => {
413
+ const { cache, scaledIndex } = this._cache.getCacheAndIndex(row.index);
414
+ const item = cache.items[scaledIndex];
415
+ if (item && this._isExpanded(item)) {
416
+ cache.ensureSubCacheForScaledIndex(scaledIndex);
417
+ }
418
+ });
419
+
420
+ this._hasData = true;
421
+
422
+ // Remove the pending request
423
+ delete cache.pendingRequests[page];
424
+
425
+ // Schedule a debouncer to update the visible rows
426
+ this._debouncerApplyCachedData = Debouncer.debounce(this._debouncerApplyCachedData, timeOut.after(0), () => {
427
+ this._setLoading(false);
428
+
429
+ this._getRenderedRows().forEach((row) => {
430
+ const cachedItem = this._cache.getItemForIndex(row.index);
431
+ if (cachedItem) {
432
+ this._getItem(row.index, row);
433
+ }
434
+ });
435
+
436
+ this.__scrollToPendingIndexes();
437
+ });
438
+
439
+ // If the grid is not loading anything, flush the debouncer immediately
440
+ if (!this._cache.isLoading()) {
441
+ this._debouncerApplyCachedData.flush();
442
+ }
287
443
 
288
- this._hasData = true;
444
+ // Notify that new data has been received
445
+ this._onDataProviderPageLoaded();
446
+ });
447
+ }
289
448
  }
290
449
 
291
450
  /** @protected */
292
- _onDataProviderPageLoaded() {
293
- // Schedule a debouncer to update the visible rows
294
- this._debouncerApplyCachedData = Debouncer.debounce(this._debouncerApplyCachedData, timeOut.after(0), () => {
295
- this._setLoading(false);
296
-
297
- this._getRenderedRows().forEach((row) => {
298
- const { item } = this._dataProviderController.getFlatIndexInfo(row.index);
299
- if (item) {
300
- this._getItem(row.index, row);
301
- }
302
- });
451
+ _onDataProviderPageLoaded() {}
303
452
 
304
- this.__scrollToPendingIndexes();
305
- });
306
-
307
- // If the grid is not loading anything, flush the debouncer immediately
308
- if (!this._dataProviderController.isLoading()) {
309
- this._debouncerApplyCachedData.flush();
310
- }
453
+ /**
454
+ * @param {number} index
455
+ * @return {number}
456
+ * @private
457
+ */
458
+ _getPageForIndex(index) {
459
+ return Math.floor(index / this.pageSize);
311
460
  }
312
461
 
313
462
  /**
314
463
  * Clears the cached pages and reloads data from dataprovider when needed.
315
464
  */
316
465
  clearCache() {
317
- this._dataProviderController.clearCache();
466
+ this._cache = new ItemCache(this);
467
+ this._cache.size = this.size || 0;
468
+ this._cache.updateSize();
318
469
  this._hasData = false;
319
470
  this.__updateVisibleRows();
320
471
 
321
472
  if (!this._effectiveSize) {
322
- this._dataProviderController.loadFirstPage();
473
+ this._loadPage(0, this._cache);
323
474
  }
324
475
  }
325
476
 
326
477
  /** @private */
327
478
  _pageSizeChanged(pageSize, oldPageSize) {
328
- this._dataProviderController.setPageSize(pageSize);
329
-
330
479
  if (oldPageSize !== undefined && pageSize !== oldPageSize) {
331
480
  this.clearCache();
332
481
  }
@@ -346,8 +495,6 @@ export const DataProviderMixin = (superClass) =>
346
495
 
347
496
  /** @private */
348
497
  _dataProviderChanged(dataProvider, oldDataProvider) {
349
- this._dataProviderController.setDataProvider(dataProvider ? dataProvider.bind(this) : null);
350
-
351
498
  if (oldDataProvider !== undefined) {
352
499
  this.clearCache();
353
500
  }
@@ -366,7 +513,7 @@ export const DataProviderMixin = (superClass) =>
366
513
  if (!this._hasData) {
367
514
  // Load data before adding rows to make sure they have content when
368
515
  // rendered for the first time.
369
- this._dataProviderController.loadFirstPage();
516
+ this._loadPage(0, this._cache);
370
517
  }
371
518
  }
372
519
 
@@ -416,15 +563,39 @@ export const DataProviderMixin = (superClass) =>
416
563
  // ending up in a loading state. Try scrolling to the index until the target
417
564
  // index stabilizes.
418
565
  let targetIndex;
419
- while (targetIndex !== (targetIndex = this._dataProviderController.getFlatIndexByPath(indexes))) {
566
+ while (targetIndex !== (targetIndex = this.__getGlobalFlatIndex(indexes))) {
420
567
  this._scrollToFlatIndex(targetIndex);
421
568
  }
422
569
 
423
- if (this._dataProviderController.isLoading() || !this.clientHeight) {
570
+ if (this._cache.isLoading() || !this.clientHeight) {
424
571
  this.__pendingScrollToIndexes = indexes;
425
572
  }
426
573
  }
427
574
 
575
+ /**
576
+ * Recursively returns the globally flat index of the item the given indexes point to.
577
+ * Each index in the array points to a sub-item of the previous index.
578
+ * Using `Infinity` as an index will point to the last item on the level.
579
+ *
580
+ * @param {!Array<number>} indexes
581
+ * @param {!ItemCache} cache
582
+ * @param {number} flatIndex
583
+ * @return {number}
584
+ * @private
585
+ */
586
+ __getGlobalFlatIndex([levelIndex, ...subIndexes], cache = this._cache, flatIndex = 0) {
587
+ if (levelIndex === Infinity) {
588
+ // Treat Infinity as the last index on the level
589
+ levelIndex = cache.size - 1;
590
+ }
591
+ const flatIndexOnLevel = cache.getFlatIndex(levelIndex);
592
+ const subCache = cache.itemCaches[levelIndex];
593
+ if (subCache && subCache.effectiveSize && subIndexes.length) {
594
+ return this.__getGlobalFlatIndex(subIndexes, subCache, flatIndex + flatIndexOnLevel + 1);
595
+ }
596
+ return flatIndex + flatIndexOnLevel;
597
+ }
598
+
428
599
  /** @private */
429
600
  __scrollToPendingIndexes() {
430
601
  if (this.__pendingScrollToIndexes && this.$.items.children.length) {
@@ -112,7 +112,7 @@ export const DynamicColumnsMixin = (superClass) =>
112
112
  if (hasColumnElements(info.addedNodes) || hasColumnElements(info.removedNodes)) {
113
113
  const allRemovedCells = info.removedNodes.flatMap((c) => c._allCells);
114
114
  const filterNotConnected = (element) =>
115
- allRemovedCells.filter((cell) => cell._content.contains(element)).length;
115
+ allRemovedCells.filter((cell) => cell && cell._content.contains(element)).length;
116
116
 
117
117
  this.__removeSorters(this._sorters.filter(filterNotConnected));
118
118
  this.__removeFilters(this._filters.filter(filterNotConnected));
@@ -4,6 +4,7 @@
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import './vaadin-grid-filter.js';
7
+ import { defineCustomElement } from '@vaadin/component-base/src/define.js';
7
8
  import { GridColumn } from './vaadin-grid-column.js';
8
9
 
9
10
  /**
@@ -18,6 +19,9 @@ import { GridColumn } from './vaadin-grid-column.js';
18
19
  * <vaadin-grid-column>
19
20
  * ...
20
21
  * ```
22
+ *
23
+ * @customElement
24
+ * @extends GridColumn
21
25
  */
22
26
  class GridFilterColumn extends GridColumn {
23
27
  static get is() {
@@ -114,6 +118,6 @@ class GridFilterColumn extends GridColumn {
114
118
  }
115
119
  }
116
120
 
117
- customElements.define(GridFilterColumn.is, GridFilterColumn);
121
+ defineCustomElement(GridFilterColumn);
118
122
 
119
123
  export { GridFilterColumn };
@@ -8,6 +8,7 @@ import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
8
8
  import { timeOut } from '@vaadin/component-base/src/async.js';
9
9
  import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
10
10
  import { Debouncer } from '@vaadin/component-base/src/debounce.js';
11
+ import { defineCustomElement } from '@vaadin/component-base/src/define.js';
11
12
  import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
12
13
 
13
14
  /**
@@ -35,6 +36,7 @@ import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
35
36
  *
36
37
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
37
38
  *
39
+ * @customElement
38
40
  * @extends HTMLElement
39
41
  */
40
42
  class GridFilter extends ControllerMixin(PolymerElement) {
@@ -125,6 +127,6 @@ class GridFilter extends ControllerMixin(PolymerElement) {
125
127
  }
126
128
  }
127
129
 
128
- customElements.define(GridFilter.is, GridFilter);
130
+ defineCustomElement(GridFilter);
129
131
 
130
132
  export { GridFilter };
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { isKeyboardActive } from '@vaadin/a11y-base/src/focus-utils.js';
7
7
  import { addValueToAttribute, removeValueFromAttribute } from '@vaadin/component-base/src/dom-utils.js';
8
+ import { get } from '@vaadin/component-base/src/path-utils.js';
8
9
 
9
10
  /**
10
11
  * @polymerMixin
@@ -266,7 +267,7 @@ export const KeyboardNavigationMixin = (superClass) =>
266
267
  __isRowExpandable(row) {
267
268
  if (this.itemHasChildrenPath) {
268
269
  const item = row._item;
269
- return item && this.get(this.itemHasChildrenPath, item) && !this._isExpanded(item);
270
+ return item && get(this.itemHasChildrenPath, item) && !this._isExpanded(item);
270
271
  }
271
272
  }
272
273
 
@@ -475,7 +476,7 @@ export const KeyboardNavigationMixin = (superClass) =>
475
476
  // Row details navigation logic
476
477
  if (activeRowGroup === this.$.items) {
477
478
  const item = activeRow._item;
478
- const { item: dstItem } = this._dataProviderController.getFlatIndexInfo(dstRowIndex);
479
+ const dstItem = this._cache.getItemForIndex(dstRowIndex);
479
480
  // Should we navigate to row details?
480
481
  if (isRowDetails) {
481
482
  dstIsRowDetails = dy === 0;
@@ -681,7 +682,26 @@ export const KeyboardNavigationMixin = (superClass) =>
681
682
  }
682
683
  }
683
684
 
684
- return tabOrder[index];
685
+ let focusStepTarget = tabOrder[index];
686
+
687
+ // If the target focusable is tied to a column that is not visible,
688
+ // find the first visible column and update the target in order to
689
+ // prevent scrolling to the start of the row.
690
+ if (focusStepTarget && focusStepTarget._column && !this.__isColumnInViewport(focusStepTarget._column)) {
691
+ const firstVisibleColumn = this._getColumnsInOrder().find((column) => this.__isColumnInViewport(column));
692
+ if (firstVisibleColumn) {
693
+ if (focusStepTarget === this._headerFocusable) {
694
+ focusStepTarget = firstVisibleColumn._headerCell;
695
+ } else if (focusStepTarget === this._itemsFocusable) {
696
+ const rowIndex = focusStepTarget._column._cells.indexOf(focusStepTarget);
697
+ focusStepTarget = firstVisibleColumn._cells[rowIndex];
698
+ } else if (focusStepTarget === this._footerFocusable) {
699
+ focusStepTarget = firstVisibleColumn._footerCell;
700
+ }
701
+ }
702
+ }
703
+
704
+ return focusStepTarget;
685
705
  }
686
706
 
687
707
  /** @private */