@everymatrix/general-input 1.28.7 → 1.28.8

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,111 +1,11 @@
1
- import { i, r as registerStyles, f as defineCustomElement, h as html, g as ThemableMixin, n as DirMixin, P as PolymerElement, m as microTask, R as idlePeriod, U as animationFrame, W as flush, o as Debouncer, X as enqueueDebouncer, t as timeOut, p as generateUniqueId, H as ControllerMixin, V as ValidateMixin, l as FocusMixin, K as KeyboardMixin, I as InputMixin, a as DisabledMixin, N as isElementFocused, c as InputController, e as LabelledInputController, T as TooltipController, E as ElementMixin } from './field-mixin.js';
2
- import { c as overlay, d as menuOverlayCore, P as PositionMixin, O as OverlayMixin, o as overlayStyles, b as OverlayClassMixin, V as VirtualKeyboardController } from './virtual-keyboard-controller.js';
3
- import { i as inputFieldShared, e as isSafari, I as InputConstraintsMixin, f as isTouch, c as InputControlMixin, d as inputFieldShared$1 } from './input-field-shared-styles.js';
4
-
5
- const item = i`
6
- :host {
7
- display: flex;
8
- align-items: center;
9
- box-sizing: border-box;
10
- font-family: var(--lumo-font-family);
11
- font-size: var(--lumo-font-size-m);
12
- line-height: var(--lumo-line-height-xs);
13
- padding: 0.5em calc(var(--lumo-space-l) + var(--lumo-border-radius-m) / 4) 0.5em
14
- var(--_lumo-list-box-item-padding-left, calc(var(--lumo-border-radius-m) / 4));
15
- min-height: var(--lumo-size-m);
16
- outline: none;
17
- border-radius: var(--lumo-border-radius-m);
18
- cursor: var(--lumo-clickable-cursor);
19
- -webkit-font-smoothing: antialiased;
20
- -moz-osx-font-smoothing: grayscale;
21
- -webkit-tap-highlight-color: var(--lumo-primary-color-10pct);
22
- }
23
-
24
- /* Checkmark */
25
- [part='checkmark']::before {
26
- display: var(--_lumo-item-selected-icon-display, none);
27
- content: var(--lumo-icons-checkmark);
28
- font-family: lumo-icons;
29
- font-size: var(--lumo-icon-size-m);
30
- line-height: 1;
31
- font-weight: normal;
32
- width: 1em;
33
- height: 1em;
34
- margin: calc((1 - var(--lumo-line-height-xs)) * var(--lumo-font-size-m) / 2) 0;
35
- color: var(--lumo-primary-text-color);
36
- flex: none;
37
- opacity: 0;
38
- transition: transform 0.2s cubic-bezier(0.12, 0.32, 0.54, 2), opacity 0.1s;
39
- }
40
-
41
- :host([selected]) [part='checkmark']::before {
42
- opacity: 1;
43
- }
44
-
45
- :host([active]:not([selected])) [part='checkmark']::before {
46
- transform: scale(0.8);
47
- opacity: 0;
48
- transition-duration: 0s;
49
- }
50
-
51
- [part='content'] {
52
- flex: auto;
53
- }
54
-
55
- /* Disabled */
56
- :host([disabled]) {
57
- color: var(--lumo-disabled-text-color);
58
- cursor: default;
59
- pointer-events: none;
60
- }
61
-
62
- /* TODO a workaround until we have "focus-follows-mouse". After that, use the hover style for focus-ring as well */
63
- @media (any-hover: hover) {
64
- :host(:hover:not([disabled])) {
65
- background-color: var(--lumo-primary-color-10pct);
66
- }
67
-
68
- :host([focus-ring]:not([disabled])) {
69
- box-shadow: inset 0 0 0 2px var(--lumo-primary-color-50pct);
70
- }
71
- }
72
-
73
- /* RTL specific styles */
74
- :host([dir='rtl']) {
75
- padding-left: calc(var(--lumo-space-l) + var(--lumo-border-radius-m) / 4);
76
- padding-right: var(--_lumo-list-box-item-padding-left, calc(var(--lumo-border-radius-m) / 4));
77
- }
78
-
79
- /* Slotted icons */
80
- :host ::slotted(vaadin-icon) {
81
- width: var(--lumo-icon-size-m);
82
- height: var(--lumo-icon-size-m);
83
- }
84
- `;
85
-
86
- registerStyles('vaadin-item', item, { moduleId: 'lumo-item' });
87
-
88
- const comboBoxItem = i`
89
- :host {
90
- transition: background-color 100ms;
91
- overflow: hidden;
92
- --_lumo-item-selected-icon-display: block;
93
- }
94
-
95
- @media (any-hover: hover) {
96
- :host([focused]:not([disabled])) {
97
- box-shadow: inset 0 0 0 2px var(--lumo-primary-color-50pct);
98
- }
99
- }
100
- `;
101
-
102
- registerStyles('vaadin-combo-box-item', [item, comboBoxItem], {
103
- moduleId: 'lumo-combo-box-item',
104
- });
1
+ import { i, r as registerStyles, T as ThemableMixin, A as DirMixin, P as PolymerElement, h as html, n as microTask, O as idlePeriod, Q as animationFrame, R as flush, y as Debouncer, U as enqueueDebouncer, z as timeOut, W as generateUniqueId, C as ControllerMixin, K as KeyboardMixin, I as InputMixin, a as DisabledMixin, b as isElementFocused, e as InputController, f as LabelledInputController, g as TooltipController, E as ElementMixin } from './field-mixin.js';
2
+ import { o as overlay, d as menuOverlayCore, P as PositionMixin, O as Overlay, V as VirtualKeyboardController } from './virtual-keyboard-controller.js';
3
+ import { i as inputFieldShared, e as isSafari, f as isTouch, c as InputControlMixin, d as inputFieldShared$1 } from './input-field-shared-styles.js';
4
+ import { P as PatternMixin } from './pattern-mixin.js';
105
5
 
106
6
  /**
107
7
  * @license
108
- * Copyright (c) 2022 - 2023 Vaadin Ltd.
8
+ * Copyright (c) 2022 Vaadin Ltd.
109
9
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
110
10
  */
111
11
 
@@ -156,6 +56,14 @@ const comboBoxOverlay = i`
156
56
  padding: 0;
157
57
  }
158
58
 
59
+ :host {
60
+ --_vaadin-combo-box-items-container-border-width: var(--lumo-space-xs);
61
+ --_vaadin-combo-box-items-container-border-style: solid;
62
+ --_vaadin-combo-box-items-container-border-color: transparent;
63
+ }
64
+
65
+ /* Loading state */
66
+
159
67
  /* When items are empty, the spinner needs some room */
160
68
  :host(:not([closing])) [part~='content'] {
161
69
  min-height: calc(2 * var(--lumo-space-s) + var(--lumo-icon-size-s));
@@ -172,9 +80,7 @@ const comboBoxOverlay = i`
172
80
  :host([bottom-aligned]) [part~='overlay'] {
173
81
  margin-bottom: var(--lumo-space-xs);
174
82
  }
175
- `;
176
83
 
177
- const comboBoxLoader = i`
178
84
  [part~='loader'] {
179
85
  position: absolute;
180
86
  z-index: 1;
@@ -186,6 +92,8 @@ const comboBoxLoader = i`
186
92
  margin-inline-end: 0;
187
93
  }
188
94
 
95
+ /* RTL specific styles */
96
+
189
97
  :host([dir='rtl']) [part~='loader'] {
190
98
  left: auto;
191
99
  margin-left: 0;
@@ -195,182 +103,127 @@ const comboBoxLoader = i`
195
103
  }
196
104
  `;
197
105
 
198
- registerStyles(
199
- 'vaadin-combo-box-overlay',
200
- [
201
- overlay,
202
- menuOverlayCore,
203
- comboBoxOverlay,
204
- loader,
205
- comboBoxLoader,
206
- i`
207
- :host {
208
- --_vaadin-combo-box-items-container-border-width: var(--lumo-space-xs);
209
- --_vaadin-combo-box-items-container-border-style: solid;
210
- }
211
- `,
212
- ],
213
- { moduleId: 'lumo-combo-box-overlay' },
214
- );
106
+ registerStyles('vaadin-combo-box-overlay', [overlay, menuOverlayCore, comboBoxOverlay, loader], {
107
+ moduleId: 'lumo-combo-box-overlay',
108
+ });
215
109
 
216
- const comboBox = i`
110
+ const item = i`
217
111
  :host {
112
+ display: flex;
113
+ align-items: center;
114
+ box-sizing: border-box;
115
+ font-family: var(--lumo-font-family);
116
+ font-size: var(--lumo-font-size-m);
117
+ line-height: var(--lumo-line-height-xs);
118
+ padding: 0.5em calc(var(--lumo-space-l) + var(--lumo-border-radius-m) / 4) 0.5em
119
+ var(--_lumo-list-box-item-padding-left, calc(var(--lumo-border-radius-m) / 4));
120
+ min-height: var(--lumo-size-m);
218
121
  outline: none;
122
+ border-radius: var(--lumo-border-radius-m);
123
+ cursor: var(--lumo-clickable-cursor);
124
+ -webkit-font-smoothing: antialiased;
125
+ -moz-osx-font-smoothing: grayscale;
126
+ -webkit-tap-highlight-color: var(--lumo-primary-color-10pct);
219
127
  }
220
128
 
221
- [part='toggle-button']::before {
222
- content: var(--lumo-icons-dropdown);
129
+ /* Checkmark */
130
+ [part='checkmark']::before {
131
+ display: var(--_lumo-item-selected-icon-display, none);
132
+ content: var(--lumo-icons-checkmark);
133
+ font-family: lumo-icons;
134
+ font-size: var(--lumo-icon-size-m);
135
+ line-height: 1;
136
+ font-weight: normal;
137
+ width: 1em;
138
+ height: 1em;
139
+ margin: calc((1 - var(--lumo-line-height-xs)) * var(--lumo-font-size-m) / 2) 0;
140
+ color: var(--lumo-primary-text-color);
141
+ flex: none;
142
+ opacity: 0;
143
+ transition: transform 0.2s cubic-bezier(0.12, 0.32, 0.54, 2), opacity 0.1s;
223
144
  }
224
- `;
225
145
 
226
- registerStyles('vaadin-combo-box', [inputFieldShared, comboBox], { moduleId: 'lumo-combo-box' });
227
-
228
- /**
229
- * @license
230
- * Copyright (c) 2015 - 2023 Vaadin Ltd.
231
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
232
- */
233
-
234
- /**
235
- * @polymerMixin
236
- */
237
- const ComboBoxItemMixin = (superClass) =>
238
- class ComboBoxItemMixinClass extends superClass {
239
- static get properties() {
240
- return {
241
- /**
242
- * The index of the item.
243
- */
244
- index: {
245
- type: Number,
246
- },
247
-
248
- /**
249
- * The item to render.
250
- */
251
- item: {
252
- type: Object,
253
- },
254
-
255
- /**
256
- * The text to render in the item.
257
- */
258
- label: {
259
- type: String,
260
- },
261
-
262
- /**
263
- * True when item is selected.
264
- */
265
- selected: {
266
- type: Boolean,
267
- value: false,
268
- reflectToAttribute: true,
269
- },
146
+ :host([selected]) [part='checkmark']::before {
147
+ opacity: 1;
148
+ }
270
149
 
271
- /**
272
- * True when item is focused.
273
- */
274
- focused: {
275
- type: Boolean,
276
- value: false,
277
- reflectToAttribute: true,
278
- },
150
+ :host([active]:not([selected])) [part='checkmark']::before {
151
+ transform: scale(0.8);
152
+ opacity: 0;
153
+ transition-duration: 0s;
154
+ }
279
155
 
280
- /**
281
- * Custom function for rendering the item content.
282
- */
283
- renderer: {
284
- type: Function,
285
- },
286
- };
287
- }
156
+ [part='content'] {
157
+ flex: auto;
158
+ }
288
159
 
289
- static get observers() {
290
- return ['__rendererOrItemChanged(renderer, index, item.*, selected, focused)', '__updateLabel(label, renderer)'];
291
- }
160
+ /* Disabled */
161
+ :host([disabled]) {
162
+ color: var(--lumo-disabled-text-color);
163
+ cursor: default;
164
+ pointer-events: none;
165
+ }
292
166
 
293
- static get observedAttributes() {
294
- return [...super.observedAttributes, 'hidden'];
167
+ /* TODO a workaround until we have "focus-follows-mouse". After that, use the hover style for focus-ring as well */
168
+ @media (any-hover: hover) {
169
+ :host(:hover:not([disabled])) {
170
+ background-color: var(--lumo-primary-color-10pct);
295
171
  }
296
172
 
297
- attributeChangedCallback(name, oldValue, newValue) {
298
- if (name === 'hidden' && newValue !== null) {
299
- // The element is being hidden (by virtualizer). Mark one of the __rendererOrItemChanged
300
- // dependencies as undefined to make sure it's called when the element is shown again
301
- // and assigned properties with possibly identical values as before hiding.
302
- this.index = undefined;
303
- } else {
304
- super.attributeChangedCallback(name, oldValue, newValue);
305
- }
173
+ :host([focus-ring]:not([disabled])) {
174
+ box-shadow: inset 0 0 0 2px var(--lumo-primary-color-50pct);
306
175
  }
176
+ }
307
177
 
308
- /** @protected */
309
- connectedCallback() {
310
- super.connectedCallback();
311
-
312
- this._owner = this.parentNode.owner;
178
+ /* RTL specific styles */
179
+ :host([dir='rtl']) {
180
+ padding-left: calc(var(--lumo-space-l) + var(--lumo-border-radius-m) / 4);
181
+ padding-right: var(--_lumo-list-box-item-padding-left, calc(var(--lumo-border-radius-m) / 4));
182
+ }
313
183
 
314
- const hostDir = this._owner.getAttribute('dir');
315
- if (hostDir) {
316
- this.setAttribute('dir', hostDir);
317
- }
318
- }
184
+ /* Slotted icons */
185
+ :host ::slotted(vaadin-icon),
186
+ :host ::slotted(iron-icon) {
187
+ width: var(--lumo-icon-size-m);
188
+ height: var(--lumo-icon-size-m);
189
+ }
190
+ `;
319
191
 
320
- /**
321
- * Requests an update for the content of the item.
322
- * While performing the update, it invokes the renderer passed in the `renderer` property.
323
- *
324
- * It is not guaranteed that the update happens immediately (synchronously) after it is requested.
325
- */
326
- requestContentUpdate() {
327
- if (!this.renderer) {
328
- return;
329
- }
192
+ registerStyles('vaadin-item', item, { moduleId: 'lumo-item' });
330
193
 
331
- const model = {
332
- index: this.index,
333
- item: this.item,
334
- focused: this.focused,
335
- selected: this.selected,
336
- };
194
+ const comboBoxItem = i`
195
+ :host {
196
+ transition: background-color 100ms;
197
+ overflow: hidden;
198
+ --_lumo-item-selected-icon-display: block;
199
+ }
337
200
 
338
- this.renderer(this, this._owner, model);
201
+ @media (any-hover: hover) {
202
+ :host([focused]:not([disabled])) {
203
+ box-shadow: inset 0 0 0 2px var(--lumo-primary-color-50pct);
339
204
  }
205
+ }
206
+ `;
340
207
 
341
- /** @private */
342
- __rendererOrItemChanged(renderer, index, item) {
343
- if (item === undefined || index === undefined) {
344
- return;
345
- }
346
-
347
- if (this._oldRenderer !== renderer) {
348
- this.innerHTML = '';
349
- // Whenever a Lit-based renderer is used, it assigns a Lit part to the node it was rendered into.
350
- // When clearing the rendered content, this part needs to be manually disposed of.
351
- // Otherwise, using a Lit-based renderer on the same node will throw an exception or render nothing afterward.
352
- delete this._$litPart$;
353
- }
208
+ registerStyles('vaadin-combo-box-item', [item, comboBoxItem], {
209
+ moduleId: 'lumo-combo-box-item',
210
+ });
354
211
 
355
- if (renderer) {
356
- this._oldRenderer = renderer;
357
- this.requestContentUpdate();
358
- }
359
- }
212
+ const comboBox = i`
213
+ :host {
214
+ outline: none;
215
+ }
360
216
 
361
- /** @private */
362
- __updateLabel(label, renderer) {
363
- if (renderer) {
364
- return;
365
- }
217
+ [part='toggle-button']::before {
218
+ content: var(--lumo-icons-dropdown);
219
+ }
220
+ `;
366
221
 
367
- this.textContent = label;
368
- }
369
- };
222
+ registerStyles('vaadin-combo-box', [inputFieldShared, comboBox], { moduleId: 'lumo-combo-box' });
370
223
 
371
224
  /**
372
225
  * @license
373
- * Copyright (c) 2015 - 2023 Vaadin Ltd.
226
+ * Copyright (c) 2015 - 2022 Vaadin Ltd.
374
227
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
375
228
  */
376
229
 
@@ -393,15 +246,13 @@ const ComboBoxItemMixin = (superClass) =>
393
246
  * `selected` | Set when the item is selected
394
247
  * `focused` | Set when the item is focused
395
248
  *
396
- * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
249
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components) documentation.
397
250
  *
398
- * @customElement
399
- * @mixes ComboBoxItemMixin
400
251
  * @mixes ThemableMixin
401
252
  * @mixes DirMixin
402
253
  * @private
403
254
  */
404
- class ComboBoxItem extends ComboBoxItemMixin(ThemableMixin(DirMixin(PolymerElement))) {
255
+ class ComboBoxItem extends ThemableMixin(DirMixin(PolymerElement)) {
405
256
  static get template() {
406
257
  return html`
407
258
  <style>
@@ -423,142 +274,217 @@ class ComboBoxItem extends ComboBoxItemMixin(ThemableMixin(DirMixin(PolymerEleme
423
274
  static get is() {
424
275
  return 'vaadin-combo-box-item';
425
276
  }
426
- }
427
277
 
428
- defineCustomElement(ComboBoxItem);
278
+ static get properties() {
279
+ return {
280
+ /**
281
+ * The index of the item
282
+ */
283
+ index: Number,
429
284
 
430
- /**
431
- * @license
432
- * Copyright (c) 2015 - 2023 Vaadin Ltd.
433
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
434
- */
285
+ /**
286
+ * The item to render
287
+ * @type {(String|Object)}
288
+ */
289
+ item: Object,
435
290
 
436
- /**
437
- * @polymerMixin
438
- * @mixes PositionMixin
439
- */
440
- const ComboBoxOverlayMixin = (superClass) =>
441
- class ComboBoxOverlayMixin extends PositionMixin(superClass) {
442
- static get observers() {
443
- return ['_setOverlayWidth(positionTarget, opened)'];
444
- }
291
+ /**
292
+ * The text label corresponding to the item
293
+ */
294
+ label: String,
445
295
 
446
- constructor() {
447
- super();
296
+ /**
297
+ * True when item is selected
298
+ */
299
+ selected: {
300
+ type: Boolean,
301
+ value: false,
302
+ reflectToAttribute: true,
303
+ },
448
304
 
449
- this.requiredVerticalSpace = 200;
450
- }
305
+ /**
306
+ * True when item is focused
307
+ */
308
+ focused: {
309
+ type: Boolean,
310
+ value: false,
311
+ reflectToAttribute: true,
312
+ },
451
313
 
452
- /** @protected */
453
- connectedCallback() {
454
- super.connectedCallback();
314
+ /**
315
+ * Custom function for rendering the content of the `<vaadin-combo-box-item>` propagated from the combo box element.
316
+ */
317
+ renderer: Function,
318
+
319
+ /**
320
+ * Saved instance of a custom renderer function.
321
+ */
322
+ _oldRenderer: Function,
323
+ };
324
+ }
455
325
 
456
- const comboBox = this._comboBox;
326
+ static get observers() {
327
+ return ['__rendererOrItemChanged(renderer, index, item.*, selected, focused)', '__updateLabel(label, renderer)'];
328
+ }
457
329
 
458
- const hostDir = comboBox && comboBox.getAttribute('dir');
459
- if (hostDir) {
460
- this.setAttribute('dir', hostDir);
461
- }
330
+ connectedCallback() {
331
+ super.connectedCallback();
332
+
333
+ this._comboBox = this.parentNode.comboBox;
334
+
335
+ const hostDir = this._comboBox.getAttribute('dir');
336
+ if (hostDir) {
337
+ this.setAttribute('dir', hostDir);
462
338
  }
339
+ }
463
340
 
464
- /**
465
- * Override method inherited from `Overlay`
466
- * to not close on position target click.
467
- *
468
- * @param {Event} event
469
- * @return {boolean}
470
- * @protected
471
- */
472
- _shouldCloseOnOutsideClick(event) {
473
- const eventPath = event.composedPath();
474
- return !eventPath.includes(this.positionTarget) && !eventPath.includes(this);
341
+ /**
342
+ * Requests an update for the content of the item.
343
+ * While performing the update, it invokes the renderer passed in the `renderer` property.
344
+ *
345
+ * It is not guaranteed that the update happens immediately (synchronously) after it is requested.
346
+ */
347
+ requestContentUpdate() {
348
+ if (!this.renderer) {
349
+ return;
475
350
  }
476
351
 
477
- /** @private */
478
- _setOverlayWidth(positionTarget, opened) {
479
- if (positionTarget && opened) {
480
- const propPrefix = this.localName;
481
- this.style.setProperty(`--_${propPrefix}-default-width`, `${positionTarget.clientWidth}px`);
352
+ const model = {
353
+ index: this.index,
354
+ item: this.item,
355
+ focused: this.focused,
356
+ selected: this.selected,
357
+ };
358
+
359
+ this.renderer(this, this._comboBox, model);
360
+ }
482
361
 
483
- const customWidth = getComputedStyle(this._comboBox).getPropertyValue(`--${propPrefix}-width`);
362
+ /** @private */
363
+ __rendererOrItemChanged(renderer, index, item) {
364
+ if (item === undefined || index === undefined) {
365
+ return;
366
+ }
484
367
 
485
- if (customWidth === '') {
486
- this.style.removeProperty(`--${propPrefix}-width`);
487
- } else {
488
- this.style.setProperty(`--${propPrefix}-width`, customWidth);
489
- }
368
+ if (this._oldRenderer !== renderer) {
369
+ this.innerHTML = '';
370
+ // Whenever a Lit-based renderer is used, it assigns a Lit part to the node it was rendered into.
371
+ // When clearing the rendered content, this part needs to be manually disposed of.
372
+ // Otherwise, using a Lit-based renderer on the same node will throw an exception or render nothing afterward.
373
+ delete this._$litPart$;
374
+ }
490
375
 
491
- this._updatePosition();
492
- }
376
+ if (renderer) {
377
+ this._oldRenderer = renderer;
378
+ this.requestContentUpdate();
493
379
  }
494
- };
380
+ }
381
+
382
+ /** @private */
383
+ __updateLabel(label, renderer) {
384
+ if (renderer) {
385
+ return;
386
+ }
387
+
388
+ this.textContent = label;
389
+ }
390
+ }
391
+
392
+ customElements.define(ComboBoxItem.is, ComboBoxItem);
495
393
 
496
394
  /**
497
395
  * @license
498
- * Copyright (c) 2015 - 2023 Vaadin Ltd.
396
+ * Copyright (c) 2015 - 2022 Vaadin Ltd.
499
397
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
500
398
  */
501
399
 
502
- const comboBoxOverlayStyles = i`
503
- #overlay {
504
- width: var(--vaadin-combo-box-overlay-width, var(--_vaadin-combo-box-overlay-default-width, auto));
505
- }
400
+ registerStyles(
401
+ 'vaadin-combo-box-overlay',
402
+ i`
403
+ #overlay {
404
+ width: var(--vaadin-combo-box-overlay-width, var(--_vaadin-combo-box-overlay-default-width, auto));
405
+ }
506
406
 
507
- [part='content'] {
508
- display: flex;
509
- flex-direction: column;
510
- height: 100%;
511
- }
512
- `;
407
+ [part='content'] {
408
+ display: flex;
409
+ flex-direction: column;
410
+ height: 100%;
411
+ }
412
+ `,
413
+ { moduleId: 'vaadin-combo-box-overlay-styles' },
414
+ );
513
415
 
514
- registerStyles('vaadin-combo-box-overlay', [overlayStyles, comboBoxOverlayStyles], {
515
- moduleId: 'vaadin-combo-box-overlay-styles',
516
- });
416
+ let memoizedTemplate;
517
417
 
518
418
  /**
519
419
  * An element used internally by `<vaadin-combo-box>`. Not intended to be used separately.
520
420
  *
521
- * @customElement
522
- * @extends HTMLElement
523
- * @mixes ComboBoxOverlayMixin
524
- * @mixes DirMixin
525
- * @mixes OverlayMixin
526
- * @mixes ThemableMixin
421
+ * @extends Overlay
527
422
  * @private
528
423
  */
529
- class ComboBoxOverlay extends ComboBoxOverlayMixin(OverlayMixin(DirMixin(ThemableMixin(PolymerElement)))) {
424
+ class ComboBoxOverlay extends PositionMixin(Overlay) {
530
425
  static get is() {
531
426
  return 'vaadin-combo-box-overlay';
532
427
  }
533
428
 
534
429
  static get template() {
535
- return html`
536
- <div id="backdrop" part="backdrop" hidden></div>
537
- <div part="overlay" id="overlay">
538
- <div part="loader"></div>
539
- <div part="content" id="content"><slot></slot></div>
540
- </div>
541
- `;
430
+ if (!memoizedTemplate) {
431
+ memoizedTemplate = super.template.cloneNode(true);
432
+ memoizedTemplate.content.querySelector('[part~="overlay"]').removeAttribute('tabindex');
433
+ }
434
+
435
+ return memoizedTemplate;
436
+ }
437
+
438
+ static get observers() {
439
+ return ['_setOverlayWidth(positionTarget, opened)'];
440
+ }
441
+
442
+ connectedCallback() {
443
+ super.connectedCallback();
444
+
445
+ const comboBox = this._comboBox;
446
+
447
+ const hostDir = comboBox && comboBox.getAttribute('dir');
448
+ if (hostDir) {
449
+ this.setAttribute('dir', hostDir);
450
+ }
451
+ }
452
+
453
+ ready() {
454
+ super.ready();
455
+ const loader = document.createElement('div');
456
+ loader.setAttribute('part', 'loader');
457
+ const content = this.shadowRoot.querySelector('[part~="content"]');
458
+ content.parentNode.insertBefore(loader, content);
459
+ this.requiredVerticalSpace = 200;
460
+ }
461
+
462
+ _outsideClickListener(event) {
463
+ const eventPath = event.composedPath();
464
+ if (!eventPath.includes(this.positionTarget) && !eventPath.includes(this)) {
465
+ this.close();
466
+ }
467
+ }
468
+
469
+ _setOverlayWidth(positionTarget, opened) {
470
+ if (positionTarget && opened) {
471
+ const propPrefix = this.localName;
472
+ this.style.setProperty(`--_${propPrefix}-default-width`, `${positionTarget.clientWidth}px`);
473
+
474
+ const customWidth = getComputedStyle(this._comboBox).getPropertyValue(`--${propPrefix}-width`);
475
+
476
+ if (customWidth === '') {
477
+ this.style.removeProperty(`--${propPrefix}-width`);
478
+ } else {
479
+ this.style.setProperty(`--${propPrefix}-width`, customWidth);
480
+ }
481
+
482
+ this._updatePosition();
483
+ }
542
484
  }
543
485
  }
544
486
 
545
- defineCustomElement(ComboBoxOverlay);
546
-
547
- /**
548
- * @license
549
- * Copyright (c) 2023 Vaadin Ltd.
550
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
551
- */
552
-
553
- /**
554
- * Convenience method for reading a value from a path.
555
- *
556
- * @param {string} path
557
- * @param {object} object
558
- */
559
- function get(path, object) {
560
- return path.split('.').reduce((obj, property) => (obj ? obj[property] : undefined), object);
561
- }
487
+ customElements.define(ComboBoxOverlay.is, ComboBoxOverlay);
562
488
 
563
489
  /**
564
490
  * @license
@@ -570,7 +496,7 @@ function get(path, object) {
570
496
  * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
571
497
  */
572
498
 
573
- const IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/u);
499
+ const IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/);
574
500
  const IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8;
575
501
  const DEFAULT_PHYSICAL_COUNT = 3;
576
502
 
@@ -1060,12 +986,9 @@ const ironList = {
1060
986
  this._physicalIndexForKey = {};
1061
987
  this._firstVisibleIndexVal = null;
1062
988
  this._lastVisibleIndexVal = null;
1063
- if (!this._physicalItems) {
1064
- this._physicalItems = [];
1065
- }
1066
- if (!this._physicalSizes) {
1067
- this._physicalSizes = [];
1068
- }
989
+ this._physicalCount = this._physicalCount || 0;
990
+ this._physicalItems = this._physicalItems || [];
991
+ this._physicalSizes = this._physicalSizes || [];
1069
992
  this._physicalStart = 0;
1070
993
  if (this._scrollTop > this._scrollOffset) {
1071
994
  this._resetScrollPosition(0);
@@ -1174,21 +1097,16 @@ const ironList = {
1174
1097
  * @param {boolean=} forceUpdate If true, updates the height no matter what.
1175
1098
  */
1176
1099
  _updateScrollerSize(forceUpdate) {
1177
- const estScrollHeight =
1100
+ this._estScrollHeight =
1178
1101
  this._physicalBottom +
1179
1102
  Math.max(this._virtualCount - this._physicalCount - this._virtualStart, 0) * this._physicalAverage;
1180
1103
 
1181
- this._estScrollHeight = estScrollHeight;
1182
-
1104
+ forceUpdate = forceUpdate || this._scrollHeight === 0;
1105
+ forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize;
1183
1106
  // Amortize height adjustment, so it won't trigger large repaints too often.
1184
- if (
1185
- forceUpdate ||
1186
- this._scrollHeight === 0 ||
1187
- this._scrollPosition >= estScrollHeight - this._physicalSize ||
1188
- Math.abs(estScrollHeight - this._scrollHeight) >= this._viewportHeight
1189
- ) {
1190
- this.$.items.style.height = `${estScrollHeight}px`;
1191
- this._scrollHeight = estScrollHeight;
1107
+ if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >= this._viewportHeight) {
1108
+ this.$.items.style.height = `${this._estScrollHeight}px`;
1109
+ this._scrollHeight = this._estScrollHeight;
1192
1110
  }
1193
1111
  },
1194
1112
 
@@ -1284,9 +1202,7 @@ const ironList = {
1284
1202
  },
1285
1203
 
1286
1204
  _debounce(name, cb, asyncModule) {
1287
- if (!this._debouncers) {
1288
- this._debouncers = {};
1289
- }
1205
+ this._debouncers = this._debouncers || {};
1290
1206
  this._debouncers[name] = Debouncer.debounce(this._debouncers[name], asyncModule, cb.bind(this));
1291
1207
  enqueueDebouncer(this._debouncers[name]);
1292
1208
  },
@@ -1294,7 +1210,7 @@ const ironList = {
1294
1210
 
1295
1211
  /**
1296
1212
  * @license
1297
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
1213
+ * Copyright (c) 2021 - 2022 Vaadin Ltd.
1298
1214
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
1299
1215
  */
1300
1216
 
@@ -1424,15 +1340,11 @@ class IronListAdapter {
1424
1340
  }
1425
1341
 
1426
1342
  update(startIndex = 0, endIndex = this.size - 1) {
1427
- const updatedElements = [];
1428
1343
  this.__getVisibleElements().forEach((el) => {
1429
1344
  if (el.__virtualIndex >= startIndex && el.__virtualIndex <= endIndex) {
1430
1345
  this.__updateElement(el, el.__virtualIndex, true);
1431
- updatedElements.push(el);
1432
1346
  }
1433
1347
  });
1434
-
1435
- this.__afterElementsUpdated(updatedElements);
1436
1348
  }
1437
1349
 
1438
1350
  /**
@@ -1495,40 +1407,28 @@ class IronListAdapter {
1495
1407
  this.updateElement(el, index);
1496
1408
  el.__lastUpdatedIndex = index;
1497
1409
  }
1498
- }
1499
1410
 
1500
- /**
1501
- * Called synchronously right after elements have been updated.
1502
- * This is a good place to do any post-update work.
1503
- *
1504
- * @param {!Array<!HTMLElement>} updatedElements
1505
- */
1506
- __afterElementsUpdated(updatedElements) {
1507
- updatedElements.forEach((el) => {
1508
- const elementHeight = el.offsetHeight;
1509
- if (elementHeight === 0) {
1510
- // If the elements have 0 height after update (for example due to lazy rendering),
1511
- // it results in iron-list requesting to create an unlimited count of elements.
1512
- // Assign a temporary placeholder sizing to elements that would otherwise end up having
1513
- // no height.
1514
- el.style.paddingTop = `${this.__placeholderHeight}px`;
1515
-
1516
- // Manually schedule the resize handler to make sure the placeholder padding is
1517
- // cleared in case the resize observer never triggers.
1518
- this.__placeholderClearDebouncer = Debouncer.debounce(this.__placeholderClearDebouncer, animationFrame, () =>
1519
- this._resizeHandler(),
1520
- );
1521
- } else {
1522
- // Add element height to the queue
1523
- this.__elementHeightQueue.push(elementHeight);
1524
- this.__elementHeightQueue.shift();
1525
-
1526
- // Calculate new placeholder height based on the average of the defined values in the
1527
- // element height queue
1528
- const filteredHeights = this.__elementHeightQueue.filter((h) => h !== undefined);
1529
- this.__placeholderHeight = Math.round(filteredHeights.reduce((a, b) => a + b, 0) / filteredHeights.length);
1530
- }
1531
- });
1411
+ const elementHeight = el.offsetHeight;
1412
+ if (elementHeight === 0) {
1413
+ // If the elements have 0 height after update (for example due to lazy rendering),
1414
+ // it results in iron-list requesting to create an unlimited count of elements.
1415
+ // Assign a temporary placeholder sizing to elements that would otherwise end up having
1416
+ // no height.
1417
+ el.style.paddingTop = `${this.__placeholderHeight}px`;
1418
+
1419
+ // Manually schedule the resize handler to make sure the placeholder padding is
1420
+ // cleared in case the resize observer never triggers.
1421
+ requestAnimationFrame(() => this._resizeHandler());
1422
+ } else {
1423
+ // Add element height to the queue
1424
+ this.__elementHeightQueue.push(elementHeight);
1425
+ this.__elementHeightQueue.shift();
1426
+
1427
+ // Calcualte new placeholder height based on the average of the defined values in the
1428
+ // element height queue
1429
+ const filteredHeights = this.__elementHeightQueue.filter((h) => h !== undefined);
1430
+ this.__placeholderHeight = Math.round(filteredHeights.reduce((a, b) => a + b, 0) / filteredHeights.length);
1431
+ }
1532
1432
  }
1533
1433
 
1534
1434
  __getIndexScrollOffset(index) {
@@ -1553,37 +1453,42 @@ class IronListAdapter {
1553
1453
  this._debouncers._increasePoolIfNeeded.cancel();
1554
1454
  }
1555
1455
 
1456
+ // Prevent element update while the scroll position is being restored
1457
+ this.__preventElementUpdates = true;
1458
+
1459
+ // Record the scroll position before changing the size
1460
+ let fvi; // First visible index
1461
+ let fviOffsetBefore; // Scroll offset of the first visible index
1462
+ if (size > 0) {
1463
+ fvi = this.adjustedFirstVisibleIndex;
1464
+ fviOffsetBefore = this.__getIndexScrollOffset(fvi);
1465
+ }
1466
+
1556
1467
  // Change the size
1557
1468
  this.__size = size;
1558
1469
 
1559
- if (!this._physicalItems) {
1560
- // Not initialized yet
1561
- this._itemsChanged({
1562
- path: 'items',
1563
- });
1564
- this.__preventElementUpdates = true;
1565
- flush();
1566
- this.__preventElementUpdates = false;
1567
- } else {
1568
- // Already initialized, just update _virtualCount
1569
- this._updateScrollerSize();
1570
- this._virtualCount = this.items.length;
1571
- this._render();
1572
- }
1470
+ this._itemsChanged({
1471
+ path: 'items',
1472
+ });
1473
+ flush();
1474
+
1475
+ // Try to restore the scroll position if the new size is larger than 0
1476
+ if (size > 0) {
1477
+ fvi = Math.min(fvi, size - 1);
1478
+ this.scrollToIndex(fvi);
1573
1479
 
1574
- // When reducing size while invisible, iron-list does not update items, so
1575
- // their hidden state is not updated and their __lastUpdatedIndex is not
1576
- // reset. In that case force an update here.
1577
- if (!this._isVisible) {
1578
- this._assignModels();
1480
+ const fviOffsetAfter = this.__getIndexScrollOffset(fvi);
1481
+ if (fviOffsetBefore !== undefined && fviOffsetAfter !== undefined) {
1482
+ this._scrollTop += fviOffsetBefore - fviOffsetAfter;
1483
+ }
1579
1484
  }
1580
1485
 
1581
1486
  if (!this.elementsContainer.children.length) {
1582
1487
  requestAnimationFrame(() => this._resizeHandler());
1583
1488
  }
1584
1489
 
1585
- // Schedule and flush a resize handler. This will cause a
1586
- // re-render for the elements.
1490
+ this.__preventElementUpdates = false;
1491
+ // Schedule and flush a resize handler
1587
1492
  this._resizeHandler();
1588
1493
  flush();
1589
1494
  }
@@ -1648,20 +1553,16 @@ class IronListAdapter {
1648
1553
 
1649
1554
  /** @private */
1650
1555
  _assignModels(itemSet) {
1651
- const updatedElements = [];
1652
1556
  this._iterateItems((pidx, vidx) => {
1653
1557
  const el = this._physicalItems[pidx];
1654
1558
  el.hidden = vidx >= this.size;
1655
1559
  if (!el.hidden) {
1656
1560
  el.__virtualIndex = vidx + (this._vidxOffset || 0);
1657
1561
  this.__updateElement(el, el.__virtualIndex);
1658
- updatedElements.push(el);
1659
1562
  } else {
1660
1563
  delete el.__lastUpdatedIndex;
1661
1564
  }
1662
1565
  }, itemSet);
1663
-
1664
- this.__afterElementsUpdated(updatedElements);
1665
1566
  }
1666
1567
 
1667
1568
  /** @private */
@@ -1790,9 +1691,7 @@ class IronListAdapter {
1790
1691
  deltaY *= this._scrollPageHeight;
1791
1692
  }
1792
1693
 
1793
- if (!this._deltaYAcc) {
1794
- this._deltaYAcc = 0;
1795
- }
1694
+ this._deltaYAcc = this._deltaYAcc || 0;
1796
1695
 
1797
1696
  if (this._wheelAnimationFrame) {
1798
1697
  // Accumulate wheel delta while a frame is being processed
@@ -1865,29 +1764,6 @@ class IronListAdapter {
1865
1764
  );
1866
1765
  }
1867
1766
 
1868
- /**
1869
- * Increases the pool size.
1870
- * @override
1871
- */
1872
- _increasePoolIfNeeded(count) {
1873
- if (this._physicalCount > 2 && count) {
1874
- // The iron-list logic has already created some physical items and
1875
- // has decided to create more. Since each item creation round is
1876
- // expensive, let's try to create the remaining items in one go.
1877
-
1878
- // Calculate the total item count that would be needed to fill the viewport
1879
- // plus the buffer assuming rest of the items to be of the average size
1880
- // of the items already created.
1881
- const totalItemCount = Math.ceil(this._optPhysicalSize / this._physicalAverage);
1882
- const missingItemCount = totalItemCount - this._physicalCount;
1883
- // Create the remaining items in one go. Use a maximum of 100 items
1884
- // as a safety measure.
1885
- super._increasePoolIfNeeded(Math.max(count, Math.min(100, missingItemCount)));
1886
- } else {
1887
- super._increasePoolIfNeeded(count);
1888
- }
1889
- }
1890
-
1891
1767
  /**
1892
1768
  * @returns {Number|undefined} - The browser's default font-size in pixels
1893
1769
  * @private
@@ -2017,24 +1893,6 @@ class Virtualizer {
2017
1893
  this.__adapter = new IronListAdapter(config);
2018
1894
  }
2019
1895
 
2020
- /**
2021
- * Gets the index of the first visible item in the viewport.
2022
- *
2023
- * @return {number}
2024
- */
2025
- get firstVisibleIndex() {
2026
- return this.__adapter.adjustedFirstVisibleIndex;
2027
- }
2028
-
2029
- /**
2030
- * Gets the index of the last visible item in the viewport.
2031
- *
2032
- * @return {number}
2033
- */
2034
- get lastVisibleIndex() {
2035
- return this.__adapter.adjustedLastVisibleIndex;
2036
- }
2037
-
2038
1896
  /**
2039
1897
  * The size of the virtualizer
2040
1898
  * @return {number | undefined} The size of the virtualizer
@@ -2082,11 +1940,29 @@ class Virtualizer {
2082
1940
  flush() {
2083
1941
  this.__adapter.flush();
2084
1942
  }
1943
+
1944
+ /**
1945
+ * Gets the index of the first visible item in the viewport.
1946
+ *
1947
+ * @return {number}
1948
+ */
1949
+ get firstVisibleIndex() {
1950
+ return this.__adapter.adjustedFirstVisibleIndex;
1951
+ }
1952
+
1953
+ /**
1954
+ * Gets the index of the last visible item in the viewport.
1955
+ *
1956
+ * @return {number}
1957
+ */
1958
+ get lastVisibleIndex() {
1959
+ return this.__adapter.adjustedLastVisibleIndex;
1960
+ }
2085
1961
  }
2086
1962
 
2087
1963
  /**
2088
1964
  * @license
2089
- * Copyright (c) 2015 - 2023 Vaadin Ltd.
1965
+ * Copyright (c) 2015 - 2022 Vaadin Ltd.
2090
1966
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
2091
1967
  */
2092
1968
 
@@ -2103,454 +1979,394 @@ const ComboBoxPlaceholder = class ComboBoxPlaceholder {
2103
1979
 
2104
1980
  /**
2105
1981
  * @license
2106
- * Copyright (c) 2015 - 2023 Vaadin Ltd.
1982
+ * Copyright (c) 2015 - 2022 Vaadin Ltd.
2107
1983
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
2108
1984
  */
2109
1985
 
2110
1986
  /**
2111
- * @polymerMixin
1987
+ * Element for internal use only.
1988
+ *
1989
+ * @extends HTMLElement
1990
+ * @private
2112
1991
  */
2113
- const ComboBoxScrollerMixin = (superClass) =>
2114
- class ComboBoxScrollerMixin extends superClass {
2115
- static get properties() {
2116
- return {
2117
- /**
2118
- * A full set of items to filter the visible options from.
2119
- * Set to an empty array when combo-box is not opened.
2120
- */
2121
- items: {
2122
- type: Array,
2123
- observer: '__itemsChanged',
2124
- },
2125
-
2126
- /**
2127
- * Index of an item that has focus outline and is scrolled into view.
2128
- * The actual focus still remains in the input field.
2129
- */
2130
- focusedIndex: {
2131
- type: Number,
2132
- observer: '__focusedIndexChanged',
2133
- },
2134
-
2135
- /**
2136
- * Set to true while combo-box fetches new page from the data provider.
2137
- */
2138
- loading: {
2139
- type: Boolean,
2140
- observer: '__loadingChanged',
2141
- },
2142
-
2143
- /**
2144
- * Whether the combo-box is currently opened or not. If set to false,
2145
- * calling `scrollIntoView` does not have any effect.
2146
- */
2147
- opened: {
2148
- type: Boolean,
2149
- observer: '__openedChanged',
2150
- },
1992
+ class ComboBoxScroller extends PolymerElement {
1993
+ static get is() {
1994
+ return 'vaadin-combo-box-scroller';
1995
+ }
2151
1996
 
2152
- /**
2153
- * The selected item from the `items` array.
2154
- */
2155
- selectedItem: {
2156
- type: Object,
2157
- observer: '__selectedItemChanged',
2158
- },
1997
+ static get template() {
1998
+ return html`
1999
+ <style>
2000
+ :host {
2001
+ display: block;
2002
+ min-height: 1px;
2003
+ overflow: auto;
2159
2004
 
2160
- /**
2161
- * Path for the id of the item, used to detect whether the item is selected.
2162
- */
2163
- itemIdPath: {
2164
- type: String,
2165
- },
2005
+ /* Fixes item background from getting on top of scrollbars on Safari */
2006
+ transform: translate3d(0, 0, 0);
2166
2007
 
2167
- /**
2168
- * Reference to the owner (combo-box owner), used by the item elements.
2169
- */
2170
- owner: {
2171
- type: Object,
2172
- },
2008
+ /* Enable momentum scrolling on iOS */
2009
+ -webkit-overflow-scrolling: touch;
2173
2010
 
2174
- /**
2175
- * Function used to set a label for every combo-box item.
2176
- */
2177
- getItemLabel: {
2178
- type: Object,
2179
- },
2011
+ /* Fixes scrollbar disappearing when 'Show scroll bars: Always' enabled in Safari */
2012
+ box-shadow: 0 0 0 white;
2013
+ }
2180
2014
 
2181
- /**
2182
- * Function used to render the content of every combo-box item.
2183
- */
2184
- renderer: {
2185
- type: Object,
2186
- observer: '__rendererChanged',
2187
- },
2015
+ #selector {
2016
+ border-width: var(--_vaadin-combo-box-items-container-border-width);
2017
+ border-style: var(--_vaadin-combo-box-items-container-border-style);
2018
+ border-color: var(--_vaadin-combo-box-items-container-border-color);
2019
+ position: relative;
2020
+ }
2021
+ </style>
2022
+ <div id="selector">
2023
+ <slot></slot>
2024
+ </div>
2025
+ `;
2026
+ }
2188
2027
 
2189
- /**
2190
- * Used to propagate the `theme` attribute from the host element.
2191
- */
2192
- theme: {
2193
- type: String,
2194
- },
2195
- };
2196
- }
2028
+ static get properties() {
2029
+ return {
2030
+ /**
2031
+ * A full set of items to filter the visible options from.
2032
+ * Set to an empty array when combo-box is not opened.
2033
+ */
2034
+ items: {
2035
+ type: Array,
2036
+ observer: '__itemsChanged',
2037
+ },
2197
2038
 
2198
- constructor() {
2199
- super();
2200
- this.__boundOnItemClick = this.__onItemClick.bind(this);
2201
- }
2039
+ /**
2040
+ * Index of an item that has focus outline and is scrolled into view.
2041
+ * The actual focus still remains in the input field.
2042
+ */
2043
+ focusedIndex: {
2044
+ type: Number,
2045
+ observer: '__focusedIndexChanged',
2046
+ },
2202
2047
 
2203
- /** @private */
2204
- get _viewportTotalPaddingBottom() {
2205
- if (this._cachedViewportTotalPaddingBottom === undefined) {
2206
- const itemsStyle = window.getComputedStyle(this.$.selector);
2207
- this._cachedViewportTotalPaddingBottom = [itemsStyle.paddingBottom, itemsStyle.borderBottomWidth]
2208
- .map((v) => {
2209
- return parseInt(v, 10);
2210
- })
2211
- .reduce((sum, v) => {
2212
- return sum + v;
2213
- });
2214
- }
2048
+ /**
2049
+ * Set to true while combo-box fetches new page from the data provider.
2050
+ */
2051
+ loading: {
2052
+ type: Boolean,
2053
+ observer: '__loadingChanged',
2054
+ },
2215
2055
 
2216
- return this._cachedViewportTotalPaddingBottom;
2217
- }
2056
+ /**
2057
+ * Whether the combo-box is currently opened or not. If set to false,
2058
+ * calling `scrollIntoView` does not have any effect.
2059
+ */
2060
+ opened: {
2061
+ type: Boolean,
2062
+ observer: '__openedChanged',
2063
+ },
2218
2064
 
2219
- /** @protected */
2220
- ready() {
2221
- super.ready();
2065
+ /**
2066
+ * The selected item from the `items` array.
2067
+ */
2068
+ selectedItem: {
2069
+ type: Object,
2070
+ observer: '__selectedItemChanged',
2071
+ },
2222
2072
 
2223
- this.setAttribute('role', 'listbox');
2073
+ /**
2074
+ * Path for the id of the item, used to detect whether the item is selected.
2075
+ */
2076
+ itemIdPath: {
2077
+ type: String,
2078
+ },
2224
2079
 
2225
- // Ensure every instance has unique ID
2226
- this.id = `${this.localName}-${generateUniqueId()}`;
2080
+ /**
2081
+ * Reference to the combo-box, used by the item elements.
2082
+ */
2083
+ comboBox: {
2084
+ type: Object,
2085
+ },
2227
2086
 
2228
- // Allow extensions to customize tag name for the items
2229
- this.__hostTagName = this.constructor.is.replace('-scroller', '');
2087
+ /**
2088
+ * Function used to set a label for every combo-box item.
2089
+ */
2090
+ getItemLabel: {
2091
+ type: Object,
2092
+ },
2230
2093
 
2231
- this.addEventListener('click', (e) => e.stopPropagation());
2094
+ /**
2095
+ * Function used to render the content of every combo-box item.
2096
+ */
2097
+ renderer: {
2098
+ type: Object,
2099
+ observer: '__rendererChanged',
2100
+ },
2232
2101
 
2233
- this.__patchWheelOverScrolling();
2102
+ /**
2103
+ * Used to propagate the `theme` attribute from the host element.
2104
+ */
2105
+ theme: {
2106
+ type: String,
2107
+ },
2108
+ };
2109
+ }
2234
2110
 
2235
- this.__virtualizer = new Virtualizer({
2236
- createElements: this.__createElements.bind(this),
2237
- updateElement: this._updateElement.bind(this),
2238
- elementsContainer: this,
2239
- scrollTarget: this,
2240
- scrollContainer: this.$.selector,
2241
- });
2242
- }
2111
+ constructor() {
2112
+ super();
2113
+ this.__boundOnItemClick = this.__onItemClick.bind(this);
2114
+ }
2243
2115
 
2244
- /**
2245
- * Requests an update for the virtualizer to re-render items.
2246
- */
2247
- requestContentUpdate() {
2248
- if (this.__virtualizer) {
2249
- this.__virtualizer.update();
2250
- }
2116
+ __openedChanged(opened) {
2117
+ if (opened) {
2118
+ this.requestContentUpdate();
2251
2119
  }
2120
+ }
2252
2121
 
2253
- /**
2254
- * Scrolls an item at given index into view and adjusts `scrollTop`
2255
- * so that the element gets fully visible on Arrow Down key press.
2256
- * @param {number} index
2257
- */
2258
- scrollIntoView(index) {
2259
- if (!(this.opened && index >= 0)) {
2260
- return;
2261
- }
2262
-
2263
- const visibleItemsCount = this._visibleItemsCount();
2122
+ /** @protected */
2123
+ ready() {
2124
+ super.ready();
2264
2125
 
2265
- let targetIndex = index;
2126
+ // Ensure every instance has unique ID
2127
+ this.id = `${this.localName}-${generateUniqueId()}`;
2266
2128
 
2267
- if (index > this.__virtualizer.lastVisibleIndex - 1) {
2268
- // Index is below the bottom, scrolling down. Make the item appear at the bottom.
2269
- // First scroll to target (will be at the top of the scroller) to make sure it's rendered.
2270
- this.__virtualizer.scrollToIndex(index);
2271
- // Then calculate the index for the following scroll (to get the target to bottom of the scroller).
2272
- targetIndex = index - visibleItemsCount + 1;
2273
- } else if (index > this.__virtualizer.firstVisibleIndex) {
2274
- // The item is already visible, scrolling is unnecessary per se. But we need to trigger iron-list to set
2275
- // the correct scrollTop on the scrollTarget. Scrolling to firstVisibleIndex.
2276
- targetIndex = this.__virtualizer.firstVisibleIndex;
2277
- }
2278
- this.__virtualizer.scrollToIndex(Math.max(0, targetIndex));
2129
+ // Allow extensions to customize tag name for the items
2130
+ this.__hostTagName = this.constructor.is.replace('-scroller', '');
2279
2131
 
2280
- // Sometimes the item is partly below the bottom edge, detect and adjust.
2281
- const lastPhysicalItem = [...this.children].find(
2282
- (el) => !el.hidden && el.index === this.__virtualizer.lastVisibleIndex,
2283
- );
2284
- if (!lastPhysicalItem || index !== lastPhysicalItem.index) {
2285
- return;
2286
- }
2287
- const lastPhysicalItemRect = lastPhysicalItem.getBoundingClientRect();
2288
- const scrollerRect = this.getBoundingClientRect();
2289
- const scrollTopAdjust = lastPhysicalItemRect.bottom - scrollerRect.bottom + this._viewportTotalPaddingBottom;
2290
- if (scrollTopAdjust > 0) {
2291
- this.scrollTop += scrollTopAdjust;
2292
- }
2293
- }
2132
+ this.setAttribute('role', 'listbox');
2294
2133
 
2295
- /**
2296
- * @param {string | object} item
2297
- * @param {string | object} selectedItem
2298
- * @param {string} itemIdPath
2299
- * @protected
2300
- */
2301
- _isItemSelected(item, selectedItem, itemIdPath) {
2302
- if (item instanceof ComboBoxPlaceholder) {
2303
- return false;
2304
- } else if (itemIdPath && item !== undefined && selectedItem !== undefined) {
2305
- return get(itemIdPath, item) === get(itemIdPath, selectedItem);
2306
- }
2307
- return item === selectedItem;
2308
- }
2134
+ this.addEventListener('click', (e) => e.stopPropagation());
2309
2135
 
2310
- /** @private */
2311
- __itemsChanged(items) {
2312
- if (this.__virtualizer && items) {
2313
- this.__virtualizer.size = items.length;
2314
- this.__virtualizer.flush();
2315
- this.requestContentUpdate();
2316
- }
2317
- }
2136
+ this.__patchWheelOverScrolling();
2318
2137
 
2319
- /** @private */
2320
- __loadingChanged() {
2321
- this.requestContentUpdate();
2322
- }
2138
+ this.__virtualizer = new Virtualizer({
2139
+ createElements: this.__createElements.bind(this),
2140
+ updateElement: this.__updateElement.bind(this),
2141
+ elementsContainer: this,
2142
+ scrollTarget: this,
2143
+ scrollContainer: this.$.selector,
2144
+ });
2145
+ }
2323
2146
 
2324
- /** @private */
2325
- __openedChanged(opened) {
2326
- if (opened) {
2327
- this.requestContentUpdate();
2328
- }
2147
+ requestContentUpdate() {
2148
+ if (this.__virtualizer) {
2149
+ this.__virtualizer.update();
2329
2150
  }
2151
+ }
2330
2152
 
2331
- /** @private */
2332
- __selectedItemChanged() {
2333
- this.requestContentUpdate();
2153
+ scrollIntoView(index) {
2154
+ if (!(this.opened && index >= 0)) {
2155
+ return;
2334
2156
  }
2335
2157
 
2336
- /** @private */
2337
- __focusedIndexChanged(index, oldIndex) {
2338
- if (index !== oldIndex) {
2339
- this.requestContentUpdate();
2340
- }
2158
+ const visibleItemsCount = this._visibleItemsCount();
2341
2159
 
2342
- // Do not jump back to the previously focused item while loading
2343
- // when requesting next page from the data provider on scroll.
2344
- if (index >= 0 && !this.loading) {
2345
- this.scrollIntoView(index);
2346
- }
2347
- }
2160
+ let targetIndex = index;
2348
2161
 
2349
- /** @private */
2350
- __rendererChanged(renderer, oldRenderer) {
2351
- if (renderer || oldRenderer) {
2352
- this.requestContentUpdate();
2353
- }
2162
+ if (index > this.__virtualizer.lastVisibleIndex - 1) {
2163
+ // Index is below the bottom, scrolling down. Make the item appear at the bottom.
2164
+ // First scroll to target (will be at the top of the scroller) to make sure it's rendered.
2165
+ this.__virtualizer.scrollToIndex(index);
2166
+ // Then calculate the index for the following scroll (to get the target to bottom of the scroller).
2167
+ targetIndex = index - visibleItemsCount + 1;
2168
+ } else if (index > this.__virtualizer.firstVisibleIndex) {
2169
+ // The item is already visible, scrolling is unnecessary per se. But we need to trigger iron-list to set
2170
+ // the correct scrollTop on the scrollTarget. Scrolling to firstVisibleIndex.
2171
+ targetIndex = this.__virtualizer.firstVisibleIndex;
2354
2172
  }
2173
+ this.__virtualizer.scrollToIndex(Math.max(0, targetIndex));
2355
2174
 
2356
- /** @private */
2357
- __createElements(count) {
2358
- return [...Array(count)].map(() => {
2359
- const item = document.createElement(`${this.__hostTagName}-item`);
2360
- item.addEventListener('click', this.__boundOnItemClick);
2361
- // Negative tabindex prevents the item content from being focused.
2362
- item.tabIndex = '-1';
2363
- item.style.width = '100%';
2364
- return item;
2365
- });
2175
+ // Sometimes the item is partly below the bottom edge, detect and adjust.
2176
+ const lastPhysicalItem = [...this.children].find(
2177
+ (el) => !el.hidden && el.index === this.__virtualizer.lastVisibleIndex,
2178
+ );
2179
+ if (!lastPhysicalItem || index !== lastPhysicalItem.index) {
2180
+ return;
2366
2181
  }
2182
+ const lastPhysicalItemRect = lastPhysicalItem.getBoundingClientRect();
2183
+ const scrollerRect = this.getBoundingClientRect();
2184
+ const scrollTopAdjust = lastPhysicalItemRect.bottom - scrollerRect.bottom + this._viewportTotalPaddingBottom;
2185
+ if (scrollTopAdjust > 0) {
2186
+ this.scrollTop += scrollTopAdjust;
2187
+ }
2188
+ }
2367
2189
 
2368
- /**
2369
- * @param {HTMLElement} el
2370
- * @param {number} index
2371
- * @protected
2372
- */
2373
- _updateElement(el, index) {
2374
- const item = this.items[index];
2375
- const focusedIndex = this.focusedIndex;
2376
- const selected = this._isItemSelected(item, this.selectedItem, this.itemIdPath);
2377
-
2378
- el.setProperties({
2379
- item,
2380
- index,
2381
- label: this.getItemLabel(item),
2382
- selected,
2383
- renderer: this.renderer,
2384
- focused: !this.loading && focusedIndex === index,
2385
- });
2386
-
2387
- el.id = `${this.__hostTagName}-item-${index}`;
2388
- el.setAttribute('role', index !== undefined ? 'option' : false);
2389
- el.setAttribute('aria-selected', selected.toString());
2390
- el.setAttribute('aria-posinset', index + 1);
2391
- el.setAttribute('aria-setsize', this.items.length);
2190
+ /** @private */
2191
+ __getAriaRole(itemIndex) {
2192
+ return itemIndex !== undefined ? 'option' : false;
2193
+ }
2392
2194
 
2393
- if (this.theme) {
2394
- el.setAttribute('theme', this.theme);
2395
- } else {
2396
- el.removeAttribute('theme');
2397
- }
2195
+ /** @private */
2196
+ __isItemFocused(focusedIndex, itemIndex) {
2197
+ return !this.loading && focusedIndex === itemIndex;
2198
+ }
2398
2199
 
2399
- if (item instanceof ComboBoxPlaceholder) {
2400
- this.__requestItemByIndex(index);
2401
- }
2200
+ /** @protected */
2201
+ _isItemSelected(item, selectedItem, itemIdPath) {
2202
+ if (item instanceof ComboBoxPlaceholder) {
2203
+ return false;
2204
+ } else if (itemIdPath && item !== undefined && selectedItem !== undefined) {
2205
+ return this.get(itemIdPath, item) === this.get(itemIdPath, selectedItem);
2402
2206
  }
2207
+ return item === selectedItem;
2208
+ }
2403
2209
 
2404
- /** @private */
2405
- __onItemClick(e) {
2406
- this.dispatchEvent(new CustomEvent('selection-changed', { detail: { item: e.currentTarget.item } }));
2210
+ /** @private */
2211
+ __itemsChanged(items) {
2212
+ if (this.__virtualizer && items) {
2213
+ this.__virtualizer.size = items.length;
2214
+ this.__virtualizer.flush();
2215
+ this.requestContentUpdate();
2407
2216
  }
2217
+ }
2408
2218
 
2409
- /**
2410
- * We want to prevent the kinetic scrolling energy from being transferred from the overlay contents over to the parent.
2411
- * Further improvement ideas: after the contents have been scrolled to the top or bottom and scrolling has stopped, it could allow
2412
- * scrolling the parent similarly to touch scrolling.
2413
- * @private
2414
- */
2415
- __patchWheelOverScrolling() {
2416
- this.$.selector.addEventListener('wheel', (e) => {
2417
- const scrolledToTop = this.scrollTop === 0;
2418
- const scrolledToBottom = this.scrollHeight - this.scrollTop - this.clientHeight <= 1;
2419
- if (scrolledToTop && e.deltaY < 0) {
2420
- e.preventDefault();
2421
- } else if (scrolledToBottom && e.deltaY > 0) {
2422
- e.preventDefault();
2423
- }
2424
- });
2425
- }
2219
+ /** @private */
2220
+ __loadingChanged() {
2221
+ this.requestContentUpdate();
2222
+ }
2426
2223
 
2427
- /**
2428
- * Dispatches an `index-requested` event for the given index to notify
2429
- * the data provider that it should start loading the page containing the requested index.
2430
- *
2431
- * The event is dispatched asynchronously to prevent an immediate page request and therefore
2432
- * a possible infinite recursion in case the data provider implements page request cancelation logic
2433
- * by invoking data provider page callbacks with an empty array.
2434
- * The infinite recursion may occur otherwise since invoking a data provider page callback with an empty array
2435
- * triggers a synchronous scroller update and, if the callback corresponds to the currently visible page,
2436
- * the scroller will synchronously request the page again which may lead to looping in the end.
2437
- * That was the case for the Flow counterpart:
2438
- * https://github.com/vaadin/flow-components/issues/3553#issuecomment-1239344828
2439
- * @private
2440
- */
2441
- __requestItemByIndex(index) {
2442
- requestAnimationFrame(() => {
2443
- this.dispatchEvent(
2444
- new CustomEvent('index-requested', {
2445
- detail: {
2446
- index,
2447
- currentScrollerPos: this._oldScrollerPosition,
2448
- },
2449
- }),
2450
- );
2451
- });
2452
- }
2224
+ /** @private */
2225
+ __selectedItemChanged() {
2226
+ this.requestContentUpdate();
2227
+ }
2453
2228
 
2454
- /** @private */
2455
- _visibleItemsCount() {
2456
- // Ensure items are positioned
2457
- this.__virtualizer.scrollToIndex(this.__virtualizer.firstVisibleIndex);
2458
- const hasItems = this.__virtualizer.size > 0;
2459
- return hasItems ? this.__virtualizer.lastVisibleIndex - this.__virtualizer.firstVisibleIndex + 1 : 0;
2229
+ /** @private */
2230
+ __focusedIndexChanged(index, oldIndex) {
2231
+ if (index !== oldIndex) {
2232
+ this.requestContentUpdate();
2460
2233
  }
2461
- };
2462
2234
 
2463
- /**
2464
- * @license
2465
- * Copyright (c) 2015 - 2023 Vaadin Ltd.
2466
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
2467
- */
2235
+ // Do not jump back to the previously focused item while loading
2236
+ // when requesting next page from the data provider on scroll.
2237
+ if (index >= 0 && !this.loading) {
2238
+ this.scrollIntoView(index);
2239
+ }
2240
+ }
2468
2241
 
2469
- /**
2470
- * An element used internally by `<vaadin-combo-box>`. Not intended to be used separately.
2471
- *
2472
- * @customElement
2473
- * @extends HTMLElement
2474
- * @mixes ComboBoxScrollerMixin
2475
- * @private
2476
- */
2477
- class ComboBoxScroller extends ComboBoxScrollerMixin(PolymerElement) {
2478
- static get is() {
2479
- return 'vaadin-combo-box-scroller';
2242
+ /** @private */
2243
+ __rendererChanged(renderer, oldRenderer) {
2244
+ if (renderer || oldRenderer) {
2245
+ this.requestContentUpdate();
2246
+ }
2480
2247
  }
2481
2248
 
2482
- static get template() {
2483
- return html`
2484
- <style>
2485
- :host {
2486
- display: block;
2487
- min-height: 1px;
2488
- overflow: auto;
2249
+ /** @private */
2250
+ __createElements(count) {
2251
+ return [...Array(count)].map(() => {
2252
+ const item = document.createElement(`${this.__hostTagName}-item`);
2253
+ item.addEventListener('click', this.__boundOnItemClick);
2254
+ // Negative tabindex prevents the item content from being focused.
2255
+ item.tabIndex = '-1';
2256
+ item.style.width = '100%';
2257
+ return item;
2258
+ });
2259
+ }
2489
2260
 
2490
- /* Fixes item background from getting on top of scrollbars on Safari */
2491
- transform: translate3d(0, 0, 0);
2261
+ /** @private */
2262
+ __updateElement(el, index) {
2263
+ const item = this.items[index];
2264
+ const focusedIndex = this.focusedIndex;
2265
+ const selected = this._isItemSelected(item, this.selectedItem, this.itemIdPath);
2266
+
2267
+ el.setProperties({
2268
+ item,
2269
+ index,
2270
+ label: this.getItemLabel(item),
2271
+ selected,
2272
+ renderer: this.renderer,
2273
+ focused: this.__isItemFocused(focusedIndex, index),
2274
+ });
2492
2275
 
2493
- /* Enable momentum scrolling on iOS */
2494
- -webkit-overflow-scrolling: touch;
2276
+ el.id = `${this.__hostTagName}-item-${index}`;
2277
+ el.setAttribute('role', this.__getAriaRole(index));
2278
+ el.setAttribute('aria-selected', selected.toString());
2279
+ el.setAttribute('aria-posinset', index + 1);
2280
+ el.setAttribute('aria-setsize', this.items.length);
2495
2281
 
2496
- /* Fixes scrollbar disappearing when 'Show scroll bars: Always' enabled in Safari */
2497
- box-shadow: 0 0 0 white;
2498
- }
2282
+ if (this.theme) {
2283
+ el.setAttribute('theme', this.theme);
2284
+ } else {
2285
+ el.removeAttribute('theme');
2286
+ }
2499
2287
 
2500
- #selector {
2501
- border-width: var(--_vaadin-combo-box-items-container-border-width);
2502
- border-style: var(--_vaadin-combo-box-items-container-border-style);
2503
- border-color: var(--_vaadin-combo-box-items-container-border-color, transparent);
2504
- position: relative;
2505
- }
2506
- </style>
2507
- <div id="selector">
2508
- <slot></slot>
2509
- </div>
2510
- `;
2288
+ if (item instanceof ComboBoxPlaceholder) {
2289
+ this.__requestItemByIndex(index);
2290
+ }
2511
2291
  }
2512
- }
2513
2292
 
2514
- defineCustomElement(ComboBoxScroller);
2293
+ /** @private */
2294
+ __onItemClick(e) {
2295
+ this.dispatchEvent(new CustomEvent('selection-changed', { detail: { item: e.currentTarget.item } }));
2296
+ }
2515
2297
 
2516
- /**
2517
- * @license
2518
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
2519
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
2520
- */
2298
+ /**
2299
+ * We want to prevent the kinetic scrolling energy from being transferred from the overlay contents over to the parent.
2300
+ * Further improvement ideas: after the contents have been scrolled to the top or bottom and scrolling has stopped, it could allow
2301
+ * scrolling the parent similarly to touch scrolling.
2302
+ */
2303
+ __patchWheelOverScrolling() {
2304
+ this.$.selector.addEventListener('wheel', (e) => {
2305
+ const scrolledToTop = this.scrollTop === 0;
2306
+ const scrolledToBottom = this.scrollHeight - this.scrollTop - this.clientHeight <= 1;
2307
+ if (scrolledToTop && e.deltaY < 0) {
2308
+ e.preventDefault();
2309
+ } else if (scrolledToBottom && e.deltaY > 0) {
2310
+ e.preventDefault();
2311
+ }
2312
+ });
2313
+ }
2521
2314
 
2522
- /**
2523
- * A mixin to provide `pattern` property.
2524
- *
2525
- * @polymerMixin
2526
- * @mixes InputConstraintsMixin
2527
- */
2528
- const PatternMixin = (superclass) =>
2529
- class PatternMixinClass extends InputConstraintsMixin(superclass) {
2530
- static get properties() {
2531
- return {
2532
- /**
2533
- * A regular expression that the value is checked against.
2534
- * The pattern must match the entire value, not just some subset.
2535
- */
2536
- pattern: {
2537
- type: String,
2538
- },
2539
- };
2315
+ get _viewportTotalPaddingBottom() {
2316
+ if (this._cachedViewportTotalPaddingBottom === undefined) {
2317
+ const itemsStyle = window.getComputedStyle(this.$.selector);
2318
+ this._cachedViewportTotalPaddingBottom = [itemsStyle.paddingBottom, itemsStyle.borderBottomWidth]
2319
+ .map((v) => {
2320
+ return parseInt(v, 10);
2321
+ })
2322
+ .reduce((sum, v) => {
2323
+ return sum + v;
2324
+ });
2540
2325
  }
2541
2326
 
2542
- static get delegateAttrs() {
2543
- return [...super.delegateAttrs, 'pattern'];
2544
- }
2327
+ return this._cachedViewportTotalPaddingBottom;
2328
+ }
2545
2329
 
2546
- static get constraints() {
2547
- return [...super.constraints, 'pattern'];
2548
- }
2549
- };
2330
+ /**
2331
+ * Dispatches an `index-requested` event for the given index to notify
2332
+ * the data provider that it should start loading the page containing the requested index.
2333
+ *
2334
+ * The event is dispatched asynchronously to prevent an immediate page request and therefore
2335
+ * a possible infinite recursion in case the data provider implements page request cancelation logic
2336
+ * by invoking data provider page callbacks with an empty array.
2337
+ * The infinite recursion may occur otherwise since invoking a data provider page callback with an empty array
2338
+ * triggers a synchronous scroller update and, if the callback corresponds to the currently visible page,
2339
+ * the scroller will synchronously request the page again which may lead to looping in the end.
2340
+ * That was the case for the Flow counterpart:
2341
+ * https://github.com/vaadin/flow-components/issues/3553#issuecomment-1239344828
2342
+ */
2343
+ __requestItemByIndex(index) {
2344
+ requestAnimationFrame(() => {
2345
+ this.dispatchEvent(
2346
+ new CustomEvent('index-requested', {
2347
+ detail: {
2348
+ index,
2349
+ currentScrollerPos: this._oldScrollerPosition,
2350
+ },
2351
+ }),
2352
+ );
2353
+ });
2354
+ }
2355
+
2356
+ /** @private */
2357
+ _visibleItemsCount() {
2358
+ // Ensure items are positioned
2359
+ this.__virtualizer.scrollToIndex(this.__virtualizer.firstVisibleIndex);
2360
+ const hasItems = this.__virtualizer.size > 0;
2361
+ return hasItems ? this.__virtualizer.lastVisibleIndex - this.__virtualizer.firstVisibleIndex + 1 : 0;
2362
+ }
2363
+ }
2364
+
2365
+ customElements.define(ComboBoxScroller.is, ComboBoxScroller);
2550
2366
 
2551
2367
  /**
2552
2368
  * @license
2553
- * Copyright (c) 2015 - 2023 Vaadin Ltd.
2369
+ * Copyright (c) 2015 - 2022 Vaadin Ltd.
2554
2370
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
2555
2371
  */
2556
2372
 
@@ -2860,18 +2676,20 @@ const ComboBoxDataProviderMixin = (superClass) =>
2860
2676
  _flushPendingRequests(size) {
2861
2677
  if (this._pendingRequests) {
2862
2678
  const lastPage = Math.ceil(size / this.pageSize);
2863
- Object.entries(this._pendingRequests).forEach(([page, callback]) => {
2864
- if (parseInt(page) >= lastPage) {
2865
- callback([], size);
2679
+ const pendingRequestsKeys = Object.keys(this._pendingRequests);
2680
+ for (let reqIdx = 0; reqIdx < pendingRequestsKeys.length; reqIdx++) {
2681
+ const page = parseInt(pendingRequestsKeys[reqIdx]);
2682
+ if (page >= lastPage) {
2683
+ this._pendingRequests[page]([], size);
2866
2684
  }
2867
- });
2685
+ }
2868
2686
  }
2869
2687
  }
2870
2688
  };
2871
2689
 
2872
2690
  /**
2873
2691
  * @license
2874
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
2692
+ * Copyright (c) 2021 - 2022 Vaadin Ltd.
2875
2693
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
2876
2694
  */
2877
2695
 
@@ -2896,7 +2714,7 @@ function processTemplates(component) {
2896
2714
 
2897
2715
  /**
2898
2716
  * @license
2899
- * Copyright (c) 2015 - 2023 Vaadin Ltd.
2717
+ * Copyright (c) 2015 - 2022 Vaadin Ltd.
2900
2718
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
2901
2719
  */
2902
2720
 
@@ -2930,19 +2748,10 @@ function findItemIndex(items, callback) {
2930
2748
 
2931
2749
  /**
2932
2750
  * @polymerMixin
2933
- * @mixes ControllerMixin
2934
- * @mixes ValidateMixin
2935
- * @mixes DisabledMixin
2936
- * @mixes InputMixin
2937
- * @mixes KeyboardMixin
2938
- * @mixes FocusMixin
2939
- * @mixes OverlayClassMixin
2940
2751
  * @param {function(new:HTMLElement)} subclass
2941
2752
  */
2942
2753
  const ComboBoxMixin = (subclass) =>
2943
- class ComboBoxMixinClass extends OverlayClassMixin(
2944
- ControllerMixin(ValidateMixin(FocusMixin(KeyboardMixin(InputMixin(DisabledMixin(subclass)))))),
2945
- ) {
2754
+ class VaadinComboBoxMixinElement extends ControllerMixin(KeyboardMixin(InputMixin(DisabledMixin(subclass)))) {
2946
2755
  static get properties() {
2947
2756
  return {
2948
2757
  /**
@@ -3117,14 +2926,6 @@ const ComboBoxMixin = (subclass) =>
3117
2926
  observer: '_toggleElementChanged',
3118
2927
  },
3119
2928
 
3120
- /**
3121
- * Set of items to be rendered in the dropdown.
3122
- * @protected
3123
- */
3124
- _dropdownItems: {
3125
- type: Array,
3126
- },
3127
-
3128
2929
  /** @private */
3129
2930
  _closeOnBlurIsPrevented: Boolean,
3130
2931
 
@@ -3142,13 +2943,14 @@ const ComboBoxMixin = (subclass) =>
3142
2943
  static get observers() {
3143
2944
  return [
3144
2945
  '_selectedItemChanged(selectedItem, itemValuePath, itemLabelPath)',
3145
- '_openedOrItemsChanged(opened, _dropdownItems, loading)',
3146
- '_updateScroller(_scroller, _dropdownItems, opened, loading, selectedItem, itemIdPath, _focusedIndex, renderer, theme)',
2946
+ '_openedOrItemsChanged(opened, filteredItems, loading)',
2947
+ '_updateScroller(_scroller, filteredItems, opened, loading, selectedItem, itemIdPath, _focusedIndex, renderer, theme)',
3147
2948
  ];
3148
2949
  }
3149
2950
 
3150
2951
  constructor() {
3151
2952
  super();
2953
+ this._boundOnFocusout = this._onFocusout.bind(this);
3152
2954
  this._boundOverlaySelectedItemChanged = this._overlaySelectedItemChanged.bind(this);
3153
2955
  this._boundOnClearButtonMouseDown = this.__onClearButtonMouseDown.bind(this);
3154
2956
  this._boundOnClick = this._onClick.bind(this);
@@ -3165,6 +2967,24 @@ const ComboBoxMixin = (subclass) =>
3165
2967
  return 'vaadin-combo-box';
3166
2968
  }
3167
2969
 
2970
+ /**
2971
+ * @return {string | undefined}
2972
+ * @protected
2973
+ */
2974
+ get _inputElementValue() {
2975
+ return this.inputElement ? this.inputElement[this._propertyForValue] : undefined;
2976
+ }
2977
+
2978
+ /**
2979
+ * @param {string} value
2980
+ * @protected
2981
+ */
2982
+ set _inputElementValue(value) {
2983
+ if (this.inputElement) {
2984
+ this.inputElement[this._propertyForValue] = value;
2985
+ }
2986
+ }
2987
+
3168
2988
  /**
3169
2989
  * Get a reference to the native `<input>` element.
3170
2990
  * Override to provide a custom input.
@@ -3215,6 +3035,8 @@ const ComboBoxMixin = (subclass) =>
3215
3035
  this._initOverlay();
3216
3036
  this._initScroller();
3217
3037
 
3038
+ this.addEventListener('focusout', this._boundOnFocusout);
3039
+
3218
3040
  this._lastCommittedValue = this.value;
3219
3041
 
3220
3042
  this.addEventListener('click', this._boundOnClick);
@@ -3222,7 +3044,7 @@ const ComboBoxMixin = (subclass) =>
3222
3044
 
3223
3045
  const bringToFrontListener = () => {
3224
3046
  requestAnimationFrame(() => {
3225
- this._overlayElement.bringToFront();
3047
+ this.$.overlay.bringToFront();
3226
3048
  });
3227
3049
  };
3228
3050
 
@@ -3313,8 +3135,6 @@ const ComboBoxMixin = (subclass) =>
3313
3135
  overlay.addEventListener('opened-changed', (e) => {
3314
3136
  this._overlayOpened = e.detail.value;
3315
3137
  });
3316
-
3317
- this._overlayElement = overlay;
3318
3138
  }
3319
3139
 
3320
3140
  /**
@@ -3326,7 +3146,7 @@ const ComboBoxMixin = (subclass) =>
3326
3146
  _initScroller(host) {
3327
3147
  const scrollerTag = `${this._tagNamePrefix}-scroller`;
3328
3148
 
3329
- const overlay = this._overlayElement;
3149
+ const overlay = this.$.overlay;
3330
3150
 
3331
3151
  overlay.renderer = (root) => {
3332
3152
  if (!root.firstChild) {
@@ -3339,7 +3159,7 @@ const ComboBoxMixin = (subclass) =>
3339
3159
 
3340
3160
  const scroller = overlay.querySelector(scrollerTag);
3341
3161
 
3342
- scroller.owner = host || this;
3162
+ scroller.comboBox = host || this;
3343
3163
  scroller.getItemLabel = this._getItemLabel.bind(this);
3344
3164
  scroller.addEventListener('selection-changed', this._boundOverlaySelectedItemChanged);
3345
3165
 
@@ -3382,7 +3202,7 @@ const ComboBoxMixin = (subclass) =>
3382
3202
  this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-opened', { bubbles: true, composed: true }));
3383
3203
 
3384
3204
  this._onOpened();
3385
- } else if (wasOpened && this._dropdownItems && this._dropdownItems.length) {
3205
+ } else if (wasOpened && this.filteredItems && this.filteredItems.length) {
3386
3206
  this.close();
3387
3207
 
3388
3208
  this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-closed', { bubbles: true, composed: true }));
@@ -3434,7 +3254,7 @@ const ComboBoxMixin = (subclass) =>
3434
3254
  }
3435
3255
  }
3436
3256
 
3437
- this._overlayElement.restoreFocusOnClose = true;
3257
+ this.$.overlay.restoreFocusOnClose = true;
3438
3258
  } else {
3439
3259
  this._onClosed();
3440
3260
  if (this._openedWithFocusRing && this._isInputFocused()) {
@@ -3468,19 +3288,13 @@ const ComboBoxMixin = (subclass) =>
3468
3288
  return event.composedPath()[0] === this.clearElement;
3469
3289
  }
3470
3290
 
3471
- /** @private */
3472
- __onClearButtonMouseDown(event) {
3473
- event.preventDefault(); // Prevent native focusout event
3474
- this.inputElement.focus();
3475
- }
3476
-
3477
3291
  /**
3478
3292
  * @param {Event} event
3479
3293
  * @protected
3480
3294
  */
3481
- _onClearButtonClick(event) {
3295
+ _handleClearButtonClick(event) {
3482
3296
  event.preventDefault();
3483
- this._onClearAction();
3297
+ this._clear();
3484
3298
 
3485
3299
  // De-select dropdown item
3486
3300
  if (this.opened) {
@@ -3516,13 +3330,15 @@ const ComboBoxMixin = (subclass) =>
3516
3330
  }
3517
3331
 
3518
3332
  /** @private */
3519
- _onClick(event) {
3520
- if (this._isClearButton(event)) {
3521
- this._onClearButtonClick(event);
3522
- } else if (event.composedPath().includes(this._toggleElement)) {
3523
- this._onToggleButtonClick(event);
3333
+ _onClick(e) {
3334
+ const path = e.composedPath();
3335
+
3336
+ if (this._isClearButton(e)) {
3337
+ this._handleClearButtonClick(e);
3338
+ } else if (path.indexOf(this._toggleElement) > -1) {
3339
+ this._onToggleButtonClick(e);
3524
3340
  } else {
3525
- this._onHostClick(event);
3341
+ this._onHostClick(e);
3526
3342
  }
3527
3343
  }
3528
3344
 
@@ -3537,7 +3353,7 @@ const ComboBoxMixin = (subclass) =>
3537
3353
  super._onKeyDown(e);
3538
3354
 
3539
3355
  if (e.key === 'Tab') {
3540
- this._overlayElement.restoreFocusOnClose = false;
3356
+ this.$.overlay.restoreFocusOnClose = false;
3541
3357
  } else if (e.key === 'ArrowDown') {
3542
3358
  this._onArrowDown();
3543
3359
 
@@ -3553,7 +3369,7 @@ const ComboBoxMixin = (subclass) =>
3553
3369
 
3554
3370
  /** @private */
3555
3371
  _getItemLabel(item) {
3556
- let label = item && this.itemLabelPath ? get(this.itemLabelPath, item) : undefined;
3372
+ let label = item && this.itemLabelPath ? this.get(this.itemLabelPath, item) : undefined;
3557
3373
  if (label === undefined || label === null) {
3558
3374
  label = item ? item.toString() : '';
3559
3375
  }
@@ -3562,7 +3378,7 @@ const ComboBoxMixin = (subclass) =>
3562
3378
 
3563
3379
  /** @private */
3564
3380
  _getItemValue(item) {
3565
- let value = item && this.itemValuePath ? get(this.itemValuePath, item) : undefined;
3381
+ let value = item && this.itemValuePath ? this.get(this.itemValuePath, item) : undefined;
3566
3382
  if (value === undefined) {
3567
3383
  value = item ? item.toString() : '';
3568
3384
  }
@@ -3572,7 +3388,7 @@ const ComboBoxMixin = (subclass) =>
3572
3388
  /** @private */
3573
3389
  _onArrowDown() {
3574
3390
  if (this.opened) {
3575
- const items = this._dropdownItems;
3391
+ const items = this.filteredItems;
3576
3392
  if (items) {
3577
3393
  this._focusedIndex = Math.min(items.length - 1, this._focusedIndex + 1);
3578
3394
  this._prefillFocusedItemLabel();
@@ -3588,7 +3404,7 @@ const ComboBoxMixin = (subclass) =>
3588
3404
  if (this._focusedIndex > -1) {
3589
3405
  this._focusedIndex = Math.max(0, this._focusedIndex - 1);
3590
3406
  } else {
3591
- const items = this._dropdownItems;
3407
+ const items = this.filteredItems;
3592
3408
  if (items) {
3593
3409
  this._focusedIndex = items.length - 1;
3594
3410
  }
@@ -3603,7 +3419,7 @@ const ComboBoxMixin = (subclass) =>
3603
3419
  /** @private */
3604
3420
  _prefillFocusedItemLabel() {
3605
3421
  if (this._focusedIndex > -1) {
3606
- const focusedItem = this._dropdownItems[this._focusedIndex];
3422
+ const focusedItem = this.filteredItems[this._focusedIndex];
3607
3423
  this._inputElementValue = this._getItemLabel(focusedItem);
3608
3424
  this._markAllSelectionRange();
3609
3425
  }
@@ -3700,7 +3516,7 @@ const ComboBoxMixin = (subclass) =>
3700
3516
  } else if (this.clearButtonVisible && !this.opened && !!this.value) {
3701
3517
  e.stopPropagation();
3702
3518
  // The clear button is visible and the overlay is closed, so clear the value.
3703
- this._onClearAction();
3519
+ this._clear();
3704
3520
  }
3705
3521
  } else if (this.opened) {
3706
3522
  // Auto-open is enabled
@@ -3718,7 +3534,7 @@ const ComboBoxMixin = (subclass) =>
3718
3534
  } else if (this.clearButtonVisible && !!this.value) {
3719
3535
  e.stopPropagation();
3720
3536
  // The clear button is visible and the overlay is closed, so clear the value.
3721
- this._onClearAction();
3537
+ this._clear();
3722
3538
  }
3723
3539
  }
3724
3540
 
@@ -3740,7 +3556,7 @@ const ComboBoxMixin = (subclass) =>
3740
3556
  * Clears the current value.
3741
3557
  * @protected
3742
3558
  */
3743
- _onClearAction() {
3559
+ _clear() {
3744
3560
  this.selectedItem = null;
3745
3561
 
3746
3562
  if (this.allowCustomValue) {
@@ -3776,7 +3592,7 @@ const ComboBoxMixin = (subclass) =>
3776
3592
  /** @private */
3777
3593
  _commitValue() {
3778
3594
  if (this._focusedIndex > -1) {
3779
- const focusedItem = this._dropdownItems[this._focusedIndex];
3595
+ const focusedItem = this.filteredItems[this._focusedIndex];
3780
3596
  if (this.selectedItem !== focusedItem) {
3781
3597
  this.selectedItem = focusedItem;
3782
3598
  }
@@ -3791,7 +3607,7 @@ const ComboBoxMixin = (subclass) =>
3791
3607
  }
3792
3608
  } else {
3793
3609
  // Try to find an item which label matches the input value.
3794
- const items = [this.selectedItem, ...(this._dropdownItems || [])];
3610
+ const items = [...(this.filteredItems || []), this.selectedItem];
3795
3611
  const itemMatchingInputValue = items[this.__getItemIndexByLabel(items, this._inputElementValue)];
3796
3612
 
3797
3613
  if (
@@ -3832,6 +3648,14 @@ const ComboBoxMixin = (subclass) =>
3832
3648
  this.filter = '';
3833
3649
  }
3834
3650
 
3651
+ /**
3652
+ * @return {string}
3653
+ * @protected
3654
+ */
3655
+ get _propertyForValue() {
3656
+ return 'value';
3657
+ }
3658
+
3835
3659
  /**
3836
3660
  * Override an event listener from `InputMixin`.
3837
3661
  * @param {!Event} event
@@ -3978,12 +3802,6 @@ const ComboBoxMixin = (subclass) =>
3978
3802
 
3979
3803
  /** @private */
3980
3804
  _detectAndDispatchChange() {
3981
- // Do not validate when focusout is caused by document
3982
- // losing focus, which happens on browser tab switch.
3983
- if (document.hasFocus()) {
3984
- this.validate();
3985
- }
3986
-
3987
3805
  if (this.value !== this._lastCommittedValue) {
3988
3806
  this.dispatchEvent(new CustomEvent('change', { bubbles: true }));
3989
3807
  this._lastCommittedValue = this.value;
@@ -4006,8 +3824,6 @@ const ComboBoxMixin = (subclass) =>
4006
3824
 
4007
3825
  /** @private */
4008
3826
  _filteredItemsChanged(filteredItems, oldFilteredItems) {
4009
- this._setDropdownItems(filteredItems);
4010
-
4011
3827
  // Store the currently focused item if any. The focused index preserves
4012
3828
  // in the case when more filtered items are loading but it is reset
4013
3829
  // when the user types in a filter query.
@@ -4068,16 +3884,6 @@ const ComboBoxMixin = (subclass) =>
4068
3884
  }
4069
3885
  }
4070
3886
 
4071
- /**
4072
- * Provide items to be rendered in the dropdown.
4073
- * Override this method to show custom items.
4074
- *
4075
- * @protected
4076
- */
4077
- _setDropdownItems(items) {
4078
- this._dropdownItems = items;
4079
- }
4080
-
4081
3887
  /** @private */
4082
3888
  _getItemElements() {
4083
3889
  return Array.from(this._scroller.querySelectorAll(`${this._tagNamePrefix}-item`));
@@ -4138,53 +3944,35 @@ const ComboBoxMixin = (subclass) =>
4138
3944
  }
4139
3945
  }
4140
3946
 
4141
- /**
4142
- * Override method inherited from `FocusMixin`
4143
- * to close the overlay on blur and commit the value.
4144
- *
4145
- * @param {boolean} focused
4146
- * @protected
4147
- * @override
4148
- */
4149
- _setFocused(focused) {
4150
- super._setFocused(focused);
4151
-
4152
- if (!focused && !this.readonly && !this._closeOnBlurIsPrevented) {
4153
- // User's logic in `custom-value-set` event listener might cause input to blur,
4154
- // which will result in attempting to commit the same custom value once again.
4155
- if (!this.opened && this.allowCustomValue && this._inputElementValue === this._lastCustomValue) {
4156
- delete this._lastCustomValue;
4157
- return;
4158
- }
4159
-
4160
- this._closeOrCommit();
4161
- }
3947
+ /** @private */
3948
+ __onClearButtonMouseDown(event) {
3949
+ event.preventDefault(); // Prevent native focusout event
3950
+ this.inputElement.focus();
4162
3951
  }
4163
3952
 
4164
- /**
4165
- * Override method inherited from `FocusMixin` to not remove focused
4166
- * state when focus moves to the overlay.
4167
- *
4168
- * @param {FocusEvent} event
4169
- * @return {boolean}
4170
- * @protected
4171
- * @override
4172
- */
4173
- _shouldRemoveFocus(event) {
3953
+ /** @private */
3954
+ _onFocusout(event) {
4174
3955
  // VoiceOver on iOS fires `focusout` event when moving focus to the item in the dropdown.
4175
3956
  // Do not focus the input in this case, because it would break announcement for the item.
4176
3957
  if (event.relatedTarget && event.relatedTarget.localName === `${this._tagNamePrefix}-item`) {
4177
- return false;
3958
+ return;
4178
3959
  }
4179
3960
 
4180
- // Do not blur when focus moves to the overlay
4181
- // Also, fixes the problem with `focusout` happening when clicking on the scroll bar on Edge
4182
- if (event.relatedTarget === this._overlayElement) {
3961
+ // Fixes the problem with `focusout` happening when clicking on the scroll bar on Edge
3962
+ if (event.relatedTarget === this.$.overlay) {
4183
3963
  event.composedPath()[0].focus();
4184
- return false;
3964
+ return;
4185
3965
  }
3966
+ if (!this.readonly && !this._closeOnBlurIsPrevented) {
3967
+ // User's logic in `custom-value-set` event listener might cause input to blur,
3968
+ // which will result in attempting to commit the same custom value once again.
3969
+ if (!this.opened && this.allowCustomValue && this._inputElementValue === this._lastCustomValue) {
3970
+ delete this._lastCustomValue;
3971
+ return;
3972
+ }
4186
3973
 
4187
- return true;
3974
+ this._closeOrCommit();
3975
+ }
4188
3976
  }
4189
3977
 
4190
3978
  /** @private */
@@ -4194,7 +3982,7 @@ const ComboBoxMixin = (subclass) =>
4194
3982
  }
4195
3983
 
4196
3984
  event.preventDefault();
4197
- this._onClearAction();
3985
+ this._clear();
4198
3986
  }
4199
3987
 
4200
3988
  /**
@@ -4240,7 +4028,7 @@ const ComboBoxMixin = (subclass) =>
4240
4028
 
4241
4029
  /**
4242
4030
  * @license
4243
- * Copyright (c) 2015 - 2023 Vaadin Ltd.
4031
+ * Copyright (c) 2015 - 2022 Vaadin Ltd.
4244
4032
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
4245
4033
  */
4246
4034
 
@@ -4360,7 +4148,7 @@ registerStyles('vaadin-combo-box', inputFieldShared$1, { moduleId: 'vaadin-combo
4360
4148
  * Note: the `theme` attribute value set on `<vaadin-combo-box>` is
4361
4149
  * propagated to the internal components listed above.
4362
4150
  *
4363
- * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
4151
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components) documentation.
4364
4152
  *
4365
4153
  * @fires {Event} change - Fired when the user commits a value change.
4366
4154
  * @fires {CustomEvent} custom-value-set - Fired when the user sets a custom value.
@@ -4371,7 +4159,6 @@ registerStyles('vaadin-combo-box', inputFieldShared$1, { moduleId: 'vaadin-combo
4371
4159
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
4372
4160
  * @fires {CustomEvent} validated - Fired whenever the field is validated.
4373
4161
  *
4374
- * @customElement
4375
4162
  * @extends HTMLElement
4376
4163
  * @mixes ElementMixin
4377
4164
  * @mixes ThemableMixin
@@ -4474,7 +4261,6 @@ class ComboBox extends ComboBoxDataProviderMixin(
4474
4261
  this._tooltipController = new TooltipController(this);
4475
4262
  this.addController(this._tooltipController);
4476
4263
  this._tooltipController.setPosition('top');
4477
- this._tooltipController.setAriaTarget(this.inputElement);
4478
4264
  this._tooltipController.setShouldShow((target) => !target.opened);
4479
4265
 
4480
4266
  this._positionTarget = this.shadowRoot.querySelector('[part="input-field"]');
@@ -4482,17 +4268,48 @@ class ComboBox extends ComboBoxDataProviderMixin(
4482
4268
  }
4483
4269
 
4484
4270
  /**
4485
- * Override the method from `InputControlMixin`
4486
- * to stop event propagation to prevent `ComboBoxMixin`
4487
- * from handling this click event also on its own.
4488
- *
4271
+ * Override method inherited from `FocusMixin` to validate on blur.
4272
+ * @param {boolean} focused
4273
+ * @protected
4274
+ * @override
4275
+ */
4276
+ _setFocused(focused) {
4277
+ super._setFocused(focused);
4278
+
4279
+ if (!focused) {
4280
+ this.validate();
4281
+ }
4282
+ }
4283
+
4284
+ /**
4285
+ * Override method inherited from `FocusMixin` to not remove focused
4286
+ * state when focus moves to the overlay.
4287
+ * @param {FocusEvent} event
4288
+ * @return {boolean}
4289
+ * @protected
4290
+ * @override
4291
+ */
4292
+ _shouldRemoveFocus(event) {
4293
+ // Do not blur when focus moves to the overlay
4294
+ if (event.relatedTarget === this.$.overlay) {
4295
+ event.composedPath()[0].focus();
4296
+ return false;
4297
+ }
4298
+
4299
+ return true;
4300
+ }
4301
+
4302
+ /**
4303
+ * Override method inherited from `InputControlMixin` to handle clear
4304
+ * button click and stop event from propagating to the host element.
4489
4305
  * @param {Event} event
4490
4306
  * @protected
4491
4307
  * @override
4492
4308
  */
4493
4309
  _onClearButtonClick(event) {
4494
4310
  event.stopPropagation();
4495
- super._onClearButtonClick(event);
4311
+
4312
+ this._handleClearButtonClick(event);
4496
4313
  }
4497
4314
 
4498
4315
  /**
@@ -4509,4 +4326,4 @@ class ComboBox extends ComboBoxDataProviderMixin(
4509
4326
  }
4510
4327
  }
4511
4328
 
4512
- defineCustomElement(ComboBox);
4329
+ customElements.define(ComboBox.is, ComboBox);