@vaadin/grid 24.2.0-dev.f254716fe → 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.f254716fe",
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.f254716fe",
50
- "@vaadin/checkbox": "24.2.0-dev.f254716fe",
51
- "@vaadin/component-base": "24.2.0-dev.f254716fe",
52
- "@vaadin/lit-renderer": "24.2.0-dev.f254716fe",
53
- "@vaadin/text-field": "24.2.0-dev.f254716fe",
54
- "@vaadin/vaadin-lumo-styles": "24.2.0-dev.f254716fe",
55
- "@vaadin/vaadin-material-styles": "24.2.0-dev.f254716fe",
56
- "@vaadin/vaadin-themable-mixin": "24.2.0-dev.f254716fe"
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": "da54950b9f8c14c6451ede0d426e16a489c7fb9b"
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.ensureFlatIndexChildrenLoaded(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,121 @@ 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.ensureFlatIndexChildrenLoaded(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
- });
303
-
304
- this.__scrollToPendingIndexes();
305
- });
451
+ _onDataProviderPageLoaded() {}
306
452
 
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
- this._ensureFirstPageLoaded();
471
+
472
+ if (!this._effectiveSize) {
473
+ this._loadPage(0, this._cache);
474
+ }
321
475
  }
322
476
 
323
477
  /** @private */
324
478
  _pageSizeChanged(pageSize, oldPageSize) {
325
- this._dataProviderController.setPageSize(pageSize);
326
479
  if (oldPageSize !== undefined && pageSize !== oldPageSize) {
327
- this._hasData = false;
328
- this.__updateVisibleRows();
329
- this._ensureFirstPageLoaded();
480
+ this.clearCache();
330
481
  }
331
482
  }
332
483
 
@@ -344,11 +495,8 @@ export const DataProviderMixin = (superClass) =>
344
495
 
345
496
  /** @private */
346
497
  _dataProviderChanged(dataProvider, oldDataProvider) {
347
- this._dataProviderController.setDataProvider(dataProvider ? dataProvider.bind(this) : null);
348
-
349
498
  if (oldDataProvider !== undefined) {
350
- this._hasData = false;
351
- this.__updateVisibleRows();
499
+ this.clearCache();
352
500
  }
353
501
 
354
502
  this._ensureFirstPageLoaded();
@@ -365,7 +513,7 @@ export const DataProviderMixin = (superClass) =>
365
513
  if (!this._hasData) {
366
514
  // Load data before adding rows to make sure they have content when
367
515
  // rendered for the first time.
368
- this._dataProviderController.ensureFirstPageLoaded();
516
+ this._loadPage(0, this._cache);
369
517
  }
370
518
  }
371
519
 
@@ -415,15 +563,39 @@ export const DataProviderMixin = (superClass) =>
415
563
  // ending up in a loading state. Try scrolling to the index until the target
416
564
  // index stabilizes.
417
565
  let targetIndex;
418
- while (targetIndex !== (targetIndex = this._dataProviderController.getFlatIndexByPath(indexes))) {
566
+ while (targetIndex !== (targetIndex = this.__getGlobalFlatIndex(indexes))) {
419
567
  this._scrollToFlatIndex(targetIndex);
420
568
  }
421
569
 
422
- if (this._dataProviderController.isLoading() || !this.clientHeight) {
570
+ if (this._cache.isLoading() || !this.clientHeight) {
423
571
  this.__pendingScrollToIndexes = indexes;
424
572
  }
425
573
  }
426
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
+
427
599
  /** @private */
428
600
  __scrollToPendingIndexes() {
429
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 */