@vaadin/component-base 24.3.0-alpha1 → 24.3.0-alpha3

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/component-base",
3
- "version": "24.3.0-alpha1",
3
+ "version": "24.3.0-alpha3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -42,5 +42,5 @@
42
42
  "@vaadin/testing-helpers": "^0.5.0",
43
43
  "sinon": "^13.0.2"
44
44
  },
45
- "gitHead": "9ca6f3ca220a777e8eea181a1f5717e39a732240"
45
+ "gitHead": "9162ca5fb9879dbcc8c68a77c1acb3af2c497a15"
46
46
  }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import type { DataProviderCallback } from './data-provider-controller.js';
7
+
8
+ export type CacheContext<TItem> = { isExpanded(item: TItem): boolean };
9
+
10
+ /**
11
+ * A class that stores items with their associated sub-caches.
12
+ */
13
+ export class Cache<TItem> {
14
+ /**
15
+ * A context object.
16
+ */
17
+ context: CacheContext<TItem>;
18
+
19
+ /**
20
+ * The number of items.
21
+ */
22
+ size: number;
23
+
24
+ /**
25
+ * The number of items to display per page.
26
+ */
27
+ pageSize: number;
28
+
29
+ /**
30
+ * An array of cached items.
31
+ */
32
+ items: TItem[];
33
+
34
+ /**
35
+ * A map where the key is a requested page and the value is a callback
36
+ * that will be called with data once the request is complete.
37
+ */
38
+ pendingRequests: Record<number, DataProviderCallback<TItem>>;
39
+
40
+ /**
41
+ * An item in the parent cache that the current cache is associated with.
42
+ */
43
+ get parentItem(): TItem | undefined;
44
+
45
+ /**
46
+ * An array of sub-caches sorted in the same order as their associated items
47
+ * appear in the `items` array.
48
+ */
49
+ get subCaches(): Array<Cache<TItem>>;
50
+
51
+ /**
52
+ * Whether the cache or any of its descendant caches have pending requests.
53
+ */
54
+ get isLoading(): boolean;
55
+
56
+ /**
57
+ * The total number of items, including items from expanded sub-caches.
58
+ */
59
+ get flatSize(): number;
60
+
61
+ /**
62
+ * The total number of items, including items from expanded sub-caches.
63
+ *
64
+ * @protected
65
+ * @deprecated since 24.3 and will be removed in Vaadin 25.
66
+ */
67
+ get effectiveSize(): number;
68
+
69
+ constructor(
70
+ context: CacheContext<TItem>,
71
+ pageSize: number,
72
+ size: number,
73
+ parentCache?: Cache<TItem>,
74
+ parentCacheIndex?: number,
75
+ );
76
+
77
+ /**
78
+ * Recalculates the flattened size for the cache and its descendant caches recursively.
79
+ */
80
+ recalculateFlatSize(): void;
81
+
82
+ /**
83
+ * Adds an array of items corresponding to the given page
84
+ * to the `items` array.
85
+ */
86
+ setPage(page: number, items: unknown[]): void;
87
+
88
+ /**
89
+ * Retrieves the sub-cache associated with the item at the given index
90
+ * in the `items` array.
91
+ */
92
+ getSubCache(index: number): Cache<TItem> | undefined;
93
+
94
+ /**
95
+ * Removes the sub-cache associated with the item at the given index
96
+ * in the `items` array.
97
+ */
98
+ removeSubCache(index: number): void;
99
+
100
+ /**
101
+ * Removes all sub-caches.
102
+ */
103
+ removeSubCaches(): void;
104
+
105
+ /**
106
+ * Creates and associates a sub-cache for the item at the given index
107
+ * in the `items` array.
108
+ */
109
+ createSubCache(index: number): Cache<TItem>;
110
+
111
+ /**
112
+ * Retrieves the flattened index corresponding to the given index
113
+ * of an item in the `items` array.
114
+ */
115
+ getFlatIndex(index: number): number;
116
+
117
+ /**
118
+ * @deprecated since 24.3 and will be removed in Vaadin 25.
119
+ */
120
+ getItemForIndex(index: number): TItem | undefined;
121
+
122
+ /**
123
+ * @deprecated since 24.3 and will be removed in Vaadin 25.
124
+ */
125
+ getCacheAndIndex(index: number): { cache: Cache<TItem>; scaledIndex: number };
126
+
127
+ /**
128
+ * @deprecated since 24.3 and will be removed in Vaadin 25.
129
+ */
130
+ updateSize(): void;
131
+
132
+ /**
133
+ * @deprecated since 24.3 and will be removed in Vaadin 25.
134
+ */
135
+ ensureSubCacheForScaledIndex(scaledIndex: number): void;
136
+
137
+ /**
138
+ * @deprecated since 24.3 and will be removed in Vaadin 25.
139
+ */
140
+ get grid(): HTMLElement;
141
+
142
+ /**
143
+ * @deprecated since 24.3 and will be removed in Vaadin 25.
144
+ */
145
+ get itemCaches(): object;
146
+ }
@@ -3,12 +3,15 @@
3
3
  * Copyright (c) 2021 - 2023 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
+ import { getFlatIndexContext } from './helpers.js';
6
7
 
7
8
  /**
8
9
  * A class that stores items with their associated sub-caches.
9
10
  */
10
11
  export class Cache {
11
12
  /**
13
+ * A context object.
14
+ *
12
15
  * @type {{ isExpanded: (item: unknown) => boolean }}
13
16
  */
14
17
  context;
@@ -38,9 +41,9 @@ export class Cache {
38
41
  * A map where the key is a requested page and the value is a callback
39
42
  * that will be called with data once the request is complete.
40
43
  *
41
- * @type {Map<number, Function>}
44
+ * @type {Record<number, Function>}
42
45
  */
43
- pendingRequests = new Map();
46
+ pendingRequests = {};
44
47
 
45
48
  /**
46
49
  * A map where the key is the index of an item in the `items` array
@@ -61,7 +64,7 @@ export class Cache {
61
64
  * @type {number}
62
65
  * @private
63
66
  */
64
- __effectiveSize = 0;
67
+ __flatSize = 0;
65
68
 
66
69
  /**
67
70
  * @param {Cache['context']} context
@@ -76,7 +79,7 @@ export class Cache {
76
79
  this.size = size || 0;
77
80
  this.parentCache = parentCache;
78
81
  this.parentCacheIndex = parentCacheIndex;
79
- this.__effectiveSize = size || 0;
82
+ this.__flatSize = size || 0;
80
83
  }
81
84
 
82
85
  /**
@@ -104,7 +107,7 @@ export class Cache {
104
107
  * @return {boolean}
105
108
  */
106
109
  get isLoading() {
107
- if (this.pendingRequests.size > 0) {
110
+ if (Object.keys(this.pendingRequests).length > 0) {
108
111
  return true;
109
112
  }
110
113
 
@@ -116,20 +119,33 @@ export class Cache {
116
119
  *
117
120
  * @return {number}
118
121
  */
122
+ get flatSize() {
123
+ return this.__flatSize;
124
+ }
125
+
126
+ /**
127
+ * The total number of items, including items from expanded sub-caches.
128
+ *
129
+ * @protected
130
+ * @deprecated since 24.3 and will be removed in Vaadin 25.
131
+ */
119
132
  get effectiveSize() {
120
- return this.__effectiveSize;
133
+ console.warn(
134
+ '<vaadin-grid> The `effectiveSize` property of ItemCache is deprecated and will be removed in Vaadin 25.',
135
+ );
136
+ return this.flatSize;
121
137
  }
122
138
 
123
139
  /**
124
- * Recalculates the effective size for the cache and its descendant caches recursively.
140
+ * Recalculates the flattened size for the cache and its descendant caches recursively.
125
141
  */
126
- recalculateEffectiveSize() {
127
- this.__effectiveSize =
142
+ recalculateFlatSize() {
143
+ this.__flatSize =
128
144
  !this.parentItem || this.context.isExpanded(this.parentItem)
129
145
  ? this.size +
130
146
  this.subCaches.reduce((total, subCache) => {
131
- subCache.recalculateEffectiveSize();
132
- return total + subCache.effectiveSize;
147
+ subCache.recalculateFlatSize();
148
+ return total + subCache.flatSize;
133
149
  }, 0)
134
150
  : 0;
135
151
  }
@@ -201,7 +217,69 @@ export class Cache {
201
217
 
202
218
  return this.subCaches.reduce((prev, subCache) => {
203
219
  const index = subCache.parentCacheIndex;
204
- return clampedIndex > index ? prev + subCache.effectiveSize : prev;
220
+ return clampedIndex > index ? prev + subCache.flatSize : prev;
205
221
  }, clampedIndex);
206
222
  }
223
+
224
+ /**
225
+ * @deprecated since 24.3 and will be removed in Vaadin 25.
226
+ */
227
+ getItemForIndex(index) {
228
+ console.warn(
229
+ '<vaadin-grid> The `getItemForIndex` method of ItemCache is deprecated and will be removed in Vaadin 25.',
230
+ );
231
+ const { item } = getFlatIndexContext(this, index);
232
+ return item;
233
+ }
234
+
235
+ /**
236
+ * @deprecated since 24.3 and will be removed in Vaadin 25.
237
+ */
238
+ getCacheAndIndex(index) {
239
+ console.warn(
240
+ '<vaadin-grid> The `getCacheAndIndex` method of ItemCache is deprecated and will be removed in Vaadin 25.',
241
+ );
242
+ const { cache, index: scaledIndex } = getFlatIndexContext(this, index);
243
+ return { cache, scaledIndex };
244
+ }
245
+
246
+ /**
247
+ * @deprecated since 24.3 and will be removed in Vaadin 25.
248
+ */
249
+ updateSize() {
250
+ console.warn('<vaadin-grid> The `updateSize` method of ItemCache is deprecated and will be removed in Vaadin 25.');
251
+ this.recalculateFlatSize();
252
+ }
253
+
254
+ /**
255
+ * @deprecated since 24.3 and will be removed in Vaadin 25.
256
+ */
257
+ ensureSubCacheForScaledIndex(scaledIndex) {
258
+ console.warn(
259
+ '<vaadin-grid> The `ensureSubCacheForScaledIndex` method of ItemCache is deprecated and will be removed in Vaadin 25.',
260
+ );
261
+
262
+ if (!this.getSubCache(scaledIndex)) {
263
+ const subCache = this.createSubCache(scaledIndex);
264
+ this.context.__controller.__loadCachePage(subCache, 0);
265
+ }
266
+ }
267
+
268
+ /**
269
+ * @deprecated since 24.3 and will be removed in Vaadin 25.
270
+ */
271
+ get grid() {
272
+ console.warn('<vaadin-grid> The `grid` property of ItemCache is deprecated and will be removed in Vaadin 25.');
273
+ return this.context.__controller.host;
274
+ }
275
+
276
+ /**
277
+ * @deprecated since 24.3 and will be removed in Vaadin 25.
278
+ */
279
+ get itemCaches() {
280
+ console.warn(
281
+ '<vaadin-grid> The `itemCaches` property of ItemCache is deprecated and will be removed in Vaadin 25.',
282
+ );
283
+ return this.__subCacheByIndex;
284
+ }
207
285
  }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import type { ReactiveController } from 'lit';
7
+ import type { Cache } from './cache.js';
8
+ import type { getFlatIndexByPath, getFlatIndexContext, getItemContext } from './helpers.js';
9
+
10
+ type DataProviderDefaultParams = {
11
+ page: number;
12
+ pageSize: number;
13
+ parentItem?: unknown;
14
+ };
15
+
16
+ export type DataProviderCallback<TItem> = (items: TItem[], size?: number) => void;
17
+
18
+ export type DataProvider<TItem, TDataProviderParams extends Record<string, unknown>> = (
19
+ params: DataProviderDefaultParams & TDataProviderParams,
20
+ callback: DataProviderCallback<TItem>,
21
+ ) => void;
22
+
23
+ /**
24
+ * A controller that stores and manages items loaded with a data provider.
25
+ */
26
+ export class DataProviderController<TItem, TDataProviderParams extends Record<string, unknown>>
27
+ implements ReactiveController
28
+ {
29
+ /**
30
+ * The controller host element.
31
+ */
32
+ host: HTMLElement;
33
+
34
+ /**
35
+ * A callback that returns data based on the passed params such as
36
+ * `page`, `pageSize`, `parentItem`, etc.
37
+ */
38
+ dataProvider: DataProvider<TItem, TDataProviderParams>;
39
+
40
+ /**
41
+ * A callback that returns additional params that need to be passed
42
+ * to the data provider callback with every request.
43
+ */
44
+ dataProviderParams: () => TDataProviderParams;
45
+
46
+ /**
47
+ * A number of items in the root cache.
48
+ */
49
+ size?: number;
50
+
51
+ /**
52
+ * A number of items to display per page.
53
+ */
54
+ pageSize: number;
55
+
56
+ /**
57
+ * A callback that returns whether the given item is expanded.
58
+ */
59
+ isExpanded: (item: TItem) => boolean;
60
+
61
+ /**
62
+ * A callback that returns the id for the given item and that
63
+ * is used when checking object items for equality.
64
+ */
65
+ getItemId: (item: TItem) => unknown;
66
+
67
+ /**
68
+ * A reference to the root cache instance.
69
+ */
70
+ rootCache: Cache<TItem>;
71
+
72
+ constructor(
73
+ host: HTMLElement,
74
+ config: {
75
+ size?: number;
76
+ pageSize: number;
77
+ getItemId(item: TItem): unknown;
78
+ isExpanded(item: TItem): boolean;
79
+ dataProvider: DataProvider<TItem, TDataProviderParams>;
80
+ dataProviderParams(): TDataProviderParams;
81
+ },
82
+ );
83
+
84
+ /**
85
+ * The total number of items, including items from expanded sub-caches.
86
+ */
87
+ get flatSize(): number;
88
+
89
+ hostConnected(): void;
90
+
91
+ hostDisconnected(): void;
92
+
93
+ /**
94
+ * Whether the root cache or any of its decendant caches have pending requests.
95
+ */
96
+ isLoading(): boolean;
97
+
98
+ /**
99
+ * Sets the size for the root cache and recalculates the flattened size.
100
+ */
101
+ setSize(size: number): void;
102
+
103
+ /**
104
+ * Sets the page size and clears the cache.
105
+ */
106
+ setPageSize(pageSize: number): void;
107
+
108
+ /**
109
+ * Sets the data provider callback and clears the cache.
110
+ */
111
+ setDataProvider(dataProvider: DataProvider<TItem, TDataProviderParams>): void;
112
+
113
+ /**
114
+ * Recalculates the flattened size.
115
+ */
116
+ recalculateFlatSize(): void;
117
+
118
+ /**
119
+ * Clears the cache.
120
+ */
121
+ clearCache(): void;
122
+
123
+ /**
124
+ * Returns context for the given flattened index, including:
125
+ * - the corresponding cache
126
+ * - the cache level
127
+ * - the corresponding item (if loaded)
128
+ * - the item's index in the cache's items array
129
+ * - the page containing the item
130
+ */
131
+ getFlatIndexContext(flatIndex: number): ReturnType<typeof getFlatIndexContext<TItem>>;
132
+
133
+ /**
134
+ * Returns context for the given item, including:
135
+ * - the cache containing the item
136
+ * - the cache level
137
+ * - the item
138
+ * - the item's index in the cache's items array
139
+ * - the item's flattened index
140
+ * - the item's sub-cache (if exists)
141
+ * - the page containing the item
142
+ *
143
+ * If the item isn't found, the method returns undefined.
144
+ */
145
+ getItemContext(item: TItem): ReturnType<typeof getItemContext<TItem>>;
146
+
147
+ /**
148
+ * Returns the flattened index for the item that the given indexes point to.
149
+ * Each index in the path array points to a sub-item of the previous index.
150
+ * Using `Infinity` as an index will point to the last item on the level.
151
+ */
152
+ getFlatIndexByPath(path: number[]): ReturnType<typeof getFlatIndexByPath<TItem>>;
153
+
154
+ /**
155
+ * Requests the data provider to load the page with the item corresponding
156
+ * to the given flattened index. If the item is already loaded, the method
157
+ * returns immediatelly.
158
+ */
159
+ ensureFlatIndexLoaded(flatIndex: number): void;
160
+
161
+ /**
162
+ * Creates a sub-cache for the item corresponding to the given flattened index and
163
+ * requests the data provider to load the first page into the created sub-cache.
164
+ * If the sub-cache already exists, the method returns immediatelly.
165
+ */
166
+ ensureFlatIndexHierarchy(flatIndex: number): void;
167
+
168
+ /**
169
+ * Loads the first page into the root cache.
170
+ */
171
+ loadFirstPage(): void;
172
+ }
@@ -4,7 +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 { Cache } from './cache.js';
7
- import { getFlatIndexByPath, getFlatIndexContext } from './helpers.js';
7
+ import { getFlatIndexByPath, getFlatIndexContext, getItemContext } from './helpers.js';
8
8
 
9
9
  /**
10
10
  * A controller that stores and manages items loaded with a data provider.
@@ -50,6 +50,14 @@ export class DataProviderController extends EventTarget {
50
50
  */
51
51
  isExpanded;
52
52
 
53
+ /**
54
+ * A callback that returns the id for the given item and that
55
+ * is used when checking object items for equality.
56
+ *
57
+ * @type { (item: unknown) => unknown}
58
+ */
59
+ getItemId;
60
+
53
61
  /**
54
62
  * A reference to the root cache instance.
55
63
  *
@@ -57,11 +65,12 @@ export class DataProviderController extends EventTarget {
57
65
  */
58
66
  rootCache;
59
67
 
60
- constructor(host, { size, pageSize, isExpanded, dataProvider, dataProviderParams }) {
68
+ constructor(host, { size, pageSize, isExpanded, getItemId, dataProvider, dataProviderParams }) {
61
69
  super();
62
70
  this.host = host;
63
71
  this.size = size;
64
72
  this.pageSize = pageSize;
73
+ this.getItemId = getItemId;
65
74
  this.isExpanded = isExpanded;
66
75
  this.dataProvider = dataProvider;
67
76
  this.dataProviderParams = dataProviderParams;
@@ -71,13 +80,17 @@ export class DataProviderController extends EventTarget {
71
80
  /**
72
81
  * The total number of items, including items from expanded sub-caches.
73
82
  */
74
- get effectiveSize() {
75
- return this.rootCache.effectiveSize;
83
+ get flatSize() {
84
+ return this.rootCache.flatSize;
76
85
  }
77
86
 
78
87
  /** @private */
79
88
  get __cacheContext() {
80
- return { isExpanded: this.isExpanded };
89
+ return {
90
+ isExpanded: this.isExpanded,
91
+ // The controller instance is needed to ensure deprecated cache methods work.
92
+ __controller: this,
93
+ };
81
94
  }
82
95
 
83
96
  /**
@@ -90,14 +103,14 @@ export class DataProviderController extends EventTarget {
90
103
  }
91
104
 
92
105
  /**
93
- * Sets the size for the root cache and recalculates the effective size.
106
+ * Sets the size for the root cache and recalculates the flattened size.
94
107
  *
95
108
  * @param {number} size
96
109
  */
97
110
  setSize(size) {
98
111
  this.size = size;
99
112
  this.rootCache.size = size;
100
- this.recalculateEffectiveSize();
113
+ this.recalculateFlatSize();
101
114
  }
102
115
 
103
116
  /**
@@ -121,10 +134,10 @@ export class DataProviderController extends EventTarget {
121
134
  }
122
135
 
123
136
  /**
124
- * Recalculates the effective size.
137
+ * Recalculates the flattened size.
125
138
  */
126
- recalculateEffectiveSize() {
127
- this.rootCache.recalculateEffectiveSize();
139
+ recalculateFlatSize() {
140
+ this.rootCache.recalculateFlatSize();
128
141
  }
129
142
 
130
143
  /**
@@ -137,15 +150,33 @@ export class DataProviderController extends EventTarget {
137
150
  /**
138
151
  * Returns context for the given flattened index, including:
139
152
  * - the corresponding cache
140
- * - the associated item (if loaded)
141
- * - the corresponding index in the cache's items array.
142
- * - the page containing the index.
143
153
  * - the cache level
154
+ * - the corresponding item (if loaded)
155
+ * - the item's index in the cache's items array
156
+ * - the page containing the item
157
+ *
158
+ * @param {number} flatIndex
144
159
  */
145
160
  getFlatIndexContext(flatIndex) {
146
161
  return getFlatIndexContext(this.rootCache, flatIndex);
147
162
  }
148
163
 
164
+ /**
165
+ * Returns context for the given item, including:
166
+ * - the cache containing the item
167
+ * - the cache level
168
+ * - the item
169
+ * - the item's index in the cache's items array
170
+ * - the item's flattened index
171
+ * - the item's sub-cache (if exists)
172
+ * - the page containing the item
173
+ *
174
+ * If the item isn't found, the method returns undefined.
175
+ */
176
+ getItemContext(item) {
177
+ return getItemContext({ getItemId: this.getItemId }, this.rootCache, item);
178
+ }
179
+
149
180
  /**
150
181
  * Returns the flattened index for the item that the given indexes point to.
151
182
  * Each index in the path array points to a sub-item of the previous index.
@@ -203,17 +234,20 @@ export class DataProviderController extends EventTarget {
203
234
 
204
235
  /** @private */
205
236
  __loadCachePage(cache, page) {
206
- if (!this.dataProvider || cache.pendingRequests.has(page)) {
237
+ if (!this.dataProvider || cache.pendingRequests[page]) {
207
238
  return;
208
239
  }
209
240
 
210
- const params = {
241
+ let params = {
211
242
  page,
212
243
  pageSize: this.pageSize,
213
244
  parentItem: cache.parentItem,
214
- ...this.dataProviderParams(),
215
245
  };
216
246
 
247
+ if (this.dataProviderParams) {
248
+ params = { ...params, ...this.dataProviderParams() };
249
+ }
250
+
217
251
  const callback = (items, size) => {
218
252
  if (size !== undefined) {
219
253
  cache.size = size;
@@ -223,16 +257,16 @@ export class DataProviderController extends EventTarget {
223
257
 
224
258
  cache.setPage(page, items);
225
259
 
226
- this.recalculateEffectiveSize();
260
+ this.recalculateFlatSize();
227
261
 
228
262
  this.dispatchEvent(new CustomEvent('page-received'));
229
263
 
230
- cache.pendingRequests.delete(page);
264
+ delete cache.pendingRequests[page];
231
265
 
232
266
  this.dispatchEvent(new CustomEvent('page-loaded'));
233
267
  };
234
268
 
235
- cache.pendingRequests.set(page, callback);
269
+ cache.pendingRequests[page] = callback;
236
270
 
237
271
  this.dispatchEvent(new CustomEvent('page-requested'));
238
272
 
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import type { Cache } from './cache.js';
7
+
8
+ /**
9
+ * Returns context for the given flattened index, including:
10
+ * - the corresponding cache
11
+ * - the cache level
12
+ * - the corresponding item (if loaded)
13
+ * - the item's index in the cache's items array
14
+ * - the page containing the item
15
+ */
16
+ export function getFlatIndexContext<TItem>(
17
+ cache: Cache<TItem>,
18
+ flatIndex: number,
19
+ level: number,
20
+ ): {
21
+ cache: Cache<TItem>;
22
+ item: TItem | undefined;
23
+ index: number;
24
+ page: number;
25
+ level: number;
26
+ };
27
+
28
+ /**
29
+ * Returns context for the given item, including:
30
+ * - the cache containing the item
31
+ * - the cache level
32
+ * - the item
33
+ * - the item's index in the cache's items array
34
+ * - the item's flattened index
35
+ * - the item's sub-cache (if exists)
36
+ * - the page containing the item
37
+ *
38
+ * If the item isn't found, the method returns undefined.
39
+ */
40
+ export function getItemContext<TItem>(
41
+ context: { getItemId(item: TItem): unknown },
42
+ cache: Cache<TItem>,
43
+ targetItem: TItem,
44
+ level: number,
45
+ levelFlatIndex: number,
46
+ ):
47
+ | {
48
+ level: number;
49
+ item: TItem;
50
+ index: number;
51
+ page: number;
52
+ flatIndex: number;
53
+ cache: Cache<TItem>;
54
+ subCache: Cache<TItem> | undefined;
55
+ }
56
+ | undefined;
57
+
58
+ /**
59
+ * Recursively returns the globally flat index of the item the given indexes point to.
60
+ * Each index in the array points to a sub-item of the previous index.
61
+ * Using `Infinity` as an index will point to the last item on the level.
62
+ */
63
+ export function getFlatIndexByPath<TItem>(cache: Cache<TItem>, path: number[], flatIndex: number): number;
@@ -4,17 +4,20 @@
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
 
7
+ /**
8
+ * @typedef {import('./cache.js').Cache} Cache
9
+ */
10
+
7
11
  /**
8
12
  * Returns context for the given flattened index, including:
9
13
  * - the corresponding cache
10
- * - the associated item (if loaded)
11
- * - the corresponding index in the cache's items array.
12
- * - the page containing the index.
13
14
  * - the cache level
15
+ * - the corresponding item (if loaded)
16
+ * - the item's index in the cache's items array
17
+ * - the page containing the item
14
18
  *
15
- * @param {import('./cache.js').Cache} cache
19
+ * @param {Cache} cache
16
20
  * @param {number} flatIndex
17
- * @return {{ cache: Cache, item: object | undefined, index: number, page: number, level: number }}
18
21
  */
19
22
  export function getFlatIndexContext(cache, flatIndex, level = 0) {
20
23
  let levelIndex = flatIndex;
@@ -23,10 +26,10 @@ export function getFlatIndexContext(cache, flatIndex, level = 0) {
23
26
  const index = subCache.parentCacheIndex;
24
27
  if (levelIndex <= index) {
25
28
  break;
26
- } else if (levelIndex <= index + subCache.effectiveSize) {
29
+ } else if (levelIndex <= index + subCache.flatSize) {
27
30
  return getFlatIndexContext(subCache, levelIndex - index - 1, level + 1);
28
31
  }
29
- levelIndex -= subCache.effectiveSize;
32
+ levelIndex -= subCache.flatSize;
30
33
  }
31
34
 
32
35
  return {
@@ -38,6 +41,52 @@ export function getFlatIndexContext(cache, flatIndex, level = 0) {
38
41
  };
39
42
  }
40
43
 
44
+ /**
45
+ * Returns context for the given item, including:
46
+ * - the cache containing the item
47
+ * - the cache level
48
+ * - the item
49
+ * - the item's index in the cache's items array
50
+ * - the item's flattened index
51
+ * - the item's sub-cache (if exists)
52
+ * - the page containing the item
53
+ *
54
+ * If the item isn't found, the method returns undefined.
55
+ *
56
+ * @param {Cache} cache
57
+ * @param {{ getItemId: (item: unknown) => unknown}} context
58
+ * @param {Cache} cache
59
+ * @param {unknown} targetItem
60
+ * @param {number} level
61
+ * @param {number} levelFlatIndex
62
+ */
63
+ export function getItemContext({ getItemId }, cache, targetItem, level = 0, levelFlatIndex = 0) {
64
+ // Start looking in this cache
65
+ for (let index = 0; index < cache.items.length; index++) {
66
+ const item = cache.items[index];
67
+ if (!!item && getItemId(item) === getItemId(targetItem)) {
68
+ return {
69
+ cache,
70
+ level,
71
+ item,
72
+ index,
73
+ page: Math.floor(index / cache.pageSize),
74
+ subCache: cache.getSubCache(index),
75
+ flatIndex: levelFlatIndex + cache.getFlatIndex(index),
76
+ };
77
+ }
78
+ }
79
+
80
+ // Look through sub-caches
81
+ for (const subCache of cache.subCaches) {
82
+ const parentItemFlatIndex = levelFlatIndex + cache.getFlatIndex(subCache.parentCacheIndex);
83
+ const result = getItemContext({ getItemId }, subCache, targetItem, level + 1, parentItemFlatIndex + 1);
84
+ if (result) {
85
+ return result;
86
+ }
87
+ }
88
+ }
89
+
41
90
  /**
42
91
  * Recursively returns the globally flat index of the item the given indexes point to.
43
92
  * Each index in the array points to a sub-item of the previous index.
@@ -56,7 +105,7 @@ export function getFlatIndexByPath(cache, [levelIndex, ...subIndexes], flatIndex
56
105
 
57
106
  const flatIndexOnLevel = cache.getFlatIndex(levelIndex);
58
107
  const subCache = cache.getSubCache(levelIndex);
59
- if (subCache && subCache.effectiveSize > 0 && subIndexes.length) {
108
+ if (subCache && subCache.flatSize > 0 && subIndexes.length) {
60
109
  return getFlatIndexByPath(subCache, subIndexes, flatIndex + flatIndexOnLevel + 1);
61
110
  }
62
111
  return flatIndex + flatIndexOnLevel;
package/src/define.js CHANGED
@@ -3,9 +3,16 @@
3
3
  * Copyright (c) 2021 - 2023 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
+
6
7
  export function defineCustomElement(CustomElement) {
7
8
  const defined = customElements.get(CustomElement.is);
8
9
  if (!defined) {
10
+ Object.defineProperty(CustomElement, 'version', {
11
+ get() {
12
+ return '24.3.0-alpha3';
13
+ },
14
+ });
15
+
9
16
  customElements.define(CustomElement.is, CustomElement);
10
17
  } else {
11
18
  const definedVersion = defined.version;
@@ -44,10 +44,6 @@ const registered = new Set();
44
44
  */
45
45
  export const ElementMixin = (superClass) =>
46
46
  class VaadinElementMixin extends DirMixin(superClass) {
47
- static get version() {
48
- return '24.3.0-alpha1';
49
- }
50
-
51
47
  /** @protected */
52
48
  static finalize() {
53
49
  super.finalize();
@@ -82,10 +82,24 @@ export class IronListAdapter {
82
82
  return this.lastVisibleIndex + this._vidxOffset;
83
83
  }
84
84
 
85
+ __hasPlaceholders() {
86
+ return this.__getVisibleElements().some((el) => el.__virtualizerPlaceholder);
87
+ }
88
+
85
89
  scrollToIndex(index) {
86
90
  if (typeof index !== 'number' || isNaN(index) || this.size === 0 || !this.scrollTarget.offsetHeight) {
87
91
  return;
88
92
  }
93
+ delete this.__pendingScrollToIndex;
94
+
95
+ if (this._physicalCount <= 3 /* iron-list-core.DEFAULT_PHYSICAL_COUNT */) {
96
+ // The condition here is a performance improvement to avoid an unnecessary
97
+ // re-render when the physical item pool is already covered.
98
+
99
+ // Finish rendering at the current scroll position before scrolling
100
+ this.flush();
101
+ }
102
+
89
103
  index = this._clamp(index, 0, this.size - 1);
90
104
 
91
105
  const visibleElementCount = this.__getVisibleElements().length;
@@ -113,6 +127,12 @@ export class IronListAdapter {
113
127
  this._scrollTop -= this.__getIndexScrollOffset(index) || 0;
114
128
  }
115
129
  this._scrollHandler();
130
+
131
+ if (this.__hasPlaceholders()) {
132
+ // After rendering synchronously, there are still placeholders in the DOM.
133
+ // Try again after the next elements update.
134
+ this.__pendingScrollToIndex = index;
135
+ }
116
136
  }
117
137
 
118
138
  flush() {
@@ -199,8 +219,9 @@ export class IronListAdapter {
199
219
 
200
220
  __updateElement(el, index, forceSameIndexUpdates) {
201
221
  // Clean up temporary placeholder sizing
202
- if (el.style.paddingTop) {
222
+ if (el.__virtualizerPlaceholder) {
203
223
  el.style.paddingTop = '';
224
+ el.__virtualizerPlaceholder = false;
204
225
  }
205
226
 
206
227
  if (!this.__preventElementUpdates && (el.__lastUpdatedIndex !== index || forceSameIndexUpdates)) {
@@ -224,6 +245,7 @@ export class IronListAdapter {
224
245
  // Assign a temporary placeholder sizing to elements that would otherwise end up having
225
246
  // no height.
226
247
  el.style.paddingTop = `${this.__placeholderHeight}px`;
248
+ el.__virtualizerPlaceholder = true;
227
249
 
228
250
  // Manually schedule the resize handler to make sure the placeholder padding is
229
251
  // cleared in case the resize observer never triggers.
@@ -241,6 +263,10 @@ export class IronListAdapter {
241
263
  this.__placeholderHeight = Math.round(filteredHeights.reduce((a, b) => a + b, 0) / filteredHeights.length);
242
264
  }
243
265
  });
266
+
267
+ if (this.__pendingScrollToIndex !== undefined && !this.__hasPlaceholders()) {
268
+ this.scrollToIndex(this.__pendingScrollToIndex);
269
+ }
244
270
  }
245
271
 
246
272
  __getIndexScrollOffset(index) {