@vaadin/vaadin-combo-box 21.0.1 → 22.0.0-alpha10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,323 +0,0 @@
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 { ComboBoxPlaceholder } from './vaadin-combo-box-placeholder.js';
7
-
8
- /**
9
- * @polymerMixin
10
- */
11
- export const ComboBoxDataProviderMixin = (superClass) =>
12
- class DataProviderMixin extends superClass {
13
- static get properties() {
14
- return {
15
- /**
16
- * Number of items fetched at a time from the dataprovider.
17
- * @attr {number} page-size
18
- * @type {number}
19
- */
20
- pageSize: {
21
- type: Number,
22
- value: 50,
23
- observer: '_pageSizeChanged'
24
- },
25
-
26
- /**
27
- * Total number of items.
28
- * @type {number | undefined}
29
- */
30
- size: {
31
- type: Number,
32
- observer: '_sizeChanged'
33
- },
34
-
35
- /**
36
- * Function that provides items lazily. Receives arguments `params`, `callback`
37
- *
38
- * `params.page` Requested page index
39
- *
40
- * `params.pageSize` Current page size
41
- *
42
- * `params.filter` Currently applied filter
43
- *
44
- * `callback(items, size)` Callback function with arguments:
45
- * - `items` Current page of items
46
- * - `size` Total number of items.
47
- * @type {ComboBoxDataProvider | undefined}
48
- */
49
- dataProvider: {
50
- type: Object,
51
- observer: '_dataProviderChanged'
52
- },
53
-
54
- /** @private */
55
- _pendingRequests: {
56
- value: () => {
57
- return {};
58
- }
59
- },
60
-
61
- /** @private */
62
- __placeHolder: {
63
- value: new ComboBoxPlaceholder()
64
- }
65
- };
66
- }
67
-
68
- static get observers() {
69
- return [
70
- '_dataProviderFilterChanged(filter, dataProvider)',
71
- '_dataProviderClearFilter(dataProvider, opened, value)',
72
- '_warnDataProviderValue(dataProvider, value)',
73
- '_ensureFirstPage(opened)'
74
- ];
75
- }
76
-
77
- /** @private */
78
- _dataProviderClearFilter(dataProvider, opened, value) {
79
- // Can't depend on filter in this observer as we don't want
80
- // to clear the filter whenever it's set
81
- if (dataProvider && !this.loading && this.filter && !(opened && this.autoOpenDisabled && value === this.filter)) {
82
- this.size = undefined;
83
- this._pendingRequests = {};
84
- this.filter = '';
85
- this.clearCache();
86
- }
87
- }
88
-
89
- /** @protected */
90
- ready() {
91
- super.ready();
92
- this.clearCache();
93
- this.$.overlay.addEventListener('index-requested', (e) => {
94
- const index = e.detail.index;
95
- const currentScrollerPos = e.detail.currentScrollerPos;
96
- const allowedIndexRange = Math.floor(this.pageSize * 1.5);
97
-
98
- // Ignores the indexes, which are being re-sent during scrolling reset,
99
- // if the corresponding page is around the current scroller position.
100
- // Otherwise, there might be a last pages duplicates, which cause the
101
- // loading indicator hanging and blank items
102
- if (this._shouldSkipIndex(index, allowedIndexRange, currentScrollerPos)) {
103
- return;
104
- }
105
-
106
- if (index !== undefined) {
107
- const page = this._getPageForIndex(index);
108
- if (this._shouldLoadPage(page)) {
109
- this._loadPage(page);
110
- }
111
- }
112
- });
113
- }
114
-
115
- /** @private */
116
- _dataProviderFilterChanged() {
117
- if (!this._shouldFetchData()) {
118
- return;
119
- }
120
-
121
- this.size = undefined;
122
- this._pendingRequests = {};
123
- this.clearCache();
124
- }
125
-
126
- /** @private */
127
- _shouldFetchData() {
128
- if (!this.dataProvider) {
129
- return false;
130
- }
131
-
132
- return this.opened || (this.filter && this.filter.length);
133
- }
134
-
135
- /** @private */
136
- _ensureFirstPage(opened) {
137
- if (opened && this._shouldLoadPage(0)) {
138
- this._loadPage(0);
139
- }
140
- }
141
-
142
- /** @private */
143
- _shouldSkipIndex(index, allowedIndexRange, currentScrollerPos) {
144
- return (
145
- currentScrollerPos !== 0 &&
146
- index >= currentScrollerPos - allowedIndexRange &&
147
- index <= currentScrollerPos + allowedIndexRange
148
- );
149
- }
150
-
151
- /** @private */
152
- _shouldLoadPage(page) {
153
- if (!this.filteredItems || this._forceNextRequest) {
154
- this._forceNextRequest = false;
155
- return true;
156
- }
157
-
158
- const loadedItem = this.filteredItems[page * this.pageSize];
159
- if (loadedItem !== undefined) {
160
- return loadedItem instanceof ComboBoxPlaceholder;
161
- } else {
162
- return this.size === undefined;
163
- }
164
- }
165
-
166
- /** @private */
167
- _loadPage(page) {
168
- // make sure same page isn't requested multiple times.
169
- if (!this._pendingRequests[page] && this.dataProvider) {
170
- this.loading = true;
171
-
172
- const params = {
173
- page,
174
- pageSize: this.pageSize,
175
- filter: this.filter
176
- };
177
-
178
- const callback = (items, size) => {
179
- if (this._pendingRequests[page] === callback) {
180
- if (!this.filteredItems) {
181
- const filteredItems = [];
182
- filteredItems.splice(params.page * params.pageSize, items.length, ...items);
183
- this.filteredItems = filteredItems;
184
- } else {
185
- this.splice('filteredItems', params.page * params.pageSize, items.length, ...items);
186
- }
187
- // Update selectedItem from filteredItems if value is set
188
- if (this._isValidValue(this.value) && this._getItemValue(this.selectedItem) !== this.value) {
189
- this._selectItemForValue(this.value);
190
- }
191
- if (!this.opened && !this.hasAttribute('focused')) {
192
- this._commitValue();
193
- }
194
- this.size = size;
195
-
196
- delete this._pendingRequests[page];
197
-
198
- if (Object.keys(this._pendingRequests).length === 0) {
199
- this.loading = false;
200
- }
201
- if (page === 0 && this.__repositionOverlayDebouncer && items.length > (this.__maxRenderedItems || 0)) {
202
- setTimeout(() => this.__repositionOverlayDebouncer.flush());
203
- this.__maxRenderedItems = items.length;
204
- }
205
- }
206
- };
207
-
208
- if (!this._pendingRequests[page]) {
209
- // Don't request page if it's already being requested
210
- this._pendingRequests[page] = callback;
211
- this.dataProvider(params, callback);
212
- }
213
- }
214
- }
215
-
216
- /** @private */
217
- _getPageForIndex(index) {
218
- return Math.floor(index / this.pageSize);
219
- }
220
-
221
- /**
222
- * Clears the cached pages and reloads data from dataprovider when needed.
223
- */
224
- clearCache() {
225
- if (!this.dataProvider) {
226
- return;
227
- }
228
- this._pendingRequests = {};
229
- const filteredItems = [];
230
- for (let i = 0; i < (this.size || 0); i++) {
231
- filteredItems.push(this.__placeHolder);
232
- }
233
- this.filteredItems = filteredItems;
234
- if (this._shouldFetchData()) {
235
- this._loadPage(0);
236
- } else {
237
- this._forceNextRequest = true;
238
- }
239
- }
240
-
241
- /** @private */
242
- _sizeChanged(size = 0) {
243
- const filteredItems = (this.filteredItems || []).slice(0, size);
244
- for (let i = 0; i < size; i++) {
245
- filteredItems[i] = filteredItems[i] !== undefined ? filteredItems[i] : this.__placeHolder;
246
- }
247
- this.filteredItems = filteredItems;
248
-
249
- // Cleans up the redundant pending requests for pages > size
250
- // Refers to https://github.com/vaadin/vaadin-flow-components/issues/229
251
- this._flushPendingRequests(size);
252
- }
253
-
254
- /** @private */
255
- _pageSizeChanged(pageSize, oldPageSize) {
256
- if (Math.floor(pageSize) !== pageSize || pageSize < 1) {
257
- this.pageSize = oldPageSize;
258
- throw new Error('`pageSize` value must be an integer > 0');
259
- }
260
- this.clearCache();
261
- }
262
-
263
- /** @private */
264
- _dataProviderChanged(dataProvider, oldDataProvider) {
265
- this._ensureItemsOrDataProvider(() => {
266
- this.dataProvider = oldDataProvider;
267
- });
268
- }
269
-
270
- /** @private */
271
- _ensureItemsOrDataProvider(restoreOldValueCallback) {
272
- if (this.items !== undefined && this.dataProvider !== undefined) {
273
- restoreOldValueCallback();
274
- throw new Error('Using `items` and `dataProvider` together is not supported');
275
- } else if (this.dataProvider && !this.filteredItems) {
276
- this.filteredItems = [];
277
- }
278
- }
279
-
280
- /** @private */
281
- _warnDataProviderValue(dataProvider, value) {
282
- if (dataProvider && value !== '' && (this.selectedItem === undefined || this.selectedItem === null)) {
283
- const valueIndex = this._indexOfValue(value, this.filteredItems);
284
- if (valueIndex < 0 || !this._getItemLabel(this.filteredItems[valueIndex])) {
285
- /* eslint-disable no-console */
286
- console.warn(
287
- 'Warning: unable to determine the label for the provided `value`. ' +
288
- 'Nothing to display in the text field. This usually happens when ' +
289
- 'setting an initial `value` before any items are returned from ' +
290
- 'the `dataProvider` callback. Consider setting `selectedItem` ' +
291
- 'instead of `value`'
292
- );
293
- /* eslint-enable no-console */
294
- }
295
- }
296
- }
297
-
298
- /**
299
- * This method cleans up the page callbacks which refers to the
300
- * non-existing pages, i.e. which item indexes are greater than the
301
- * changed size.
302
- * This case is basically happens when:
303
- * 1. Users scroll fast to the bottom and combo box generates the
304
- * redundant page request/callback
305
- * 2. Server side uses undefined size lazy loading and suddenly reaches
306
- * the exact size which is on the range edge
307
- * (for default page size = 50, it will be 100, 200, 300, ...).
308
- * @param size the new size of items
309
- * @private
310
- */
311
- _flushPendingRequests(size) {
312
- if (this._pendingRequests) {
313
- const lastPage = Math.ceil(size / this.pageSize);
314
- const pendingRequestsKeys = Object.keys(this._pendingRequests);
315
- for (let reqIdx = 0; reqIdx < pendingRequestsKeys.length; reqIdx++) {
316
- const page = parseInt(pendingRequestsKeys[reqIdx]);
317
- if (page >= lastPage) {
318
- this._pendingRequests[page]([], size);
319
- }
320
- }
321
- }
322
- }
323
- };