@everymatrix/general-input 1.22.0 → 1.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/checkbox-group-input_10.cjs.entry.js +27763 -17684
- package/dist/components/active-mixin.js +97 -7
- package/dist/components/checkbox-group-input2.js +2863 -390
- package/dist/components/date-input2.js +6683 -2984
- package/dist/components/field-mixin.js +2423 -3723
- package/dist/components/input-field-shared-styles.js +993 -242
- package/dist/components/password-input2.js +1870 -94
- package/dist/components/vaadin-button.js +1531 -157
- package/dist/components/vaadin-combo-box.js +2655 -783
- package/dist/components/virtual-keyboard-controller.js +1163 -1742
- package/dist/esm/checkbox-group-input_10.entry.js +27763 -17684
- package/dist/general-input/general-input.esm.js +1 -1
- package/dist/general-input/p-983d18d7.entry.js +4143 -0
- package/package.json +9 -6
- package/dist/components/pattern-mixin.js +0 -85
- package/dist/general-input/p-553c91f3.entry.js +0 -3583
- /package/dist/types/Users/{catalin.poclid/Documents/work → adrian.pripon/Documents/Work}/widgets-stencil/packages/general-input/.stencil/packages/general-input/stencil.config.d.ts +0 -0
|
@@ -1,111 +1,73 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { i as inputFieldShared,
|
|
4
|
-
import { P as PatternMixin } from './pattern-mixin.js';
|
|
1
|
+
import { o, i, h as html, P as PolymerElement, u as usageStatistics, d as dedupingMixin, V as ValidateMixin, j as FocusMixin, K as KeyboardMixin, I as InputMixin, a as DisabledMixin, b as isElementFocused, e as InputController, f as LabelledInputController } from './field-mixin.js';
|
|
2
|
+
import { b as overlay, c as menuOverlayCore, P as PositionMixin, O as OverlayMixin, o as overlayStyles, V as VirtualKeyboardController } from './virtual-keyboard-controller.js';
|
|
3
|
+
import { i as inputFieldShared, I as InputConstraintsMixin, a as InputControlMixin, b as inputFieldShared$1 } from './input-field-shared-styles.js';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* @license
|
|
8
|
-
* Copyright (c)
|
|
7
|
+
* Copyright (c) 2017 - 2023 Vaadin Ltd.
|
|
9
8
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
10
9
|
*/
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
border-radius: calc(0.5 * var(--lumo-icon-size-s));
|
|
21
|
-
opacity: 0;
|
|
22
|
-
pointer-events: none;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
:host(:not([loading])) [part~='loader'] {
|
|
26
|
-
display: none;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
:host([loading]) [part~='loader'] {
|
|
30
|
-
animation: 1s linear infinite lumo-loader-rotate, 0.3s 0.1s lumo-loader-fade-in both;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
@keyframes lumo-loader-fade-in {
|
|
34
|
-
0% {
|
|
35
|
-
opacity: 0;
|
|
36
|
-
}
|
|
11
|
+
/**
|
|
12
|
+
* Check if the custom element type has themes applied.
|
|
13
|
+
* @param {Function} elementClass
|
|
14
|
+
* @returns {boolean}
|
|
15
|
+
*/
|
|
16
|
+
function classHasThemes$1(elementClass) {
|
|
17
|
+
return elementClass && Object.prototype.hasOwnProperty.call(elementClass, '__themes');
|
|
18
|
+
}
|
|
37
19
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Check if the custom element type has themes applied.
|
|
22
|
+
* @param {string} tagName
|
|
23
|
+
* @returns {boolean}
|
|
24
|
+
*/
|
|
25
|
+
function hasThemes$1(tagName) {
|
|
26
|
+
return classHasThemes$1(customElements.get(tagName));
|
|
27
|
+
}
|
|
42
28
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Flattens the styles into a single array of styles.
|
|
31
|
+
* @param {CSSResultGroup} styles
|
|
32
|
+
* @param {CSSResult[]} result
|
|
33
|
+
* @returns {CSSResult[]}
|
|
34
|
+
*/
|
|
35
|
+
function flattenStyles$1(styles = []) {
|
|
36
|
+
return [styles].flat(Infinity).filter((style) => {
|
|
37
|
+
if (style instanceof o) {
|
|
38
|
+
return true;
|
|
46
39
|
}
|
|
40
|
+
console.warn('An item in styles is not of type CSSResult. Use `unsafeCSS` or `css`.');
|
|
41
|
+
return false;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Registers CSS styles for a component type. Make sure to register the styles before
|
|
47
|
+
* the first instance of a component of the type is attached to DOM.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} themeFor The local/tag name of the component type to register the styles for
|
|
50
|
+
* @param {CSSResultGroup} styles The CSS style rules to be registered for the component type
|
|
51
|
+
* matching themeFor and included in the local scope of each component instance
|
|
52
|
+
* @param {{moduleId?: string, include?: string | string[]}} options Additional options
|
|
53
|
+
* @return {void}
|
|
54
|
+
*/
|
|
55
|
+
function registerStyles$1(themeFor, styles, options = {}) {
|
|
56
|
+
if (themeFor) {
|
|
57
|
+
if (hasThemes$1(themeFor)) {
|
|
58
|
+
console.warn(`The custom element definition for "${themeFor}"
|
|
59
|
+
was finalized before a style module was registered.
|
|
60
|
+
Make sure to add component specific style modules before
|
|
61
|
+
importing the corresponding custom element.`);
|
|
50
62
|
}
|
|
51
63
|
}
|
|
52
|
-
`;
|
|
53
|
-
|
|
54
|
-
const comboBoxOverlay = i`
|
|
55
|
-
[part='content'] {
|
|
56
|
-
padding: 0;
|
|
57
|
-
}
|
|
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
|
-
|
|
67
|
-
/* When items are empty, the spinner needs some room */
|
|
68
|
-
:host(:not([closing])) [part~='content'] {
|
|
69
|
-
min-height: calc(2 * var(--lumo-space-s) + var(--lumo-icon-size-s));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
[part~='overlay'] {
|
|
73
|
-
position: relative;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
:host([top-aligned]) [part~='overlay'] {
|
|
77
|
-
margin-top: var(--lumo-space-xs);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
:host([bottom-aligned]) [part~='overlay'] {
|
|
81
|
-
margin-bottom: var(--lumo-space-xs);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
[part~='loader'] {
|
|
85
|
-
position: absolute;
|
|
86
|
-
z-index: 1;
|
|
87
|
-
left: var(--lumo-space-s);
|
|
88
|
-
right: var(--lumo-space-s);
|
|
89
|
-
top: var(--lumo-space-s);
|
|
90
|
-
margin-left: auto;
|
|
91
|
-
margin-inline-start: auto;
|
|
92
|
-
margin-inline-end: 0;
|
|
93
|
-
}
|
|
94
64
|
|
|
95
|
-
|
|
65
|
+
styles = flattenStyles$1(styles);
|
|
96
66
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
margin-left: 0;
|
|
100
|
-
margin-right: auto;
|
|
101
|
-
margin-inline-start: 0;
|
|
102
|
-
margin-inline-end: auto;
|
|
67
|
+
if (window.Vaadin && window.Vaadin.styleModules) {
|
|
68
|
+
window.Vaadin.styleModules.registerStyles(themeFor, styles, options);
|
|
103
69
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
registerStyles('vaadin-combo-box-overlay', [overlay, menuOverlayCore, comboBoxOverlay, loader], {
|
|
107
|
-
moduleId: 'lumo-combo-box-overlay',
|
|
108
|
-
});
|
|
70
|
+
}
|
|
109
71
|
|
|
110
72
|
const item = i`
|
|
111
73
|
:host {
|
|
@@ -182,309 +144,1377 @@ const item = i`
|
|
|
182
144
|
}
|
|
183
145
|
|
|
184
146
|
/* Slotted icons */
|
|
185
|
-
:host ::slotted(vaadin-icon)
|
|
186
|
-
:host ::slotted(iron-icon) {
|
|
147
|
+
:host ::slotted(vaadin-icon) {
|
|
187
148
|
width: var(--lumo-icon-size-m);
|
|
188
149
|
height: var(--lumo-icon-size-m);
|
|
189
150
|
}
|
|
190
151
|
`;
|
|
191
152
|
|
|
192
|
-
registerStyles('vaadin-item', item, { moduleId: 'lumo-item' });
|
|
193
|
-
|
|
194
|
-
const comboBoxItem = i`
|
|
195
|
-
:host {
|
|
196
|
-
transition: background-color 100ms;
|
|
197
|
-
overflow: hidden;
|
|
198
|
-
--_lumo-item-selected-icon-display: block;
|
|
199
|
-
}
|
|
153
|
+
registerStyles$1('vaadin-item', item, { moduleId: 'lumo-item' });
|
|
200
154
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
155
|
+
/**
|
|
156
|
+
* @license
|
|
157
|
+
* Copyright (c) 2017 - 2023 Vaadin Ltd.
|
|
158
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
159
|
+
*/
|
|
160
|
+
/**
|
|
161
|
+
* @polymerMixin
|
|
162
|
+
*/
|
|
163
|
+
const ThemePropertyMixin = (superClass) =>
|
|
164
|
+
class VaadinThemePropertyMixin extends superClass {
|
|
165
|
+
static get properties() {
|
|
166
|
+
return {
|
|
167
|
+
/**
|
|
168
|
+
* Helper property with theme attribute value facilitating propagation
|
|
169
|
+
* in shadow DOM.
|
|
170
|
+
*
|
|
171
|
+
* Enables the component implementation to propagate the `theme`
|
|
172
|
+
* attribute value to the sub-components in Shadow DOM by binding
|
|
173
|
+
* the sub-component's "theme" attribute to the `theme` property of
|
|
174
|
+
* the host.
|
|
175
|
+
*
|
|
176
|
+
* **NOTE:** Extending the mixin only provides the property for binding,
|
|
177
|
+
* and does not make the propagation alone.
|
|
178
|
+
*
|
|
179
|
+
* See [Styling Components: Sub-components](https://vaadin.com/docs/latest/styling/styling-components/#sub-components).
|
|
180
|
+
* page for more information.
|
|
181
|
+
*
|
|
182
|
+
* @protected
|
|
183
|
+
*/
|
|
184
|
+
_theme: {
|
|
185
|
+
type: String,
|
|
186
|
+
readOnly: true,
|
|
187
|
+
},
|
|
188
|
+
};
|
|
204
189
|
}
|
|
205
|
-
}
|
|
206
|
-
`;
|
|
207
190
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const comboBox = i`
|
|
213
|
-
:host {
|
|
214
|
-
outline: none;
|
|
215
|
-
}
|
|
191
|
+
static get observedAttributes() {
|
|
192
|
+
return [...super.observedAttributes, 'theme'];
|
|
193
|
+
}
|
|
216
194
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
`;
|
|
195
|
+
/** @protected */
|
|
196
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
197
|
+
super.attributeChangedCallback(name, oldValue, newValue);
|
|
221
198
|
|
|
222
|
-
|
|
199
|
+
if (name === 'theme') {
|
|
200
|
+
this._set_theme(newValue);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
};
|
|
223
204
|
|
|
224
205
|
/**
|
|
225
206
|
* @license
|
|
226
|
-
* Copyright (c)
|
|
207
|
+
* Copyright (c) 2017 - 2023 Vaadin Ltd.
|
|
227
208
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
228
209
|
*/
|
|
229
210
|
|
|
230
211
|
/**
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
* Part name | Description
|
|
238
|
-
* ------------|--------------
|
|
239
|
-
* `checkmark` | The graphical checkmark shown for a selected item
|
|
240
|
-
* `content` | The element that wraps the item content
|
|
241
|
-
*
|
|
242
|
-
* The following state attributes are exposed for styling:
|
|
243
|
-
*
|
|
244
|
-
* Attribute | Description
|
|
245
|
-
* -------------|-------------
|
|
246
|
-
* `selected` | Set when the item is selected
|
|
247
|
-
* `focused` | Set when the item is focused
|
|
248
|
-
*
|
|
249
|
-
* See [Styling Components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components) documentation.
|
|
212
|
+
* @typedef {Object} Theme
|
|
213
|
+
* @property {string} themeFor
|
|
214
|
+
* @property {CSSResult[]} styles
|
|
215
|
+
* @property {string | string[]} [include]
|
|
216
|
+
* @property {string} [moduleId]
|
|
250
217
|
*
|
|
251
|
-
* @
|
|
252
|
-
* @mixes DirMixin
|
|
253
|
-
* @private
|
|
218
|
+
* @typedef {CSSResult[] | CSSResult} CSSResultGroup
|
|
254
219
|
*/
|
|
255
|
-
class ComboBoxItem extends ThemableMixin(DirMixin(PolymerElement)) {
|
|
256
|
-
static get template() {
|
|
257
|
-
return html`
|
|
258
|
-
<style>
|
|
259
|
-
:host {
|
|
260
|
-
display: block;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
:host([hidden]) {
|
|
264
|
-
display: none;
|
|
265
|
-
}
|
|
266
|
-
</style>
|
|
267
|
-
<span part="checkmark" aria-hidden="true"></span>
|
|
268
|
-
<div part="content">
|
|
269
|
-
<slot></slot>
|
|
270
|
-
</div>
|
|
271
|
-
`;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
static get is() {
|
|
275
|
-
return 'vaadin-combo-box-item';
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
static get properties() {
|
|
279
|
-
return {
|
|
280
|
-
/**
|
|
281
|
-
* The index of the item
|
|
282
|
-
*/
|
|
283
|
-
index: Number,
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* The item to render
|
|
287
|
-
* @type {(String|Object)}
|
|
288
|
-
*/
|
|
289
|
-
item: Object,
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* The text label corresponding to the item
|
|
293
|
-
*/
|
|
294
|
-
label: String,
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* True when item is selected
|
|
298
|
-
*/
|
|
299
|
-
selected: {
|
|
300
|
-
type: Boolean,
|
|
301
|
-
value: false,
|
|
302
|
-
reflectToAttribute: true,
|
|
303
|
-
},
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* True when item is focused
|
|
307
|
-
*/
|
|
308
|
-
focused: {
|
|
309
|
-
type: Boolean,
|
|
310
|
-
value: false,
|
|
311
|
-
reflectToAttribute: true,
|
|
312
|
-
},
|
|
313
|
-
|
|
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
|
-
}
|
|
325
220
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
221
|
+
/**
|
|
222
|
+
* @type {Theme[]}
|
|
223
|
+
*/
|
|
224
|
+
const themeRegistry = [];
|
|
329
225
|
|
|
330
|
-
|
|
331
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Check if the custom element type has themes applied.
|
|
228
|
+
* @param {Function} elementClass
|
|
229
|
+
* @returns {boolean}
|
|
230
|
+
*/
|
|
231
|
+
function classHasThemes(elementClass) {
|
|
232
|
+
return elementClass && Object.prototype.hasOwnProperty.call(elementClass, '__themes');
|
|
233
|
+
}
|
|
332
234
|
|
|
333
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Check if the custom element type has themes applied.
|
|
237
|
+
* @param {string} tagName
|
|
238
|
+
* @returns {boolean}
|
|
239
|
+
*/
|
|
240
|
+
function hasThemes(tagName) {
|
|
241
|
+
return classHasThemes(customElements.get(tagName));
|
|
242
|
+
}
|
|
334
243
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
244
|
+
/**
|
|
245
|
+
* Flattens the styles into a single array of styles.
|
|
246
|
+
* @param {CSSResultGroup} styles
|
|
247
|
+
* @param {CSSResult[]} result
|
|
248
|
+
* @returns {CSSResult[]}
|
|
249
|
+
*/
|
|
250
|
+
function flattenStyles(styles = []) {
|
|
251
|
+
return [styles].flat(Infinity).filter((style) => {
|
|
252
|
+
if (style instanceof o) {
|
|
253
|
+
return true;
|
|
338
254
|
}
|
|
339
|
-
|
|
255
|
+
console.warn('An item in styles is not of type CSSResult. Use `unsafeCSS` or `css`.');
|
|
256
|
+
return false;
|
|
257
|
+
});
|
|
258
|
+
}
|
|
340
259
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
260
|
+
/**
|
|
261
|
+
* Registers CSS styles for a component type. Make sure to register the styles before
|
|
262
|
+
* the first instance of a component of the type is attached to DOM.
|
|
263
|
+
*
|
|
264
|
+
* @param {string} themeFor The local/tag name of the component type to register the styles for
|
|
265
|
+
* @param {CSSResultGroup} styles The CSS style rules to be registered for the component type
|
|
266
|
+
* matching themeFor and included in the local scope of each component instance
|
|
267
|
+
* @param {{moduleId?: string, include?: string | string[]}} options Additional options
|
|
268
|
+
* @return {void}
|
|
269
|
+
*/
|
|
270
|
+
function registerStyles(themeFor, styles, options = {}) {
|
|
271
|
+
if (themeFor) {
|
|
272
|
+
if (hasThemes(themeFor)) {
|
|
273
|
+
console.warn(`The custom element definition for "${themeFor}"
|
|
274
|
+
was finalized before a style module was registered.
|
|
275
|
+
Make sure to add component specific style modules before
|
|
276
|
+
importing the corresponding custom element.`);
|
|
350
277
|
}
|
|
351
|
-
|
|
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
278
|
}
|
|
361
279
|
|
|
362
|
-
|
|
363
|
-
__rendererOrItemChanged(renderer, index, item) {
|
|
364
|
-
if (item === undefined || index === undefined) {
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
|
|
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
|
-
}
|
|
280
|
+
styles = flattenStyles(styles);
|
|
375
281
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
282
|
+
if (window.Vaadin && window.Vaadin.styleModules) {
|
|
283
|
+
window.Vaadin.styleModules.registerStyles(themeFor, styles, options);
|
|
284
|
+
} else {
|
|
285
|
+
themeRegistry.push({
|
|
286
|
+
themeFor,
|
|
287
|
+
styles,
|
|
288
|
+
include: options.include,
|
|
289
|
+
moduleId: options.moduleId,
|
|
290
|
+
});
|
|
380
291
|
}
|
|
292
|
+
}
|
|
381
293
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
294
|
+
/**
|
|
295
|
+
* Returns all registered themes. By default the themeRegistry is returned as is.
|
|
296
|
+
* In case the style-modules adapter is imported, the themes are obtained from there instead
|
|
297
|
+
* @returns {Theme[]}
|
|
298
|
+
*/
|
|
299
|
+
function getAllThemes() {
|
|
300
|
+
if (window.Vaadin && window.Vaadin.styleModules) {
|
|
301
|
+
return window.Vaadin.styleModules.getAllThemes();
|
|
389
302
|
}
|
|
303
|
+
return themeRegistry;
|
|
390
304
|
}
|
|
391
305
|
|
|
392
|
-
|
|
393
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Returns true if the themeFor string matches the tag name
|
|
308
|
+
* @param {string} themeFor
|
|
309
|
+
* @param {string} tagName
|
|
310
|
+
* @returns {boolean}
|
|
311
|
+
*/
|
|
312
|
+
function matchesThemeFor(themeFor, tagName) {
|
|
313
|
+
return (themeFor || '').split(' ').some((themeForToken) => {
|
|
314
|
+
return new RegExp(`^${themeForToken.split('*').join('.*')}$`, 'u').test(tagName);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Maps the moduleName to an include priority number which is used for
|
|
320
|
+
* determining the order in which styles are applied.
|
|
321
|
+
* @param {string} moduleName
|
|
322
|
+
* @returns {number}
|
|
323
|
+
*/
|
|
324
|
+
function getIncludePriority(moduleName = '') {
|
|
325
|
+
let includePriority = 0;
|
|
326
|
+
if (moduleName.startsWith('lumo-') || moduleName.startsWith('material-')) {
|
|
327
|
+
includePriority = 1;
|
|
328
|
+
} else if (moduleName.startsWith('vaadin-')) {
|
|
329
|
+
includePriority = 2;
|
|
330
|
+
}
|
|
331
|
+
return includePriority;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Gets an array of CSSResults matching the include property of the theme.
|
|
336
|
+
* @param {Theme} theme
|
|
337
|
+
* @returns {CSSResult[]}
|
|
338
|
+
*/
|
|
339
|
+
function getIncludedStyles(theme) {
|
|
340
|
+
const includedStyles = [];
|
|
341
|
+
if (theme.include) {
|
|
342
|
+
[].concat(theme.include).forEach((includeModuleId) => {
|
|
343
|
+
const includedTheme = getAllThemes().find((s) => s.moduleId === includeModuleId);
|
|
344
|
+
if (includedTheme) {
|
|
345
|
+
includedStyles.push(...getIncludedStyles(includedTheme), ...includedTheme.styles);
|
|
346
|
+
} else {
|
|
347
|
+
console.warn(`Included moduleId ${includeModuleId} not found in style registry`);
|
|
348
|
+
}
|
|
349
|
+
}, theme.styles);
|
|
350
|
+
}
|
|
351
|
+
return includedStyles;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Includes the styles to the template.
|
|
356
|
+
* @param {CSSResult[]} styles
|
|
357
|
+
* @param {HTMLTemplateElement} template
|
|
358
|
+
*/
|
|
359
|
+
function addStylesToTemplate(styles, template) {
|
|
360
|
+
const styleEl = document.createElement('style');
|
|
361
|
+
styleEl.innerHTML = styles.map((style) => style.cssText).join('\n');
|
|
362
|
+
template.content.appendChild(styleEl);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Returns an array of themes that should be used for styling a component matching
|
|
367
|
+
* the tag name. The array is sorted by the include order.
|
|
368
|
+
* @param {string} tagName
|
|
369
|
+
* @returns {Theme[]}
|
|
370
|
+
*/
|
|
371
|
+
function getThemes(tagName) {
|
|
372
|
+
const defaultModuleName = `${tagName}-default-theme`;
|
|
373
|
+
|
|
374
|
+
const themes = getAllThemes()
|
|
375
|
+
// Filter by matching themeFor properties
|
|
376
|
+
.filter((theme) => theme.moduleId !== defaultModuleName && matchesThemeFor(theme.themeFor, tagName))
|
|
377
|
+
.map((theme) => ({
|
|
378
|
+
...theme,
|
|
379
|
+
// Prepend styles from included themes
|
|
380
|
+
styles: [...getIncludedStyles(theme), ...theme.styles],
|
|
381
|
+
// Map moduleId to includePriority
|
|
382
|
+
includePriority: getIncludePriority(theme.moduleId),
|
|
383
|
+
}))
|
|
384
|
+
// Sort by includePriority
|
|
385
|
+
.sort((themeA, themeB) => themeB.includePriority - themeA.includePriority);
|
|
386
|
+
|
|
387
|
+
if (themes.length > 0) {
|
|
388
|
+
return themes;
|
|
389
|
+
}
|
|
390
|
+
// No theme modules found, return the default module if it exists
|
|
391
|
+
return getAllThemes().filter((theme) => theme.moduleId === defaultModuleName);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* @polymerMixin
|
|
396
|
+
* @mixes ThemePropertyMixin
|
|
397
|
+
*/
|
|
398
|
+
const ThemableMixin = (superClass) =>
|
|
399
|
+
class VaadinThemableMixin extends ThemePropertyMixin(superClass) {
|
|
400
|
+
/**
|
|
401
|
+
* Covers PolymerElement based component styling
|
|
402
|
+
* @protected
|
|
403
|
+
*/
|
|
404
|
+
static finalize() {
|
|
405
|
+
super.finalize();
|
|
406
|
+
|
|
407
|
+
// Make sure not to run the logic intended for PolymerElement when LitElement is used.
|
|
408
|
+
if (this.elementStyles) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const template = this.prototype._template;
|
|
413
|
+
if (!template || classHasThemes(this)) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
addStylesToTemplate(this.getStylesForThis(), template);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Covers LitElement based component styling
|
|
422
|
+
*
|
|
423
|
+
* @protected
|
|
424
|
+
*/
|
|
425
|
+
static finalizeStyles(styles) {
|
|
426
|
+
// The "styles" object originates from the "static get styles()" function of
|
|
427
|
+
// a LitElement based component. The theme styles are added after it
|
|
428
|
+
// so that they can override the component styles.
|
|
429
|
+
const themeStyles = this.getStylesForThis();
|
|
430
|
+
return styles ? [...super.finalizeStyles(styles), ...themeStyles] : themeStyles;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Get styles for the component type
|
|
435
|
+
*
|
|
436
|
+
* @private
|
|
437
|
+
*/
|
|
438
|
+
static getStylesForThis() {
|
|
439
|
+
const parent = Object.getPrototypeOf(this.prototype);
|
|
440
|
+
const inheritedThemes = (parent ? parent.constructor.__themes : []) || [];
|
|
441
|
+
this.__themes = [...inheritedThemes, ...getThemes(this.is)];
|
|
442
|
+
const themeStyles = this.__themes.flatMap((theme) => theme.styles);
|
|
443
|
+
// Remove duplicates
|
|
444
|
+
return themeStyles.filter((style, index) => index === themeStyles.lastIndexOf(style));
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
const comboBoxItem = i`
|
|
449
|
+
:host {
|
|
450
|
+
transition: background-color 100ms;
|
|
451
|
+
overflow: hidden;
|
|
452
|
+
--_lumo-item-selected-icon-display: block;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
@media (any-hover: hover) {
|
|
456
|
+
:host([focused]:not([disabled])) {
|
|
457
|
+
box-shadow: inset 0 0 0 2px var(--lumo-primary-color-50pct);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
`;
|
|
461
|
+
|
|
462
|
+
registerStyles('vaadin-combo-box-item', [item, comboBoxItem], {
|
|
463
|
+
moduleId: 'lumo-combo-box-item',
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* @license
|
|
468
|
+
* Copyright (c) 2022 - 2023 Vaadin Ltd.
|
|
469
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
470
|
+
*/
|
|
471
|
+
|
|
472
|
+
const loader = i`
|
|
473
|
+
[part~='loader'] {
|
|
474
|
+
box-sizing: border-box;
|
|
475
|
+
width: var(--lumo-icon-size-s);
|
|
476
|
+
height: var(--lumo-icon-size-s);
|
|
477
|
+
border: 2px solid transparent;
|
|
478
|
+
border-color: var(--lumo-primary-color-10pct) var(--lumo-primary-color-10pct) var(--lumo-primary-color)
|
|
479
|
+
var(--lumo-primary-color);
|
|
480
|
+
border-radius: calc(0.5 * var(--lumo-icon-size-s));
|
|
481
|
+
opacity: 0;
|
|
482
|
+
pointer-events: none;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
:host(:not([loading])) [part~='loader'] {
|
|
486
|
+
display: none;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
:host([loading]) [part~='loader'] {
|
|
490
|
+
animation: 1s linear infinite lumo-loader-rotate, 0.3s 0.1s lumo-loader-fade-in both;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
@keyframes lumo-loader-fade-in {
|
|
494
|
+
0% {
|
|
495
|
+
opacity: 0;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
100% {
|
|
499
|
+
opacity: 1;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
@keyframes lumo-loader-rotate {
|
|
504
|
+
0% {
|
|
505
|
+
transform: rotate(0deg);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
100% {
|
|
509
|
+
transform: rotate(360deg);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
`;
|
|
513
|
+
|
|
514
|
+
const comboBoxOverlay = i`
|
|
515
|
+
[part='content'] {
|
|
516
|
+
padding: 0;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/* When items are empty, the spinner needs some room */
|
|
520
|
+
:host(:not([closing])) [part~='content'] {
|
|
521
|
+
min-height: calc(2 * var(--lumo-space-s) + var(--lumo-icon-size-s));
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
[part~='overlay'] {
|
|
525
|
+
position: relative;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
:host([top-aligned]) [part~='overlay'] {
|
|
529
|
+
margin-top: var(--lumo-space-xs);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
:host([bottom-aligned]) [part~='overlay'] {
|
|
533
|
+
margin-bottom: var(--lumo-space-xs);
|
|
534
|
+
}
|
|
535
|
+
`;
|
|
536
|
+
|
|
537
|
+
const comboBoxLoader = i`
|
|
538
|
+
[part~='loader'] {
|
|
539
|
+
position: absolute;
|
|
540
|
+
z-index: 1;
|
|
541
|
+
left: var(--lumo-space-s);
|
|
542
|
+
right: var(--lumo-space-s);
|
|
543
|
+
top: var(--lumo-space-s);
|
|
544
|
+
margin-left: auto;
|
|
545
|
+
margin-inline-start: auto;
|
|
546
|
+
margin-inline-end: 0;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
:host([dir='rtl']) [part~='loader'] {
|
|
550
|
+
left: auto;
|
|
551
|
+
margin-left: 0;
|
|
552
|
+
margin-right: auto;
|
|
553
|
+
margin-inline-start: 0;
|
|
554
|
+
margin-inline-end: auto;
|
|
555
|
+
}
|
|
556
|
+
`;
|
|
557
|
+
|
|
558
|
+
registerStyles(
|
|
559
|
+
'vaadin-combo-box-overlay',
|
|
560
|
+
[
|
|
561
|
+
overlay,
|
|
562
|
+
menuOverlayCore,
|
|
563
|
+
comboBoxOverlay,
|
|
564
|
+
loader,
|
|
565
|
+
comboBoxLoader,
|
|
566
|
+
i`
|
|
567
|
+
:host {
|
|
568
|
+
--_vaadin-combo-box-items-container-border-width: var(--lumo-space-xs);
|
|
569
|
+
--_vaadin-combo-box-items-container-border-style: solid;
|
|
570
|
+
}
|
|
571
|
+
`,
|
|
572
|
+
],
|
|
573
|
+
{ moduleId: 'lumo-combo-box-overlay' },
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
const comboBox = i`
|
|
577
|
+
:host {
|
|
578
|
+
outline: none;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
[part='toggle-button']::before {
|
|
582
|
+
content: var(--lumo-icons-dropdown);
|
|
583
|
+
}
|
|
584
|
+
`;
|
|
585
|
+
|
|
586
|
+
registerStyles('vaadin-combo-box', [inputFieldShared, comboBox], { moduleId: 'lumo-combo-box' });
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* @license
|
|
590
|
+
* Copyright (c) 2021 - 2023 Vaadin Ltd.
|
|
591
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
592
|
+
*/
|
|
593
|
+
function defineCustomElement(CustomElement) {
|
|
594
|
+
const defined = customElements.get(CustomElement.is);
|
|
595
|
+
if (!defined) {
|
|
596
|
+
customElements.define(CustomElement.is, CustomElement);
|
|
597
|
+
} else {
|
|
598
|
+
const definedVersion = defined.version;
|
|
599
|
+
if (definedVersion && CustomElement.version && definedVersion === CustomElement.version) {
|
|
600
|
+
// Just loading the same thing again
|
|
601
|
+
console.warn(`The component ${CustomElement.is} has been loaded twice`);
|
|
602
|
+
} else {
|
|
603
|
+
console.error(
|
|
604
|
+
`Tried to define ${CustomElement.is} version ${CustomElement.version} when version ${defined.version} is already in use. Something will probably break.`,
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* @license
|
|
612
|
+
* Copyright (c) 2021 - 2023 Vaadin Ltd.
|
|
613
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
614
|
+
*/
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Array of Vaadin custom element classes that have been subscribed to the dir changes.
|
|
618
|
+
*/
|
|
619
|
+
const directionSubscribers = [];
|
|
620
|
+
|
|
621
|
+
function alignDirs(element, documentDir, elementDir = element.getAttribute('dir')) {
|
|
622
|
+
if (documentDir) {
|
|
623
|
+
element.setAttribute('dir', documentDir);
|
|
624
|
+
} else if (elementDir != null) {
|
|
625
|
+
element.removeAttribute('dir');
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function getDocumentDir() {
|
|
630
|
+
return document.documentElement.getAttribute('dir');
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function directionUpdater() {
|
|
634
|
+
const documentDir = getDocumentDir();
|
|
635
|
+
directionSubscribers.forEach((element) => {
|
|
636
|
+
alignDirs(element, documentDir);
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const directionObserver = new MutationObserver(directionUpdater);
|
|
641
|
+
directionObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['dir'] });
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* A mixin to handle `dir` attribute based on the one set on the `<html>` element.
|
|
645
|
+
*
|
|
646
|
+
* @polymerMixin
|
|
647
|
+
*/
|
|
648
|
+
const DirMixin = (superClass) =>
|
|
649
|
+
class VaadinDirMixin extends superClass {
|
|
650
|
+
static get properties() {
|
|
651
|
+
return {
|
|
652
|
+
/**
|
|
653
|
+
* @protected
|
|
654
|
+
*/
|
|
655
|
+
dir: {
|
|
656
|
+
type: String,
|
|
657
|
+
value: '',
|
|
658
|
+
reflectToAttribute: true,
|
|
659
|
+
converter: {
|
|
660
|
+
fromAttribute: (attr) => {
|
|
661
|
+
return !attr ? '' : attr;
|
|
662
|
+
},
|
|
663
|
+
toAttribute: (prop) => {
|
|
664
|
+
return prop === '' ? null : prop;
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* @return {boolean}
|
|
673
|
+
* @protected
|
|
674
|
+
*/
|
|
675
|
+
get __isRTL() {
|
|
676
|
+
return this.getAttribute('dir') === 'rtl';
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/** @protected */
|
|
680
|
+
connectedCallback() {
|
|
681
|
+
super.connectedCallback();
|
|
682
|
+
|
|
683
|
+
if (!this.hasAttribute('dir') || this.__restoreSubscription) {
|
|
684
|
+
this.__subscribe();
|
|
685
|
+
alignDirs(this, getDocumentDir(), null);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/** @protected */
|
|
690
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
691
|
+
super.attributeChangedCallback(name, oldValue, newValue);
|
|
692
|
+
if (name !== 'dir') {
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const documentDir = getDocumentDir();
|
|
697
|
+
|
|
698
|
+
// New value equals to the document direction and the element is not subscribed to the changes
|
|
699
|
+
const newValueEqlDocDir = newValue === documentDir && directionSubscribers.indexOf(this) === -1;
|
|
700
|
+
// Value was emptied and the element is not subscribed to the changes
|
|
701
|
+
const newValueEmptied = !newValue && oldValue && directionSubscribers.indexOf(this) === -1;
|
|
702
|
+
// New value is different and the old equals to document direction and the element is not subscribed to the changes
|
|
703
|
+
const newDiffValue = newValue !== documentDir && oldValue === documentDir;
|
|
704
|
+
|
|
705
|
+
if (newValueEqlDocDir || newValueEmptied) {
|
|
706
|
+
this.__subscribe();
|
|
707
|
+
alignDirs(this, documentDir, newValue);
|
|
708
|
+
} else if (newDiffValue) {
|
|
709
|
+
this.__unsubscribe();
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/** @protected */
|
|
714
|
+
disconnectedCallback() {
|
|
715
|
+
super.disconnectedCallback();
|
|
716
|
+
this.__restoreSubscription = directionSubscribers.includes(this);
|
|
717
|
+
this.__unsubscribe();
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/** @protected */
|
|
721
|
+
_valueToNodeAttribute(node, value, attribute) {
|
|
722
|
+
// Override default Polymer attribute reflection to match native behavior of HTMLElement.dir property
|
|
723
|
+
// If the property contains an empty string then it should not create an empty attribute
|
|
724
|
+
if (attribute === 'dir' && value === '' && !node.hasAttribute('dir')) {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
super._valueToNodeAttribute(node, value, attribute);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/** @protected */
|
|
731
|
+
_attributeToProperty(attribute, value, type) {
|
|
732
|
+
// Override default Polymer attribute reflection to match native behavior of HTMLElement.dir property
|
|
733
|
+
// If the attribute is removed, then the dir property should contain an empty string instead of null
|
|
734
|
+
if (attribute === 'dir' && !value) {
|
|
735
|
+
this.dir = '';
|
|
736
|
+
} else {
|
|
737
|
+
super._attributeToProperty(attribute, value, type);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/** @private */
|
|
742
|
+
__subscribe() {
|
|
743
|
+
if (!directionSubscribers.includes(this)) {
|
|
744
|
+
directionSubscribers.push(this);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/** @private */
|
|
749
|
+
__unsubscribe() {
|
|
750
|
+
if (directionSubscribers.includes(this)) {
|
|
751
|
+
directionSubscribers.splice(directionSubscribers.indexOf(this), 1);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* @license
|
|
758
|
+
* Copyright (c) 2015 - 2023 Vaadin Ltd.
|
|
759
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
760
|
+
*/
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* @polymerMixin
|
|
764
|
+
*/
|
|
765
|
+
const ComboBoxItemMixin = (superClass) =>
|
|
766
|
+
class ComboBoxItemMixinClass extends superClass {
|
|
767
|
+
static get properties() {
|
|
768
|
+
return {
|
|
769
|
+
/**
|
|
770
|
+
* The index of the item.
|
|
771
|
+
*/
|
|
772
|
+
index: {
|
|
773
|
+
type: Number,
|
|
774
|
+
},
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* The item to render.
|
|
778
|
+
*/
|
|
779
|
+
item: {
|
|
780
|
+
type: Object,
|
|
781
|
+
},
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* The text to render in the item.
|
|
785
|
+
*/
|
|
786
|
+
label: {
|
|
787
|
+
type: String,
|
|
788
|
+
},
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* True when item is selected.
|
|
792
|
+
*/
|
|
793
|
+
selected: {
|
|
794
|
+
type: Boolean,
|
|
795
|
+
value: false,
|
|
796
|
+
reflectToAttribute: true,
|
|
797
|
+
},
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* True when item is focused.
|
|
801
|
+
*/
|
|
802
|
+
focused: {
|
|
803
|
+
type: Boolean,
|
|
804
|
+
value: false,
|
|
805
|
+
reflectToAttribute: true,
|
|
806
|
+
},
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* Custom function for rendering the item content.
|
|
810
|
+
*/
|
|
811
|
+
renderer: {
|
|
812
|
+
type: Function,
|
|
813
|
+
},
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
static get observers() {
|
|
818
|
+
return ['__rendererOrItemChanged(renderer, index, item.*, selected, focused)', '__updateLabel(label, renderer)'];
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
static get observedAttributes() {
|
|
822
|
+
return [...super.observedAttributes, 'hidden'];
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
826
|
+
if (name === 'hidden' && newValue !== null) {
|
|
827
|
+
// The element is being hidden (by virtualizer). Mark one of the __rendererOrItemChanged
|
|
828
|
+
// dependencies as undefined to make sure it's called when the element is shown again
|
|
829
|
+
// and assigned properties with possibly identical values as before hiding.
|
|
830
|
+
this.index = undefined;
|
|
831
|
+
} else {
|
|
832
|
+
super.attributeChangedCallback(name, oldValue, newValue);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/** @protected */
|
|
837
|
+
connectedCallback() {
|
|
838
|
+
super.connectedCallback();
|
|
839
|
+
|
|
840
|
+
this._owner = this.parentNode.owner;
|
|
841
|
+
|
|
842
|
+
const hostDir = this._owner.getAttribute('dir');
|
|
843
|
+
if (hostDir) {
|
|
844
|
+
this.setAttribute('dir', hostDir);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Requests an update for the content of the item.
|
|
850
|
+
* While performing the update, it invokes the renderer passed in the `renderer` property.
|
|
851
|
+
*
|
|
852
|
+
* It is not guaranteed that the update happens immediately (synchronously) after it is requested.
|
|
853
|
+
*/
|
|
854
|
+
requestContentUpdate() {
|
|
855
|
+
if (!this.renderer) {
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const model = {
|
|
860
|
+
index: this.index,
|
|
861
|
+
item: this.item,
|
|
862
|
+
focused: this.focused,
|
|
863
|
+
selected: this.selected,
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
this.renderer(this, this._owner, model);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/** @private */
|
|
870
|
+
__rendererOrItemChanged(renderer, index, item) {
|
|
871
|
+
if (item === undefined || index === undefined) {
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
if (this._oldRenderer !== renderer) {
|
|
876
|
+
this.innerHTML = '';
|
|
877
|
+
// Whenever a Lit-based renderer is used, it assigns a Lit part to the node it was rendered into.
|
|
878
|
+
// When clearing the rendered content, this part needs to be manually disposed of.
|
|
879
|
+
// Otherwise, using a Lit-based renderer on the same node will throw an exception or render nothing afterward.
|
|
880
|
+
delete this._$litPart$;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
if (renderer) {
|
|
884
|
+
this._oldRenderer = renderer;
|
|
885
|
+
this.requestContentUpdate();
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/** @private */
|
|
890
|
+
__updateLabel(label, renderer) {
|
|
891
|
+
if (renderer) {
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
this.textContent = label;
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* @license
|
|
901
|
+
* Copyright (c) 2015 - 2023 Vaadin Ltd.
|
|
902
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
903
|
+
*/
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* An item element used by the `<vaadin-combo-box>` dropdown.
|
|
907
|
+
*
|
|
908
|
+
* ### Styling
|
|
909
|
+
*
|
|
910
|
+
* The following shadow DOM parts are available for styling:
|
|
911
|
+
*
|
|
912
|
+
* Part name | Description
|
|
913
|
+
* ------------|--------------
|
|
914
|
+
* `checkmark` | The graphical checkmark shown for a selected item
|
|
915
|
+
* `content` | The element that wraps the item content
|
|
916
|
+
*
|
|
917
|
+
* The following state attributes are exposed for styling:
|
|
918
|
+
*
|
|
919
|
+
* Attribute | Description
|
|
920
|
+
* -------------|-------------
|
|
921
|
+
* `selected` | Set when the item is selected
|
|
922
|
+
* `focused` | Set when the item is focused
|
|
923
|
+
*
|
|
924
|
+
* See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
|
|
925
|
+
*
|
|
926
|
+
* @customElement
|
|
927
|
+
* @mixes ComboBoxItemMixin
|
|
928
|
+
* @mixes ThemableMixin
|
|
929
|
+
* @mixes DirMixin
|
|
930
|
+
* @private
|
|
931
|
+
*/
|
|
932
|
+
class ComboBoxItem extends ComboBoxItemMixin(ThemableMixin(DirMixin(PolymerElement))) {
|
|
933
|
+
static get template() {
|
|
934
|
+
return html`
|
|
935
|
+
<style>
|
|
936
|
+
:host {
|
|
937
|
+
display: block;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
:host([hidden]) {
|
|
941
|
+
display: none;
|
|
942
|
+
}
|
|
943
|
+
</style>
|
|
944
|
+
<span part="checkmark" aria-hidden="true"></span>
|
|
945
|
+
<div part="content">
|
|
946
|
+
<slot></slot>
|
|
947
|
+
</div>
|
|
948
|
+
`;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
static get is() {
|
|
952
|
+
return 'vaadin-combo-box-item';
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
defineCustomElement(ComboBoxItem);
|
|
957
|
+
|
|
394
958
|
/**
|
|
395
959
|
* @license
|
|
396
|
-
* Copyright (c) 2015 -
|
|
960
|
+
* Copyright (c) 2015 - 2023 Vaadin Ltd.
|
|
397
961
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
398
962
|
*/
|
|
399
963
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
964
|
+
/**
|
|
965
|
+
* @polymerMixin
|
|
966
|
+
* @mixes PositionMixin
|
|
967
|
+
*/
|
|
968
|
+
const ComboBoxOverlayMixin = (superClass) =>
|
|
969
|
+
class ComboBoxOverlayMixin extends PositionMixin(superClass) {
|
|
970
|
+
static get observers() {
|
|
971
|
+
return ['_setOverlayWidth(positionTarget, opened)'];
|
|
405
972
|
}
|
|
406
973
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
974
|
+
constructor() {
|
|
975
|
+
super();
|
|
976
|
+
|
|
977
|
+
this.requiredVerticalSpace = 200;
|
|
411
978
|
}
|
|
412
|
-
`,
|
|
413
|
-
{ moduleId: 'vaadin-combo-box-overlay-styles' },
|
|
414
|
-
);
|
|
415
979
|
|
|
416
|
-
|
|
980
|
+
/** @protected */
|
|
981
|
+
connectedCallback() {
|
|
982
|
+
super.connectedCallback();
|
|
983
|
+
|
|
984
|
+
const comboBox = this._comboBox;
|
|
985
|
+
|
|
986
|
+
const hostDir = comboBox && comboBox.getAttribute('dir');
|
|
987
|
+
if (hostDir) {
|
|
988
|
+
this.setAttribute('dir', hostDir);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* Override method inherited from `Overlay`
|
|
994
|
+
* to not close on position target click.
|
|
995
|
+
*
|
|
996
|
+
* @param {Event} event
|
|
997
|
+
* @return {boolean}
|
|
998
|
+
* @protected
|
|
999
|
+
*/
|
|
1000
|
+
_shouldCloseOnOutsideClick(event) {
|
|
1001
|
+
const eventPath = event.composedPath();
|
|
1002
|
+
return !eventPath.includes(this.positionTarget) && !eventPath.includes(this);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/** @private */
|
|
1006
|
+
_setOverlayWidth(positionTarget, opened) {
|
|
1007
|
+
if (positionTarget && opened) {
|
|
1008
|
+
const propPrefix = this.localName;
|
|
1009
|
+
this.style.setProperty(`--_${propPrefix}-default-width`, `${positionTarget.clientWidth}px`);
|
|
1010
|
+
|
|
1011
|
+
const customWidth = getComputedStyle(this._comboBox).getPropertyValue(`--${propPrefix}-width`);
|
|
1012
|
+
|
|
1013
|
+
if (customWidth === '') {
|
|
1014
|
+
this.style.removeProperty(`--${propPrefix}-width`);
|
|
1015
|
+
} else {
|
|
1016
|
+
this.style.setProperty(`--${propPrefix}-width`, customWidth);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
this._updatePosition();
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* @license
|
|
1026
|
+
* Copyright (c) 2015 - 2023 Vaadin Ltd.
|
|
1027
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
1028
|
+
*/
|
|
1029
|
+
|
|
1030
|
+
const comboBoxOverlayStyles = i`
|
|
1031
|
+
#overlay {
|
|
1032
|
+
width: var(--vaadin-combo-box-overlay-width, var(--_vaadin-combo-box-overlay-default-width, auto));
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
[part='content'] {
|
|
1036
|
+
display: flex;
|
|
1037
|
+
flex-direction: column;
|
|
1038
|
+
height: 100%;
|
|
1039
|
+
}
|
|
1040
|
+
`;
|
|
1041
|
+
|
|
1042
|
+
registerStyles('vaadin-combo-box-overlay', [overlayStyles, comboBoxOverlayStyles], {
|
|
1043
|
+
moduleId: 'vaadin-combo-box-overlay-styles',
|
|
1044
|
+
});
|
|
417
1045
|
|
|
418
1046
|
/**
|
|
419
1047
|
* An element used internally by `<vaadin-combo-box>`. Not intended to be used separately.
|
|
420
1048
|
*
|
|
421
|
-
* @
|
|
1049
|
+
* @customElement
|
|
1050
|
+
* @extends HTMLElement
|
|
1051
|
+
* @mixes ComboBoxOverlayMixin
|
|
1052
|
+
* @mixes DirMixin
|
|
1053
|
+
* @mixes OverlayMixin
|
|
1054
|
+
* @mixes ThemableMixin
|
|
422
1055
|
* @private
|
|
423
1056
|
*/
|
|
424
|
-
class ComboBoxOverlay extends
|
|
1057
|
+
class ComboBoxOverlay extends ComboBoxOverlayMixin(OverlayMixin(DirMixin(ThemableMixin(PolymerElement)))) {
|
|
425
1058
|
static get is() {
|
|
426
1059
|
return 'vaadin-combo-box-overlay';
|
|
427
1060
|
}
|
|
428
1061
|
|
|
429
1062
|
static get template() {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
1063
|
+
return html`
|
|
1064
|
+
<div id="backdrop" part="backdrop" hidden></div>
|
|
1065
|
+
<div part="overlay" id="overlay">
|
|
1066
|
+
<div part="loader"></div>
|
|
1067
|
+
<div part="content" id="content"><slot></slot></div>
|
|
1068
|
+
</div>
|
|
1069
|
+
`;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
defineCustomElement(ComboBoxOverlay);
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* @license
|
|
1077
|
+
* Copyright (c) 2023 Vaadin Ltd.
|
|
1078
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
1079
|
+
*/
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* Convenience method for reading a value from a path.
|
|
1083
|
+
*
|
|
1084
|
+
* @param {string} path
|
|
1085
|
+
* @param {object} object
|
|
1086
|
+
*/
|
|
1087
|
+
function get(path, object) {
|
|
1088
|
+
return path.split('.').reduce((obj, property) => (obj ? obj[property] : undefined), object);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* @license
|
|
1093
|
+
* Copyright (c) 2021 - 2023 Vaadin Ltd.
|
|
1094
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
1095
|
+
*/
|
|
1096
|
+
|
|
1097
|
+
let uniqueId = 0;
|
|
1098
|
+
|
|
1099
|
+
/**
|
|
1100
|
+
* Returns a unique integer id.
|
|
1101
|
+
*
|
|
1102
|
+
* @return {number}
|
|
1103
|
+
*/
|
|
1104
|
+
function generateUniqueId() {
|
|
1105
|
+
// eslint-disable-next-line no-plusplus
|
|
1106
|
+
return uniqueId++;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* @license
|
|
1111
|
+
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
1112
|
+
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
1113
|
+
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
1114
|
+
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
1115
|
+
* Code distributed by Google as part of the polymer project is also
|
|
1116
|
+
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
1117
|
+
*/
|
|
1118
|
+
|
|
1119
|
+
/**
|
|
1120
|
+
* @fileoverview
|
|
1121
|
+
*
|
|
1122
|
+
* This module provides a number of strategies for enqueuing asynchronous
|
|
1123
|
+
* tasks. Each sub-module provides a standard `run(fn)` interface that returns a
|
|
1124
|
+
* handle, and a `cancel(handle)` interface for canceling async tasks before
|
|
1125
|
+
* they run.
|
|
1126
|
+
*
|
|
1127
|
+
* @summary Module that provides a number of strategies for enqueuing
|
|
1128
|
+
* asynchronous tasks.
|
|
1129
|
+
*/
|
|
434
1130
|
|
|
435
|
-
|
|
1131
|
+
let microtaskCurrHandle = 0;
|
|
1132
|
+
let microtaskLastHandle = 0;
|
|
1133
|
+
const microtaskCallbacks = [];
|
|
1134
|
+
let microtaskScheduled = false;
|
|
1135
|
+
|
|
1136
|
+
function microtaskFlush() {
|
|
1137
|
+
microtaskScheduled = false;
|
|
1138
|
+
const len = microtaskCallbacks.length;
|
|
1139
|
+
for (let i = 0; i < len; i++) {
|
|
1140
|
+
const cb = microtaskCallbacks[i];
|
|
1141
|
+
if (cb) {
|
|
1142
|
+
try {
|
|
1143
|
+
cb();
|
|
1144
|
+
} catch (e) {
|
|
1145
|
+
setTimeout(() => {
|
|
1146
|
+
throw e;
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
436
1150
|
}
|
|
1151
|
+
microtaskCallbacks.splice(0, len);
|
|
1152
|
+
microtaskLastHandle += len;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* Async interface wrapper around `setTimeout`.
|
|
1157
|
+
*
|
|
1158
|
+
* @namespace
|
|
1159
|
+
* @summary Async interface wrapper around `setTimeout`.
|
|
1160
|
+
*/
|
|
1161
|
+
const timeOut = {
|
|
1162
|
+
/**
|
|
1163
|
+
* Returns a sub-module with the async interface providing the provided
|
|
1164
|
+
* delay.
|
|
1165
|
+
*
|
|
1166
|
+
* @memberof timeOut
|
|
1167
|
+
* @param {number=} delay Time to wait before calling callbacks in ms
|
|
1168
|
+
* @return {!AsyncInterface} An async timeout interface
|
|
1169
|
+
*/
|
|
1170
|
+
after(delay) {
|
|
1171
|
+
return {
|
|
1172
|
+
run(fn) {
|
|
1173
|
+
return window.setTimeout(fn, delay);
|
|
1174
|
+
},
|
|
1175
|
+
cancel(handle) {
|
|
1176
|
+
window.clearTimeout(handle);
|
|
1177
|
+
},
|
|
1178
|
+
};
|
|
1179
|
+
},
|
|
1180
|
+
/**
|
|
1181
|
+
* Enqueues a function called in the next task.
|
|
1182
|
+
*
|
|
1183
|
+
* @memberof timeOut
|
|
1184
|
+
* @param {!Function} fn Callback to run
|
|
1185
|
+
* @param {number=} delay Delay in milliseconds
|
|
1186
|
+
* @return {number} Handle used for canceling task
|
|
1187
|
+
*/
|
|
1188
|
+
run(fn, delay) {
|
|
1189
|
+
return window.setTimeout(fn, delay);
|
|
1190
|
+
},
|
|
1191
|
+
/**
|
|
1192
|
+
* Cancels a previously enqueued `timeOut` callback.
|
|
1193
|
+
*
|
|
1194
|
+
* @memberof timeOut
|
|
1195
|
+
* @param {number} handle Handle returned from `run` of callback to cancel
|
|
1196
|
+
* @return {void}
|
|
1197
|
+
*/
|
|
1198
|
+
cancel(handle) {
|
|
1199
|
+
window.clearTimeout(handle);
|
|
1200
|
+
},
|
|
1201
|
+
};
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* Async interface wrapper around `requestAnimationFrame`.
|
|
1205
|
+
*
|
|
1206
|
+
* @namespace
|
|
1207
|
+
* @summary Async interface wrapper around `requestAnimationFrame`.
|
|
1208
|
+
*/
|
|
1209
|
+
const animationFrame = {
|
|
1210
|
+
/**
|
|
1211
|
+
* Enqueues a function called at `requestAnimationFrame` timing.
|
|
1212
|
+
*
|
|
1213
|
+
* @memberof animationFrame
|
|
1214
|
+
* @param {function(number):void} fn Callback to run
|
|
1215
|
+
* @return {number} Handle used for canceling task
|
|
1216
|
+
*/
|
|
1217
|
+
run(fn) {
|
|
1218
|
+
return window.requestAnimationFrame(fn);
|
|
1219
|
+
},
|
|
1220
|
+
/**
|
|
1221
|
+
* Cancels a previously enqueued `animationFrame` callback.
|
|
1222
|
+
*
|
|
1223
|
+
* @memberof animationFrame
|
|
1224
|
+
* @param {number} handle Handle returned from `run` of callback to cancel
|
|
1225
|
+
* @return {void}
|
|
1226
|
+
*/
|
|
1227
|
+
cancel(handle) {
|
|
1228
|
+
window.cancelAnimationFrame(handle);
|
|
1229
|
+
},
|
|
1230
|
+
};
|
|
1231
|
+
|
|
1232
|
+
/**
|
|
1233
|
+
* Async interface wrapper around `requestIdleCallback`. Falls back to
|
|
1234
|
+
* `setTimeout` on browsers that do not support `requestIdleCallback`.
|
|
1235
|
+
*
|
|
1236
|
+
* @namespace
|
|
1237
|
+
* @summary Async interface wrapper around `requestIdleCallback`.
|
|
1238
|
+
*/
|
|
1239
|
+
const idlePeriod = {
|
|
1240
|
+
/**
|
|
1241
|
+
* Enqueues a function called at `requestIdleCallback` timing.
|
|
1242
|
+
*
|
|
1243
|
+
* @memberof idlePeriod
|
|
1244
|
+
* @param {function(!IdleDeadline):void} fn Callback to run
|
|
1245
|
+
* @return {number} Handle used for canceling task
|
|
1246
|
+
*/
|
|
1247
|
+
run(fn) {
|
|
1248
|
+
return window.requestIdleCallback ? window.requestIdleCallback(fn) : window.setTimeout(fn, 16);
|
|
1249
|
+
},
|
|
1250
|
+
/**
|
|
1251
|
+
* Cancels a previously enqueued `idlePeriod` callback.
|
|
1252
|
+
*
|
|
1253
|
+
* @memberof idlePeriod
|
|
1254
|
+
* @param {number} handle Handle returned from `run` of callback to cancel
|
|
1255
|
+
* @return {void}
|
|
1256
|
+
*/
|
|
1257
|
+
cancel(handle) {
|
|
1258
|
+
if (window.cancelIdleCallback) {
|
|
1259
|
+
window.cancelIdleCallback(handle);
|
|
1260
|
+
} else {
|
|
1261
|
+
window.clearTimeout(handle);
|
|
1262
|
+
}
|
|
1263
|
+
},
|
|
1264
|
+
};
|
|
1265
|
+
|
|
1266
|
+
/**
|
|
1267
|
+
* Async interface for enqueuing callbacks that run at microtask timing.
|
|
1268
|
+
*
|
|
1269
|
+
* @namespace
|
|
1270
|
+
* @summary Async interface for enqueuing callbacks that run at microtask
|
|
1271
|
+
* timing.
|
|
1272
|
+
*/
|
|
1273
|
+
const microTask = {
|
|
1274
|
+
/**
|
|
1275
|
+
* Enqueues a function called at microtask timing.
|
|
1276
|
+
*
|
|
1277
|
+
* @memberof microTask
|
|
1278
|
+
* @param {!Function=} callback Callback to run
|
|
1279
|
+
* @return {number} Handle used for canceling task
|
|
1280
|
+
*/
|
|
1281
|
+
run(callback) {
|
|
1282
|
+
if (!microtaskScheduled) {
|
|
1283
|
+
microtaskScheduled = true;
|
|
1284
|
+
queueMicrotask(() => microtaskFlush());
|
|
1285
|
+
}
|
|
1286
|
+
microtaskCallbacks.push(callback);
|
|
1287
|
+
const result = microtaskCurrHandle;
|
|
1288
|
+
microtaskCurrHandle += 1;
|
|
1289
|
+
return result;
|
|
1290
|
+
},
|
|
1291
|
+
|
|
1292
|
+
/**
|
|
1293
|
+
* Cancels a previously enqueued `microTask` callback.
|
|
1294
|
+
*
|
|
1295
|
+
* @memberof microTask
|
|
1296
|
+
* @param {number} handle Handle returned from `run` of callback to cancel
|
|
1297
|
+
* @return {void}
|
|
1298
|
+
*/
|
|
1299
|
+
cancel(handle) {
|
|
1300
|
+
const idx = handle - microtaskLastHandle;
|
|
1301
|
+
if (idx >= 0) {
|
|
1302
|
+
if (!microtaskCallbacks[idx]) {
|
|
1303
|
+
throw new Error(`invalid async handle: ${handle}`);
|
|
1304
|
+
}
|
|
1305
|
+
microtaskCallbacks[idx] = null;
|
|
1306
|
+
}
|
|
1307
|
+
},
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
/**
|
|
1311
|
+
* @license
|
|
1312
|
+
* Copyright (c) 2021 - 2023 Vaadin Ltd.
|
|
1313
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
1314
|
+
*/
|
|
1315
|
+
|
|
1316
|
+
const testUserAgent = (regexp) => regexp.test(navigator.userAgent);
|
|
1317
|
+
|
|
1318
|
+
const testPlatform = (regexp) => regexp.test(navigator.platform);
|
|
1319
|
+
|
|
1320
|
+
const testVendor = (regexp) => regexp.test(navigator.vendor);
|
|
1321
|
+
|
|
1322
|
+
testUserAgent(/Android/u);
|
|
1323
|
+
|
|
1324
|
+
testUserAgent(/Chrome/u) && testVendor(/Google Inc/u);
|
|
1325
|
+
|
|
1326
|
+
testUserAgent(/Firefox/u);
|
|
437
1327
|
|
|
438
|
-
|
|
439
|
-
|
|
1328
|
+
// IPadOS 13 lies and says it's a Mac, but we can distinguish by detecting touch support.
|
|
1329
|
+
testPlatform(/^iPad/u) || (testPlatform(/^Mac/u) && navigator.maxTouchPoints > 1);
|
|
1330
|
+
|
|
1331
|
+
testPlatform(/^iPhone/u);
|
|
1332
|
+
|
|
1333
|
+
const isSafari = testUserAgent(/^((?!chrome|android).)*safari/iu);
|
|
1334
|
+
|
|
1335
|
+
const isTouch = (() => {
|
|
1336
|
+
try {
|
|
1337
|
+
document.createEvent('TouchEvent');
|
|
1338
|
+
return true;
|
|
1339
|
+
} catch (e) {
|
|
1340
|
+
return false;
|
|
440
1341
|
}
|
|
1342
|
+
})();
|
|
441
1343
|
|
|
442
|
-
|
|
443
|
-
|
|
1344
|
+
/**
|
|
1345
|
+
@license
|
|
1346
|
+
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
1347
|
+
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
1348
|
+
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
1349
|
+
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
1350
|
+
Code distributed by Google as part of the polymer project is also
|
|
1351
|
+
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
1352
|
+
*/
|
|
444
1353
|
|
|
445
|
-
|
|
1354
|
+
const debouncerQueue = new Set();
|
|
446
1355
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
1356
|
+
/**
|
|
1357
|
+
* @summary Collapse multiple callbacks into one invocation after a timer.
|
|
1358
|
+
*/
|
|
1359
|
+
class Debouncer {
|
|
1360
|
+
/**
|
|
1361
|
+
* Creates a debouncer if no debouncer is passed as a parameter
|
|
1362
|
+
* or it cancels an active debouncer otherwise. The following
|
|
1363
|
+
* example shows how a debouncer can be called multiple times within a
|
|
1364
|
+
* microtask and "debounced" such that the provided callback function is
|
|
1365
|
+
* called once. Add this method to a custom element:
|
|
1366
|
+
*
|
|
1367
|
+
* ```js
|
|
1368
|
+
* import {microTask} from '@vaadin/component-base/src/async.js';
|
|
1369
|
+
* import {Debouncer} from '@vaadin/component-base/src/debounce.js';
|
|
1370
|
+
* // ...
|
|
1371
|
+
*
|
|
1372
|
+
* _debounceWork() {
|
|
1373
|
+
* this._debounceJob = Debouncer.debounce(this._debounceJob,
|
|
1374
|
+
* microTask, () => this._doWork());
|
|
1375
|
+
* }
|
|
1376
|
+
* ```
|
|
1377
|
+
*
|
|
1378
|
+
* If the `_debounceWork` method is called multiple times within the same
|
|
1379
|
+
* microtask, the `_doWork` function will be called only once at the next
|
|
1380
|
+
* microtask checkpoint.
|
|
1381
|
+
*
|
|
1382
|
+
* Note: In testing it is often convenient to avoid asynchrony. To accomplish
|
|
1383
|
+
* this with a debouncer, you can use `enqueueDebouncer` and
|
|
1384
|
+
* `flush`. For example, extend the above example by adding
|
|
1385
|
+
* `enqueueDebouncer(this._debounceJob)` at the end of the
|
|
1386
|
+
* `_debounceWork` method. Then in a test, call `flush` to ensure
|
|
1387
|
+
* the debouncer has completed.
|
|
1388
|
+
*
|
|
1389
|
+
* @param {Debouncer?} debouncer Debouncer object.
|
|
1390
|
+
* @param {!AsyncInterface} asyncModule Object with Async interface
|
|
1391
|
+
* @param {function()} callback Callback to run.
|
|
1392
|
+
* @return {!Debouncer} Returns a debouncer object.
|
|
1393
|
+
*/
|
|
1394
|
+
static debounce(debouncer, asyncModule, callback) {
|
|
1395
|
+
if (debouncer instanceof Debouncer) {
|
|
1396
|
+
// Cancel the async callback, but leave in debouncerQueue if it was
|
|
1397
|
+
// enqueued, to maintain 1.x flush order
|
|
1398
|
+
debouncer._cancelAsync();
|
|
1399
|
+
} else {
|
|
1400
|
+
debouncer = new Debouncer();
|
|
450
1401
|
}
|
|
1402
|
+
debouncer.setConfig(asyncModule, callback);
|
|
1403
|
+
return debouncer;
|
|
451
1404
|
}
|
|
452
1405
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
const content = this.shadowRoot.querySelector('[part~="content"]');
|
|
458
|
-
content.parentNode.insertBefore(loader, content);
|
|
459
|
-
this.requiredVerticalSpace = 200;
|
|
1406
|
+
constructor() {
|
|
1407
|
+
this._asyncModule = null;
|
|
1408
|
+
this._callback = null;
|
|
1409
|
+
this._timer = null;
|
|
460
1410
|
}
|
|
461
1411
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
1412
|
+
/**
|
|
1413
|
+
* Sets the scheduler; that is, a module with the Async interface,
|
|
1414
|
+
* a callback and optional arguments to be passed to the run function
|
|
1415
|
+
* from the async module.
|
|
1416
|
+
*
|
|
1417
|
+
* @param {!AsyncInterface} asyncModule Object with Async interface.
|
|
1418
|
+
* @param {function()} callback Callback to run.
|
|
1419
|
+
* @return {void}
|
|
1420
|
+
*/
|
|
1421
|
+
setConfig(asyncModule, callback) {
|
|
1422
|
+
this._asyncModule = asyncModule;
|
|
1423
|
+
this._callback = callback;
|
|
1424
|
+
this._timer = this._asyncModule.run(() => {
|
|
1425
|
+
this._timer = null;
|
|
1426
|
+
debouncerQueue.delete(this);
|
|
1427
|
+
this._callback();
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
/**
|
|
1432
|
+
* Cancels an active debouncer and returns a reference to itself.
|
|
1433
|
+
*
|
|
1434
|
+
* @return {void}
|
|
1435
|
+
*/
|
|
1436
|
+
cancel() {
|
|
1437
|
+
if (this.isActive()) {
|
|
1438
|
+
this._cancelAsync();
|
|
1439
|
+
// Canceling a debouncer removes its spot from the flush queue,
|
|
1440
|
+
// so if a debouncer is manually canceled and re-debounced, it
|
|
1441
|
+
// will reset its flush order (this is a very minor difference from 1.x)
|
|
1442
|
+
// Re-debouncing via the `debounce` API retains the 1.x FIFO flush order
|
|
1443
|
+
debouncerQueue.delete(this);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
/**
|
|
1448
|
+
* Cancels a debouncer's async callback.
|
|
1449
|
+
*
|
|
1450
|
+
* @return {void}
|
|
1451
|
+
*/
|
|
1452
|
+
_cancelAsync() {
|
|
1453
|
+
if (this.isActive()) {
|
|
1454
|
+
this._asyncModule.cancel(/** @type {number} */ (this._timer));
|
|
1455
|
+
this._timer = null;
|
|
466
1456
|
}
|
|
467
1457
|
}
|
|
468
1458
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
1459
|
+
/**
|
|
1460
|
+
* Flushes an active debouncer and returns a reference to itself.
|
|
1461
|
+
*
|
|
1462
|
+
* @return {void}
|
|
1463
|
+
*/
|
|
1464
|
+
flush() {
|
|
1465
|
+
if (this.isActive()) {
|
|
1466
|
+
this.cancel();
|
|
1467
|
+
this._callback();
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
473
1470
|
|
|
474
|
-
|
|
1471
|
+
/**
|
|
1472
|
+
* Returns true if the debouncer is active.
|
|
1473
|
+
*
|
|
1474
|
+
* @return {boolean} True if active.
|
|
1475
|
+
*/
|
|
1476
|
+
isActive() {
|
|
1477
|
+
return this._timer != null;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
475
1480
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
1481
|
+
/**
|
|
1482
|
+
* Adds a `Debouncer` to a list of globally flushable tasks.
|
|
1483
|
+
*
|
|
1484
|
+
* @param {!Debouncer} debouncer Debouncer to enqueue
|
|
1485
|
+
* @return {void}
|
|
1486
|
+
*/
|
|
1487
|
+
function enqueueDebouncer(debouncer) {
|
|
1488
|
+
debouncerQueue.add(debouncer);
|
|
1489
|
+
}
|
|
481
1490
|
|
|
482
|
-
|
|
1491
|
+
/**
|
|
1492
|
+
* Flushes any enqueued debouncers
|
|
1493
|
+
*
|
|
1494
|
+
* @return {boolean} Returns whether any debouncers were flushed
|
|
1495
|
+
*/
|
|
1496
|
+
function flushDebouncers() {
|
|
1497
|
+
const didFlush = Boolean(debouncerQueue.size);
|
|
1498
|
+
// If new debouncers are added while flushing, Set.forEach will ensure
|
|
1499
|
+
// newly added ones are also flushed
|
|
1500
|
+
debouncerQueue.forEach((debouncer) => {
|
|
1501
|
+
try {
|
|
1502
|
+
debouncer.flush();
|
|
1503
|
+
} catch (e) {
|
|
1504
|
+
setTimeout(() => {
|
|
1505
|
+
throw e;
|
|
1506
|
+
});
|
|
483
1507
|
}
|
|
484
|
-
}
|
|
1508
|
+
});
|
|
1509
|
+
return didFlush;
|
|
485
1510
|
}
|
|
486
1511
|
|
|
487
|
-
|
|
1512
|
+
const flush = () => {
|
|
1513
|
+
let debouncers;
|
|
1514
|
+
do {
|
|
1515
|
+
debouncers = flushDebouncers();
|
|
1516
|
+
} while (debouncers);
|
|
1517
|
+
};
|
|
488
1518
|
|
|
489
1519
|
/**
|
|
490
1520
|
* @license
|
|
@@ -496,7 +1526,7 @@ customElements.define(ComboBoxOverlay.is, ComboBoxOverlay);
|
|
|
496
1526
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
497
1527
|
*/
|
|
498
1528
|
|
|
499
|
-
const IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/);
|
|
1529
|
+
const IOS = navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/u);
|
|
500
1530
|
const IOS_TOUCH_SCROLLING = IOS && IOS[1] >= 8;
|
|
501
1531
|
const DEFAULT_PHYSICAL_COUNT = 3;
|
|
502
1532
|
|
|
@@ -986,9 +2016,12 @@ const ironList = {
|
|
|
986
2016
|
this._physicalIndexForKey = {};
|
|
987
2017
|
this._firstVisibleIndexVal = null;
|
|
988
2018
|
this._lastVisibleIndexVal = null;
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
2019
|
+
if (!this._physicalItems) {
|
|
2020
|
+
this._physicalItems = [];
|
|
2021
|
+
}
|
|
2022
|
+
if (!this._physicalSizes) {
|
|
2023
|
+
this._physicalSizes = [];
|
|
2024
|
+
}
|
|
992
2025
|
this._physicalStart = 0;
|
|
993
2026
|
if (this._scrollTop > this._scrollOffset) {
|
|
994
2027
|
this._resetScrollPosition(0);
|
|
@@ -1097,16 +2130,21 @@ const ironList = {
|
|
|
1097
2130
|
* @param {boolean=} forceUpdate If true, updates the height no matter what.
|
|
1098
2131
|
*/
|
|
1099
2132
|
_updateScrollerSize(forceUpdate) {
|
|
1100
|
-
|
|
2133
|
+
const estScrollHeight =
|
|
1101
2134
|
this._physicalBottom +
|
|
1102
2135
|
Math.max(this._virtualCount - this._physicalCount - this._virtualStart, 0) * this._physicalAverage;
|
|
1103
2136
|
|
|
1104
|
-
|
|
1105
|
-
|
|
2137
|
+
this._estScrollHeight = estScrollHeight;
|
|
2138
|
+
|
|
1106
2139
|
// Amortize height adjustment, so it won't trigger large repaints too often.
|
|
1107
|
-
if (
|
|
1108
|
-
|
|
1109
|
-
this._scrollHeight
|
|
2140
|
+
if (
|
|
2141
|
+
forceUpdate ||
|
|
2142
|
+
this._scrollHeight === 0 ||
|
|
2143
|
+
this._scrollPosition >= estScrollHeight - this._physicalSize ||
|
|
2144
|
+
Math.abs(estScrollHeight - this._scrollHeight) >= this._viewportHeight
|
|
2145
|
+
) {
|
|
2146
|
+
this.$.items.style.height = `${estScrollHeight}px`;
|
|
2147
|
+
this._scrollHeight = estScrollHeight;
|
|
1110
2148
|
}
|
|
1111
2149
|
},
|
|
1112
2150
|
|
|
@@ -1202,7 +2240,9 @@ const ironList = {
|
|
|
1202
2240
|
},
|
|
1203
2241
|
|
|
1204
2242
|
_debounce(name, cb, asyncModule) {
|
|
1205
|
-
|
|
2243
|
+
if (!this._debouncers) {
|
|
2244
|
+
this._debouncers = {};
|
|
2245
|
+
}
|
|
1206
2246
|
this._debouncers[name] = Debouncer.debounce(this._debouncers[name], asyncModule, cb.bind(this));
|
|
1207
2247
|
enqueueDebouncer(this._debouncers[name]);
|
|
1208
2248
|
},
|
|
@@ -1210,7 +2250,7 @@ const ironList = {
|
|
|
1210
2250
|
|
|
1211
2251
|
/**
|
|
1212
2252
|
* @license
|
|
1213
|
-
* Copyright (c) 2021 -
|
|
2253
|
+
* Copyright (c) 2021 - 2023 Vaadin Ltd.
|
|
1214
2254
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
1215
2255
|
*/
|
|
1216
2256
|
|
|
@@ -1340,11 +2380,15 @@ class IronListAdapter {
|
|
|
1340
2380
|
}
|
|
1341
2381
|
|
|
1342
2382
|
update(startIndex = 0, endIndex = this.size - 1) {
|
|
2383
|
+
const updatedElements = [];
|
|
1343
2384
|
this.__getVisibleElements().forEach((el) => {
|
|
1344
2385
|
if (el.__virtualIndex >= startIndex && el.__virtualIndex <= endIndex) {
|
|
1345
2386
|
this.__updateElement(el, el.__virtualIndex, true);
|
|
2387
|
+
updatedElements.push(el);
|
|
1346
2388
|
}
|
|
1347
2389
|
});
|
|
2390
|
+
|
|
2391
|
+
this.__afterElementsUpdated(updatedElements);
|
|
1348
2392
|
}
|
|
1349
2393
|
|
|
1350
2394
|
/**
|
|
@@ -1407,28 +2451,40 @@ class IronListAdapter {
|
|
|
1407
2451
|
this.updateElement(el, index);
|
|
1408
2452
|
el.__lastUpdatedIndex = index;
|
|
1409
2453
|
}
|
|
2454
|
+
}
|
|
1410
2455
|
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
2456
|
+
/**
|
|
2457
|
+
* Called synchronously right after elements have been updated.
|
|
2458
|
+
* This is a good place to do any post-update work.
|
|
2459
|
+
*
|
|
2460
|
+
* @param {!Array<!HTMLElement>} updatedElements
|
|
2461
|
+
*/
|
|
2462
|
+
__afterElementsUpdated(updatedElements) {
|
|
2463
|
+
updatedElements.forEach((el) => {
|
|
2464
|
+
const elementHeight = el.offsetHeight;
|
|
2465
|
+
if (elementHeight === 0) {
|
|
2466
|
+
// If the elements have 0 height after update (for example due to lazy rendering),
|
|
2467
|
+
// it results in iron-list requesting to create an unlimited count of elements.
|
|
2468
|
+
// Assign a temporary placeholder sizing to elements that would otherwise end up having
|
|
2469
|
+
// no height.
|
|
2470
|
+
el.style.paddingTop = `${this.__placeholderHeight}px`;
|
|
2471
|
+
|
|
2472
|
+
// Manually schedule the resize handler to make sure the placeholder padding is
|
|
2473
|
+
// cleared in case the resize observer never triggers.
|
|
2474
|
+
this.__placeholderClearDebouncer = Debouncer.debounce(this.__placeholderClearDebouncer, animationFrame, () =>
|
|
2475
|
+
this._resizeHandler(),
|
|
2476
|
+
);
|
|
2477
|
+
} else {
|
|
2478
|
+
// Add element height to the queue
|
|
2479
|
+
this.__elementHeightQueue.push(elementHeight);
|
|
2480
|
+
this.__elementHeightQueue.shift();
|
|
2481
|
+
|
|
2482
|
+
// Calculate new placeholder height based on the average of the defined values in the
|
|
2483
|
+
// element height queue
|
|
2484
|
+
const filteredHeights = this.__elementHeightQueue.filter((h) => h !== undefined);
|
|
2485
|
+
this.__placeholderHeight = Math.round(filteredHeights.reduce((a, b) => a + b, 0) / filteredHeights.length);
|
|
2486
|
+
}
|
|
2487
|
+
});
|
|
1432
2488
|
}
|
|
1433
2489
|
|
|
1434
2490
|
__getIndexScrollOffset(index) {
|
|
@@ -1453,42 +2509,35 @@ class IronListAdapter {
|
|
|
1453
2509
|
this._debouncers._increasePoolIfNeeded.cancel();
|
|
1454
2510
|
}
|
|
1455
2511
|
|
|
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
|
-
|
|
1467
2512
|
// Change the size
|
|
1468
2513
|
this.__size = size;
|
|
1469
2514
|
|
|
1470
|
-
this.
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
2515
|
+
if (!this._physicalItems) {
|
|
2516
|
+
// Not initialized yet
|
|
2517
|
+
this._itemsChanged({
|
|
2518
|
+
path: 'items',
|
|
2519
|
+
});
|
|
2520
|
+
this.__preventElementUpdates = true;
|
|
2521
|
+
flush();
|
|
2522
|
+
this.__preventElementUpdates = false;
|
|
2523
|
+
} else {
|
|
2524
|
+
// Already initialized, just update _virtualCount
|
|
2525
|
+
this._virtualCount = this.items.length;
|
|
2526
|
+
}
|
|
1479
2527
|
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
2528
|
+
// When reducing size while invisible, iron-list does not update items, so
|
|
2529
|
+
// their hidden state is not updated and their __lastUpdatedIndex is not
|
|
2530
|
+
// reset. In that case force an update here.
|
|
2531
|
+
if (!this._isVisible) {
|
|
2532
|
+
this._assignModels();
|
|
1484
2533
|
}
|
|
1485
2534
|
|
|
1486
2535
|
if (!this.elementsContainer.children.length) {
|
|
1487
2536
|
requestAnimationFrame(() => this._resizeHandler());
|
|
1488
2537
|
}
|
|
1489
2538
|
|
|
1490
|
-
|
|
1491
|
-
//
|
|
2539
|
+
// Schedule and flush a resize handler. This will cause a
|
|
2540
|
+
// re-render for the elements.
|
|
1492
2541
|
this._resizeHandler();
|
|
1493
2542
|
flush();
|
|
1494
2543
|
}
|
|
@@ -1553,16 +2602,20 @@ class IronListAdapter {
|
|
|
1553
2602
|
|
|
1554
2603
|
/** @private */
|
|
1555
2604
|
_assignModels(itemSet) {
|
|
2605
|
+
const updatedElements = [];
|
|
1556
2606
|
this._iterateItems((pidx, vidx) => {
|
|
1557
2607
|
const el = this._physicalItems[pidx];
|
|
1558
2608
|
el.hidden = vidx >= this.size;
|
|
1559
2609
|
if (!el.hidden) {
|
|
1560
2610
|
el.__virtualIndex = vidx + (this._vidxOffset || 0);
|
|
1561
2611
|
this.__updateElement(el, el.__virtualIndex);
|
|
2612
|
+
updatedElements.push(el);
|
|
1562
2613
|
} else {
|
|
1563
2614
|
delete el.__lastUpdatedIndex;
|
|
1564
2615
|
}
|
|
1565
2616
|
}, itemSet);
|
|
2617
|
+
|
|
2618
|
+
this.__afterElementsUpdated(updatedElements);
|
|
1566
2619
|
}
|
|
1567
2620
|
|
|
1568
2621
|
/** @private */
|
|
@@ -1691,7 +2744,9 @@ class IronListAdapter {
|
|
|
1691
2744
|
deltaY *= this._scrollPageHeight;
|
|
1692
2745
|
}
|
|
1693
2746
|
|
|
1694
|
-
|
|
2747
|
+
if (!this._deltaYAcc) {
|
|
2748
|
+
this._deltaYAcc = 0;
|
|
2749
|
+
}
|
|
1695
2750
|
|
|
1696
2751
|
if (this._wheelAnimationFrame) {
|
|
1697
2752
|
// Accumulate wheel delta while a frame is being processed
|
|
@@ -1764,6 +2819,29 @@ class IronListAdapter {
|
|
|
1764
2819
|
);
|
|
1765
2820
|
}
|
|
1766
2821
|
|
|
2822
|
+
/**
|
|
2823
|
+
* Increases the pool size.
|
|
2824
|
+
* @override
|
|
2825
|
+
*/
|
|
2826
|
+
_increasePoolIfNeeded(count) {
|
|
2827
|
+
if (this._physicalCount > 2 && count) {
|
|
2828
|
+
// The iron-list logic has already created some physical items and
|
|
2829
|
+
// has decided to create more. Since each item creation round is
|
|
2830
|
+
// expensive, let's try to create the remaining items in one go.
|
|
2831
|
+
|
|
2832
|
+
// Calculate the total item count that would be needed to fill the viewport
|
|
2833
|
+
// plus the buffer assuming rest of the items to be of the average size
|
|
2834
|
+
// of the items already created.
|
|
2835
|
+
const totalItemCount = Math.ceil(this._optPhysicalSize / this._physicalAverage);
|
|
2836
|
+
const missingItemCount = totalItemCount - this._physicalCount;
|
|
2837
|
+
// Create the remaining items in one go. Use a maximum of 100 items
|
|
2838
|
+
// as a safety measure.
|
|
2839
|
+
super._increasePoolIfNeeded(Math.max(count, Math.min(100, missingItemCount)));
|
|
2840
|
+
} else {
|
|
2841
|
+
super._increasePoolIfNeeded(count);
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
|
|
1767
2845
|
/**
|
|
1768
2846
|
* @returns {Number|undefined} - The browser's default font-size in pixels
|
|
1769
2847
|
* @private
|
|
@@ -1893,6 +2971,24 @@ class Virtualizer {
|
|
|
1893
2971
|
this.__adapter = new IronListAdapter(config);
|
|
1894
2972
|
}
|
|
1895
2973
|
|
|
2974
|
+
/**
|
|
2975
|
+
* Gets the index of the first visible item in the viewport.
|
|
2976
|
+
*
|
|
2977
|
+
* @return {number}
|
|
2978
|
+
*/
|
|
2979
|
+
get firstVisibleIndex() {
|
|
2980
|
+
return this.__adapter.adjustedFirstVisibleIndex;
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
/**
|
|
2984
|
+
* Gets the index of the last visible item in the viewport.
|
|
2985
|
+
*
|
|
2986
|
+
* @return {number}
|
|
2987
|
+
*/
|
|
2988
|
+
get lastVisibleIndex() {
|
|
2989
|
+
return this.__adapter.adjustedLastVisibleIndex;
|
|
2990
|
+
}
|
|
2991
|
+
|
|
1896
2992
|
/**
|
|
1897
2993
|
* The size of the virtualizer
|
|
1898
2994
|
* @return {number | undefined} The size of the virtualizer
|
|
@@ -1940,56 +3036,399 @@ class Virtualizer {
|
|
|
1940
3036
|
flush() {
|
|
1941
3037
|
this.__adapter.flush();
|
|
1942
3038
|
}
|
|
3039
|
+
}
|
|
1943
3040
|
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
get firstVisibleIndex() {
|
|
1950
|
-
return this.__adapter.adjustedFirstVisibleIndex;
|
|
1951
|
-
}
|
|
3041
|
+
/**
|
|
3042
|
+
* @license
|
|
3043
|
+
* Copyright (c) 2015 - 2023 Vaadin Ltd.
|
|
3044
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
3045
|
+
*/
|
|
1952
3046
|
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
3047
|
+
/*
|
|
3048
|
+
* Placeholder object class representing items being loaded.
|
|
3049
|
+
*
|
|
3050
|
+
* @private
|
|
3051
|
+
*/
|
|
3052
|
+
const ComboBoxPlaceholder = class ComboBoxPlaceholder {
|
|
3053
|
+
toString() {
|
|
3054
|
+
return '';
|
|
1960
3055
|
}
|
|
1961
|
-
}
|
|
3056
|
+
};
|
|
1962
3057
|
|
|
1963
3058
|
/**
|
|
1964
3059
|
* @license
|
|
1965
|
-
* Copyright (c) 2015 -
|
|
3060
|
+
* Copyright (c) 2015 - 2023 Vaadin Ltd.
|
|
1966
3061
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
1967
3062
|
*/
|
|
1968
3063
|
|
|
1969
|
-
|
|
1970
|
-
*
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
3064
|
+
/**
|
|
3065
|
+
* @polymerMixin
|
|
3066
|
+
*/
|
|
3067
|
+
const ComboBoxScrollerMixin = (superClass) =>
|
|
3068
|
+
class ComboBoxScrollerMixin extends superClass {
|
|
3069
|
+
static get properties() {
|
|
3070
|
+
return {
|
|
3071
|
+
/**
|
|
3072
|
+
* A full set of items to filter the visible options from.
|
|
3073
|
+
* Set to an empty array when combo-box is not opened.
|
|
3074
|
+
*/
|
|
3075
|
+
items: {
|
|
3076
|
+
type: Array,
|
|
3077
|
+
observer: '__itemsChanged',
|
|
3078
|
+
},
|
|
3079
|
+
|
|
3080
|
+
/**
|
|
3081
|
+
* Index of an item that has focus outline and is scrolled into view.
|
|
3082
|
+
* The actual focus still remains in the input field.
|
|
3083
|
+
*/
|
|
3084
|
+
focusedIndex: {
|
|
3085
|
+
type: Number,
|
|
3086
|
+
observer: '__focusedIndexChanged',
|
|
3087
|
+
},
|
|
3088
|
+
|
|
3089
|
+
/**
|
|
3090
|
+
* Set to true while combo-box fetches new page from the data provider.
|
|
3091
|
+
*/
|
|
3092
|
+
loading: {
|
|
3093
|
+
type: Boolean,
|
|
3094
|
+
observer: '__loadingChanged',
|
|
3095
|
+
},
|
|
3096
|
+
|
|
3097
|
+
/**
|
|
3098
|
+
* Whether the combo-box is currently opened or not. If set to false,
|
|
3099
|
+
* calling `scrollIntoView` does not have any effect.
|
|
3100
|
+
*/
|
|
3101
|
+
opened: {
|
|
3102
|
+
type: Boolean,
|
|
3103
|
+
observer: '__openedChanged',
|
|
3104
|
+
},
|
|
3105
|
+
|
|
3106
|
+
/**
|
|
3107
|
+
* The selected item from the `items` array.
|
|
3108
|
+
*/
|
|
3109
|
+
selectedItem: {
|
|
3110
|
+
type: Object,
|
|
3111
|
+
observer: '__selectedItemChanged',
|
|
3112
|
+
},
|
|
3113
|
+
|
|
3114
|
+
/**
|
|
3115
|
+
* Path for the id of the item, used to detect whether the item is selected.
|
|
3116
|
+
*/
|
|
3117
|
+
itemIdPath: {
|
|
3118
|
+
type: String,
|
|
3119
|
+
},
|
|
3120
|
+
|
|
3121
|
+
/**
|
|
3122
|
+
* Reference to the owner (combo-box owner), used by the item elements.
|
|
3123
|
+
*/
|
|
3124
|
+
owner: {
|
|
3125
|
+
type: Object,
|
|
3126
|
+
},
|
|
3127
|
+
|
|
3128
|
+
/**
|
|
3129
|
+
* Function used to set a label for every combo-box item.
|
|
3130
|
+
*/
|
|
3131
|
+
getItemLabel: {
|
|
3132
|
+
type: Object,
|
|
3133
|
+
},
|
|
3134
|
+
|
|
3135
|
+
/**
|
|
3136
|
+
* Function used to render the content of every combo-box item.
|
|
3137
|
+
*/
|
|
3138
|
+
renderer: {
|
|
3139
|
+
type: Object,
|
|
3140
|
+
observer: '__rendererChanged',
|
|
3141
|
+
},
|
|
3142
|
+
|
|
3143
|
+
/**
|
|
3144
|
+
* Used to propagate the `theme` attribute from the host element.
|
|
3145
|
+
*/
|
|
3146
|
+
theme: {
|
|
3147
|
+
type: String,
|
|
3148
|
+
},
|
|
3149
|
+
};
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
constructor() {
|
|
3153
|
+
super();
|
|
3154
|
+
this.__boundOnItemClick = this.__onItemClick.bind(this);
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
/** @private */
|
|
3158
|
+
get _viewportTotalPaddingBottom() {
|
|
3159
|
+
if (this._cachedViewportTotalPaddingBottom === undefined) {
|
|
3160
|
+
const itemsStyle = window.getComputedStyle(this.$.selector);
|
|
3161
|
+
this._cachedViewportTotalPaddingBottom = [itemsStyle.paddingBottom, itemsStyle.borderBottomWidth]
|
|
3162
|
+
.map((v) => {
|
|
3163
|
+
return parseInt(v, 10);
|
|
3164
|
+
})
|
|
3165
|
+
.reduce((sum, v) => {
|
|
3166
|
+
return sum + v;
|
|
3167
|
+
});
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
return this._cachedViewportTotalPaddingBottom;
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
/** @protected */
|
|
3174
|
+
ready() {
|
|
3175
|
+
super.ready();
|
|
3176
|
+
|
|
3177
|
+
this.setAttribute('role', 'listbox');
|
|
3178
|
+
|
|
3179
|
+
// Ensure every instance has unique ID
|
|
3180
|
+
this.id = `${this.localName}-${generateUniqueId()}`;
|
|
3181
|
+
|
|
3182
|
+
// Allow extensions to customize tag name for the items
|
|
3183
|
+
this.__hostTagName = this.constructor.is.replace('-scroller', '');
|
|
3184
|
+
|
|
3185
|
+
this.addEventListener('click', (e) => e.stopPropagation());
|
|
3186
|
+
|
|
3187
|
+
this.__patchWheelOverScrolling();
|
|
3188
|
+
|
|
3189
|
+
this.__virtualizer = new Virtualizer({
|
|
3190
|
+
createElements: this.__createElements.bind(this),
|
|
3191
|
+
updateElement: this._updateElement.bind(this),
|
|
3192
|
+
elementsContainer: this,
|
|
3193
|
+
scrollTarget: this,
|
|
3194
|
+
scrollContainer: this.$.selector,
|
|
3195
|
+
});
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
/**
|
|
3199
|
+
* Requests an update for the virtualizer to re-render items.
|
|
3200
|
+
*/
|
|
3201
|
+
requestContentUpdate() {
|
|
3202
|
+
if (this.__virtualizer) {
|
|
3203
|
+
this.__virtualizer.update();
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
/**
|
|
3208
|
+
* Scrolls an item at given index into view and adjusts `scrollTop`
|
|
3209
|
+
* so that the element gets fully visible on Arrow Down key press.
|
|
3210
|
+
* @param {number} index
|
|
3211
|
+
*/
|
|
3212
|
+
scrollIntoView(index) {
|
|
3213
|
+
if (!(this.opened && index >= 0)) {
|
|
3214
|
+
return;
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
const visibleItemsCount = this._visibleItemsCount();
|
|
3218
|
+
|
|
3219
|
+
let targetIndex = index;
|
|
3220
|
+
|
|
3221
|
+
if (index > this.__virtualizer.lastVisibleIndex - 1) {
|
|
3222
|
+
// Index is below the bottom, scrolling down. Make the item appear at the bottom.
|
|
3223
|
+
// First scroll to target (will be at the top of the scroller) to make sure it's rendered.
|
|
3224
|
+
this.__virtualizer.scrollToIndex(index);
|
|
3225
|
+
// Then calculate the index for the following scroll (to get the target to bottom of the scroller).
|
|
3226
|
+
targetIndex = index - visibleItemsCount + 1;
|
|
3227
|
+
} else if (index > this.__virtualizer.firstVisibleIndex) {
|
|
3228
|
+
// The item is already visible, scrolling is unnecessary per se. But we need to trigger iron-list to set
|
|
3229
|
+
// the correct scrollTop on the scrollTarget. Scrolling to firstVisibleIndex.
|
|
3230
|
+
targetIndex = this.__virtualizer.firstVisibleIndex;
|
|
3231
|
+
}
|
|
3232
|
+
this.__virtualizer.scrollToIndex(Math.max(0, targetIndex));
|
|
3233
|
+
|
|
3234
|
+
// Sometimes the item is partly below the bottom edge, detect and adjust.
|
|
3235
|
+
const lastPhysicalItem = [...this.children].find(
|
|
3236
|
+
(el) => !el.hidden && el.index === this.__virtualizer.lastVisibleIndex,
|
|
3237
|
+
);
|
|
3238
|
+
if (!lastPhysicalItem || index !== lastPhysicalItem.index) {
|
|
3239
|
+
return;
|
|
3240
|
+
}
|
|
3241
|
+
const lastPhysicalItemRect = lastPhysicalItem.getBoundingClientRect();
|
|
3242
|
+
const scrollerRect = this.getBoundingClientRect();
|
|
3243
|
+
const scrollTopAdjust = lastPhysicalItemRect.bottom - scrollerRect.bottom + this._viewportTotalPaddingBottom;
|
|
3244
|
+
if (scrollTopAdjust > 0) {
|
|
3245
|
+
this.scrollTop += scrollTopAdjust;
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
/**
|
|
3250
|
+
* @param {string | object} item
|
|
3251
|
+
* @param {string | object} selectedItem
|
|
3252
|
+
* @param {string} itemIdPath
|
|
3253
|
+
* @protected
|
|
3254
|
+
*/
|
|
3255
|
+
_isItemSelected(item, selectedItem, itemIdPath) {
|
|
3256
|
+
if (item instanceof ComboBoxPlaceholder) {
|
|
3257
|
+
return false;
|
|
3258
|
+
} else if (itemIdPath && item !== undefined && selectedItem !== undefined) {
|
|
3259
|
+
return get(itemIdPath, item) === get(itemIdPath, selectedItem);
|
|
3260
|
+
}
|
|
3261
|
+
return item === selectedItem;
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
/** @private */
|
|
3265
|
+
__itemsChanged(items) {
|
|
3266
|
+
if (this.__virtualizer && items) {
|
|
3267
|
+
this.__virtualizer.size = items.length;
|
|
3268
|
+
this.__virtualizer.flush();
|
|
3269
|
+
this.requestContentUpdate();
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3273
|
+
/** @private */
|
|
3274
|
+
__loadingChanged() {
|
|
3275
|
+
this.requestContentUpdate();
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
/** @private */
|
|
3279
|
+
__openedChanged(opened) {
|
|
3280
|
+
if (opened) {
|
|
3281
|
+
this.requestContentUpdate();
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
/** @private */
|
|
3286
|
+
__selectedItemChanged() {
|
|
3287
|
+
this.requestContentUpdate();
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
/** @private */
|
|
3291
|
+
__focusedIndexChanged(index, oldIndex) {
|
|
3292
|
+
if (index !== oldIndex) {
|
|
3293
|
+
this.requestContentUpdate();
|
|
3294
|
+
}
|
|
3295
|
+
|
|
3296
|
+
// Do not jump back to the previously focused item while loading
|
|
3297
|
+
// when requesting next page from the data provider on scroll.
|
|
3298
|
+
if (index >= 0 && !this.loading) {
|
|
3299
|
+
this.scrollIntoView(index);
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
/** @private */
|
|
3304
|
+
__rendererChanged(renderer, oldRenderer) {
|
|
3305
|
+
if (renderer || oldRenderer) {
|
|
3306
|
+
this.requestContentUpdate();
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3310
|
+
/** @private */
|
|
3311
|
+
__createElements(count) {
|
|
3312
|
+
return [...Array(count)].map(() => {
|
|
3313
|
+
const item = document.createElement(`${this.__hostTagName}-item`);
|
|
3314
|
+
item.addEventListener('click', this.__boundOnItemClick);
|
|
3315
|
+
// Negative tabindex prevents the item content from being focused.
|
|
3316
|
+
item.tabIndex = '-1';
|
|
3317
|
+
item.style.width = '100%';
|
|
3318
|
+
return item;
|
|
3319
|
+
});
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
/**
|
|
3323
|
+
* @param {HTMLElement} el
|
|
3324
|
+
* @param {number} index
|
|
3325
|
+
* @protected
|
|
3326
|
+
*/
|
|
3327
|
+
_updateElement(el, index) {
|
|
3328
|
+
const item = this.items[index];
|
|
3329
|
+
const focusedIndex = this.focusedIndex;
|
|
3330
|
+
const selected = this._isItemSelected(item, this.selectedItem, this.itemIdPath);
|
|
3331
|
+
|
|
3332
|
+
el.setProperties({
|
|
3333
|
+
item,
|
|
3334
|
+
index,
|
|
3335
|
+
label: this.getItemLabel(item),
|
|
3336
|
+
selected,
|
|
3337
|
+
renderer: this.renderer,
|
|
3338
|
+
focused: !this.loading && focusedIndex === index,
|
|
3339
|
+
});
|
|
3340
|
+
|
|
3341
|
+
el.id = `${this.__hostTagName}-item-${index}`;
|
|
3342
|
+
el.setAttribute('role', index !== undefined ? 'option' : false);
|
|
3343
|
+
el.setAttribute('aria-selected', selected.toString());
|
|
3344
|
+
el.setAttribute('aria-posinset', index + 1);
|
|
3345
|
+
el.setAttribute('aria-setsize', this.items.length);
|
|
3346
|
+
|
|
3347
|
+
if (this.theme) {
|
|
3348
|
+
el.setAttribute('theme', this.theme);
|
|
3349
|
+
} else {
|
|
3350
|
+
el.removeAttribute('theme');
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
if (item instanceof ComboBoxPlaceholder) {
|
|
3354
|
+
this.__requestItemByIndex(index);
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
/** @private */
|
|
3359
|
+
__onItemClick(e) {
|
|
3360
|
+
this.dispatchEvent(new CustomEvent('selection-changed', { detail: { item: e.currentTarget.item } }));
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3363
|
+
/**
|
|
3364
|
+
* We want to prevent the kinetic scrolling energy from being transferred from the overlay contents over to the parent.
|
|
3365
|
+
* Further improvement ideas: after the contents have been scrolled to the top or bottom and scrolling has stopped, it could allow
|
|
3366
|
+
* scrolling the parent similarly to touch scrolling.
|
|
3367
|
+
* @private
|
|
3368
|
+
*/
|
|
3369
|
+
__patchWheelOverScrolling() {
|
|
3370
|
+
this.$.selector.addEventListener('wheel', (e) => {
|
|
3371
|
+
const scrolledToTop = this.scrollTop === 0;
|
|
3372
|
+
const scrolledToBottom = this.scrollHeight - this.scrollTop - this.clientHeight <= 1;
|
|
3373
|
+
if (scrolledToTop && e.deltaY < 0) {
|
|
3374
|
+
e.preventDefault();
|
|
3375
|
+
} else if (scrolledToBottom && e.deltaY > 0) {
|
|
3376
|
+
e.preventDefault();
|
|
3377
|
+
}
|
|
3378
|
+
});
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
/**
|
|
3382
|
+
* Dispatches an `index-requested` event for the given index to notify
|
|
3383
|
+
* the data provider that it should start loading the page containing the requested index.
|
|
3384
|
+
*
|
|
3385
|
+
* The event is dispatched asynchronously to prevent an immediate page request and therefore
|
|
3386
|
+
* a possible infinite recursion in case the data provider implements page request cancelation logic
|
|
3387
|
+
* by invoking data provider page callbacks with an empty array.
|
|
3388
|
+
* The infinite recursion may occur otherwise since invoking a data provider page callback with an empty array
|
|
3389
|
+
* triggers a synchronous scroller update and, if the callback corresponds to the currently visible page,
|
|
3390
|
+
* the scroller will synchronously request the page again which may lead to looping in the end.
|
|
3391
|
+
* That was the case for the Flow counterpart:
|
|
3392
|
+
* https://github.com/vaadin/flow-components/issues/3553#issuecomment-1239344828
|
|
3393
|
+
* @private
|
|
3394
|
+
*/
|
|
3395
|
+
__requestItemByIndex(index) {
|
|
3396
|
+
requestAnimationFrame(() => {
|
|
3397
|
+
this.dispatchEvent(
|
|
3398
|
+
new CustomEvent('index-requested', {
|
|
3399
|
+
detail: {
|
|
3400
|
+
index,
|
|
3401
|
+
currentScrollerPos: this._oldScrollerPosition,
|
|
3402
|
+
},
|
|
3403
|
+
}),
|
|
3404
|
+
);
|
|
3405
|
+
});
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
/** @private */
|
|
3409
|
+
_visibleItemsCount() {
|
|
3410
|
+
// Ensure items are positioned
|
|
3411
|
+
this.__virtualizer.scrollToIndex(this.__virtualizer.firstVisibleIndex);
|
|
3412
|
+
const hasItems = this.__virtualizer.size > 0;
|
|
3413
|
+
return hasItems ? this.__virtualizer.lastVisibleIndex - this.__virtualizer.firstVisibleIndex + 1 : 0;
|
|
3414
|
+
}
|
|
3415
|
+
};
|
|
1979
3416
|
|
|
1980
3417
|
/**
|
|
1981
3418
|
* @license
|
|
1982
|
-
* Copyright (c) 2015 -
|
|
3419
|
+
* Copyright (c) 2015 - 2023 Vaadin Ltd.
|
|
1983
3420
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
1984
3421
|
*/
|
|
1985
3422
|
|
|
1986
3423
|
/**
|
|
1987
|
-
*
|
|
3424
|
+
* An element used internally by `<vaadin-combo-box>`. Not intended to be used separately.
|
|
1988
3425
|
*
|
|
3426
|
+
* @customElement
|
|
1989
3427
|
* @extends HTMLElement
|
|
3428
|
+
* @mixes ComboBoxScrollerMixin
|
|
1990
3429
|
* @private
|
|
1991
3430
|
*/
|
|
1992
|
-
class ComboBoxScroller extends PolymerElement {
|
|
3431
|
+
class ComboBoxScroller extends ComboBoxScrollerMixin(PolymerElement) {
|
|
1993
3432
|
static get is() {
|
|
1994
3433
|
return 'vaadin-combo-box-scroller';
|
|
1995
3434
|
}
|
|
@@ -2015,7 +3454,7 @@ class ComboBoxScroller extends PolymerElement {
|
|
|
2015
3454
|
#selector {
|
|
2016
3455
|
border-width: var(--_vaadin-combo-box-items-container-border-width);
|
|
2017
3456
|
border-style: var(--_vaadin-combo-box-items-container-border-style);
|
|
2018
|
-
border-color: var(--_vaadin-combo-box-items-container-border-color);
|
|
3457
|
+
border-color: var(--_vaadin-combo-box-items-container-border-color, transparent);
|
|
2019
3458
|
position: relative;
|
|
2020
3459
|
}
|
|
2021
3460
|
</style>
|
|
@@ -2024,349 +3463,642 @@ class ComboBoxScroller extends PolymerElement {
|
|
|
2024
3463
|
</div>
|
|
2025
3464
|
`;
|
|
2026
3465
|
}
|
|
3466
|
+
}
|
|
2027
3467
|
|
|
2028
|
-
|
|
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
|
-
},
|
|
3468
|
+
defineCustomElement(ComboBoxScroller);
|
|
2038
3469
|
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
type: Number,
|
|
2045
|
-
observer: '__focusedIndexChanged',
|
|
2046
|
-
},
|
|
3470
|
+
/**
|
|
3471
|
+
* @license
|
|
3472
|
+
* Copyright (c) 2021 - 2023 Vaadin Ltd.
|
|
3473
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
3474
|
+
*/
|
|
2047
3475
|
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
loading: {
|
|
2052
|
-
type: Boolean,
|
|
2053
|
-
observer: '__loadingChanged',
|
|
2054
|
-
},
|
|
3476
|
+
if (!window.Vaadin) {
|
|
3477
|
+
window.Vaadin = {};
|
|
3478
|
+
}
|
|
2055
3479
|
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
observer: '__openedChanged',
|
|
2063
|
-
},
|
|
3480
|
+
/**
|
|
3481
|
+
* Array of Vaadin custom element classes that have been finalized.
|
|
3482
|
+
*/
|
|
3483
|
+
if (!window.Vaadin.registrations) {
|
|
3484
|
+
window.Vaadin.registrations = [];
|
|
3485
|
+
}
|
|
2064
3486
|
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
selectedItem: {
|
|
2069
|
-
type: Object,
|
|
2070
|
-
observer: '__selectedItemChanged',
|
|
2071
|
-
},
|
|
3487
|
+
if (!window.Vaadin.developmentModeCallback) {
|
|
3488
|
+
window.Vaadin.developmentModeCallback = {};
|
|
3489
|
+
}
|
|
2072
3490
|
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
itemIdPath: {
|
|
2077
|
-
type: String,
|
|
2078
|
-
},
|
|
3491
|
+
window.Vaadin.developmentModeCallback['vaadin-usage-statistics'] = function () {
|
|
3492
|
+
usageStatistics();
|
|
3493
|
+
};
|
|
2079
3494
|
|
|
2080
|
-
|
|
2081
|
-
* Reference to the combo-box, used by the item elements.
|
|
2082
|
-
*/
|
|
2083
|
-
comboBox: {
|
|
2084
|
-
type: Object,
|
|
2085
|
-
},
|
|
3495
|
+
let statsJob;
|
|
2086
3496
|
|
|
2087
|
-
|
|
2088
|
-
* Function used to set a label for every combo-box item.
|
|
2089
|
-
*/
|
|
2090
|
-
getItemLabel: {
|
|
2091
|
-
type: Object,
|
|
2092
|
-
},
|
|
3497
|
+
const registered = new Set();
|
|
2093
3498
|
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
3499
|
+
/**
|
|
3500
|
+
* @polymerMixin
|
|
3501
|
+
* @mixes DirMixin
|
|
3502
|
+
*/
|
|
3503
|
+
const ElementMixin = (superClass) =>
|
|
3504
|
+
class VaadinElementMixin extends DirMixin(superClass) {
|
|
3505
|
+
static get version() {
|
|
3506
|
+
return '24.2.3';
|
|
3507
|
+
}
|
|
2101
3508
|
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
3509
|
+
/** @protected */
|
|
3510
|
+
static finalize() {
|
|
3511
|
+
super.finalize();
|
|
3512
|
+
|
|
3513
|
+
const { is } = this;
|
|
3514
|
+
|
|
3515
|
+
// Registers a class prototype for telemetry purposes.
|
|
3516
|
+
if (is && !registered.has(is)) {
|
|
3517
|
+
window.Vaadin.registrations.push(this);
|
|
3518
|
+
registered.add(is);
|
|
3519
|
+
|
|
3520
|
+
if (window.Vaadin.developmentModeCallback) {
|
|
3521
|
+
statsJob = Debouncer.debounce(statsJob, idlePeriod, () => {
|
|
3522
|
+
window.Vaadin.developmentModeCallback['vaadin-usage-statistics']();
|
|
3523
|
+
});
|
|
3524
|
+
enqueueDebouncer(statsJob);
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
constructor() {
|
|
3530
|
+
super();
|
|
3531
|
+
|
|
3532
|
+
if (document.doctype === null) {
|
|
3533
|
+
console.warn(
|
|
3534
|
+
'Vaadin components require the "standards mode" declaration. Please add <!DOCTYPE html> to the HTML document.',
|
|
3535
|
+
);
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
};
|
|
3539
|
+
|
|
3540
|
+
/**
|
|
3541
|
+
* @license
|
|
3542
|
+
* Copyright (c) 2021 - 2023 Vaadin Ltd.
|
|
3543
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
3544
|
+
*/
|
|
3545
|
+
|
|
3546
|
+
/**
|
|
3547
|
+
* Returns true if the given node is an empty text node, false otherwise.
|
|
3548
|
+
*
|
|
3549
|
+
* @param {Node} node
|
|
3550
|
+
* @return {boolean}
|
|
3551
|
+
*/
|
|
3552
|
+
function isEmptyTextNode(node) {
|
|
3553
|
+
return node.nodeType === Node.TEXT_NODE && node.textContent.trim() === '';
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
/**
|
|
3557
|
+
* @license
|
|
3558
|
+
* Copyright (c) 2023 Vaadin Ltd.
|
|
3559
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
3560
|
+
*/
|
|
3561
|
+
|
|
3562
|
+
/**
|
|
3563
|
+
* A helper for observing slot changes.
|
|
3564
|
+
*/
|
|
3565
|
+
class SlotObserver {
|
|
3566
|
+
constructor(slot, callback) {
|
|
3567
|
+
/** @type HTMLSlotElement */
|
|
3568
|
+
this.slot = slot;
|
|
3569
|
+
|
|
3570
|
+
/** @type Function */
|
|
3571
|
+
this.callback = callback;
|
|
3572
|
+
|
|
3573
|
+
/** @type {Node[]} */
|
|
3574
|
+
this._storedNodes = [];
|
|
3575
|
+
|
|
3576
|
+
this._connected = false;
|
|
3577
|
+
this._scheduled = false;
|
|
3578
|
+
|
|
3579
|
+
this._boundSchedule = () => {
|
|
3580
|
+
this._schedule();
|
|
2108
3581
|
};
|
|
3582
|
+
|
|
3583
|
+
this.connect();
|
|
3584
|
+
this._schedule();
|
|
2109
3585
|
}
|
|
2110
3586
|
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
3587
|
+
/**
|
|
3588
|
+
* Activates an observer. This method is automatically called when
|
|
3589
|
+
* a `SlotObserver` is created. It should only be called to re-activate
|
|
3590
|
+
* an observer that has been deactivated via the `disconnect` method.
|
|
3591
|
+
*/
|
|
3592
|
+
connect() {
|
|
3593
|
+
this.slot.addEventListener('slotchange', this._boundSchedule);
|
|
3594
|
+
this._connected = true;
|
|
2114
3595
|
}
|
|
2115
3596
|
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
3597
|
+
/**
|
|
3598
|
+
* Deactivates the observer. After calling this method the observer callback
|
|
3599
|
+
* will not be called when changes to slotted nodes occur. The `connect` method
|
|
3600
|
+
* may be subsequently called to reactivate the observer.
|
|
3601
|
+
*/
|
|
3602
|
+
disconnect() {
|
|
3603
|
+
this.slot.removeEventListener('slotchange', this._boundSchedule);
|
|
3604
|
+
this._connected = false;
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3607
|
+
/** @private */
|
|
3608
|
+
_schedule() {
|
|
3609
|
+
if (!this._scheduled) {
|
|
3610
|
+
this._scheduled = true;
|
|
3611
|
+
|
|
3612
|
+
queueMicrotask(() => {
|
|
3613
|
+
this.flush();
|
|
3614
|
+
});
|
|
2119
3615
|
}
|
|
2120
3616
|
}
|
|
2121
3617
|
|
|
2122
|
-
/**
|
|
2123
|
-
|
|
2124
|
-
|
|
3618
|
+
/**
|
|
3619
|
+
* Run the observer callback synchronously.
|
|
3620
|
+
*/
|
|
3621
|
+
flush() {
|
|
3622
|
+
if (!this._connected) {
|
|
3623
|
+
return;
|
|
3624
|
+
}
|
|
2125
3625
|
|
|
2126
|
-
|
|
2127
|
-
this.id = `${this.localName}-${generateUniqueId()}`;
|
|
3626
|
+
this._scheduled = false;
|
|
2128
3627
|
|
|
2129
|
-
|
|
2130
|
-
|
|
3628
|
+
this._processNodes();
|
|
3629
|
+
}
|
|
2131
3630
|
|
|
2132
|
-
|
|
3631
|
+
/** @private */
|
|
3632
|
+
_processNodes() {
|
|
3633
|
+
const currentNodes = this.slot.assignedNodes({ flatten: true });
|
|
2133
3634
|
|
|
2134
|
-
|
|
3635
|
+
let addedNodes = [];
|
|
3636
|
+
const removedNodes = [];
|
|
3637
|
+
const movedNodes = [];
|
|
2135
3638
|
|
|
2136
|
-
|
|
3639
|
+
if (currentNodes.length) {
|
|
3640
|
+
addedNodes = currentNodes.filter((node) => !this._storedNodes.includes(node));
|
|
3641
|
+
}
|
|
2137
3642
|
|
|
2138
|
-
this.
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
3643
|
+
if (this._storedNodes.length) {
|
|
3644
|
+
this._storedNodes.forEach((node, index) => {
|
|
3645
|
+
const idx = currentNodes.indexOf(node);
|
|
3646
|
+
if (idx === -1) {
|
|
3647
|
+
removedNodes.push(node);
|
|
3648
|
+
} else if (idx !== index) {
|
|
3649
|
+
movedNodes.push(node);
|
|
3650
|
+
}
|
|
3651
|
+
});
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3654
|
+
if (addedNodes.length || removedNodes.length || movedNodes.length) {
|
|
3655
|
+
this.callback({ addedNodes, movedNodes, removedNodes });
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
this._storedNodes = currentNodes;
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3661
|
+
|
|
3662
|
+
/**
|
|
3663
|
+
* @license
|
|
3664
|
+
* Copyright (c) 2021 - 2023 Vaadin Ltd.
|
|
3665
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
3666
|
+
*/
|
|
3667
|
+
|
|
3668
|
+
/**
|
|
3669
|
+
* A controller for providing content to slot element and observing changes.
|
|
3670
|
+
*/
|
|
3671
|
+
class SlotController extends EventTarget {
|
|
3672
|
+
/**
|
|
3673
|
+
* Ensure that every instance has unique ID.
|
|
3674
|
+
*
|
|
3675
|
+
* @param {HTMLElement} host
|
|
3676
|
+
* @param {string} slotName
|
|
3677
|
+
* @return {string}
|
|
3678
|
+
* @protected
|
|
3679
|
+
*/
|
|
3680
|
+
static generateId(host, slotName) {
|
|
3681
|
+
const prefix = slotName || 'default';
|
|
3682
|
+
return `${prefix}-${host.localName}-${generateUniqueId()}`;
|
|
2145
3683
|
}
|
|
2146
3684
|
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
3685
|
+
constructor(host, slotName, tagName, config = {}) {
|
|
3686
|
+
super();
|
|
3687
|
+
|
|
3688
|
+
const { initializer, multiple, observe, useUniqueId } = config;
|
|
3689
|
+
|
|
3690
|
+
this.host = host;
|
|
3691
|
+
this.slotName = slotName;
|
|
3692
|
+
this.tagName = tagName;
|
|
3693
|
+
this.observe = typeof observe === 'boolean' ? observe : true;
|
|
3694
|
+
this.multiple = typeof multiple === 'boolean' ? multiple : false;
|
|
3695
|
+
this.slotInitializer = initializer;
|
|
3696
|
+
|
|
3697
|
+
if (multiple) {
|
|
3698
|
+
this.nodes = [];
|
|
3699
|
+
}
|
|
3700
|
+
|
|
3701
|
+
// Only generate the default ID if requested by the controller.
|
|
3702
|
+
if (useUniqueId) {
|
|
3703
|
+
this.defaultId = this.constructor.generateId(host, slotName);
|
|
2150
3704
|
}
|
|
2151
3705
|
}
|
|
2152
3706
|
|
|
2153
|
-
|
|
2154
|
-
if (!
|
|
2155
|
-
|
|
3707
|
+
hostConnected() {
|
|
3708
|
+
if (!this.initialized) {
|
|
3709
|
+
if (this.multiple) {
|
|
3710
|
+
this.initMultiple();
|
|
3711
|
+
} else {
|
|
3712
|
+
this.initSingle();
|
|
3713
|
+
}
|
|
3714
|
+
|
|
3715
|
+
if (this.observe) {
|
|
3716
|
+
this.observeSlot();
|
|
3717
|
+
}
|
|
3718
|
+
|
|
3719
|
+
this.initialized = true;
|
|
2156
3720
|
}
|
|
3721
|
+
}
|
|
2157
3722
|
|
|
2158
|
-
|
|
3723
|
+
/** @protected */
|
|
3724
|
+
initSingle() {
|
|
3725
|
+
let node = this.getSlotChild();
|
|
2159
3726
|
|
|
2160
|
-
|
|
3727
|
+
if (!node) {
|
|
3728
|
+
node = this.attachDefaultNode();
|
|
3729
|
+
this.initNode(node);
|
|
3730
|
+
} else {
|
|
3731
|
+
this.node = node;
|
|
3732
|
+
this.initAddedNode(node);
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
2161
3735
|
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
3736
|
+
/** @protected */
|
|
3737
|
+
initMultiple() {
|
|
3738
|
+
const children = this.getSlotChildren();
|
|
3739
|
+
|
|
3740
|
+
if (children.length === 0) {
|
|
3741
|
+
const defaultNode = this.attachDefaultNode();
|
|
3742
|
+
if (defaultNode) {
|
|
3743
|
+
this.nodes = [defaultNode];
|
|
3744
|
+
this.initNode(defaultNode);
|
|
3745
|
+
}
|
|
3746
|
+
} else {
|
|
3747
|
+
this.nodes = children;
|
|
3748
|
+
children.forEach((node) => {
|
|
3749
|
+
this.initAddedNode(node);
|
|
3750
|
+
});
|
|
2172
3751
|
}
|
|
2173
|
-
|
|
3752
|
+
}
|
|
2174
3753
|
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
3754
|
+
/**
|
|
3755
|
+
* Create and attach default node using the provided tag name, if any.
|
|
3756
|
+
* @return {Node | undefined}
|
|
3757
|
+
* @protected
|
|
3758
|
+
*/
|
|
3759
|
+
attachDefaultNode() {
|
|
3760
|
+
const { host, slotName, tagName } = this;
|
|
3761
|
+
|
|
3762
|
+
// Check if the node was created previously and if so, reuse it.
|
|
3763
|
+
let node = this.defaultNode;
|
|
3764
|
+
|
|
3765
|
+
// Tag name is optional, sometimes we don't init default content.
|
|
3766
|
+
if (!node && tagName) {
|
|
3767
|
+
node = document.createElement(tagName);
|
|
3768
|
+
if (node instanceof Element) {
|
|
3769
|
+
if (slotName !== '') {
|
|
3770
|
+
node.setAttribute('slot', slotName);
|
|
3771
|
+
}
|
|
3772
|
+
this.node = node;
|
|
3773
|
+
this.defaultNode = node;
|
|
3774
|
+
}
|
|
2181
3775
|
}
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
if (scrollTopAdjust > 0) {
|
|
2186
|
-
this.scrollTop += scrollTopAdjust;
|
|
3776
|
+
|
|
3777
|
+
if (node) {
|
|
3778
|
+
host.appendChild(node);
|
|
2187
3779
|
}
|
|
3780
|
+
|
|
3781
|
+
return node;
|
|
2188
3782
|
}
|
|
2189
3783
|
|
|
2190
|
-
/**
|
|
2191
|
-
|
|
2192
|
-
|
|
3784
|
+
/**
|
|
3785
|
+
* Return the list of nodes matching the slot managed by the controller.
|
|
3786
|
+
* @return {Node}
|
|
3787
|
+
*/
|
|
3788
|
+
getSlotChildren() {
|
|
3789
|
+
const { slotName } = this;
|
|
3790
|
+
return Array.from(this.host.childNodes).filter((node) => {
|
|
3791
|
+
// Either an element (any slot) or a text node (only un-named slot).
|
|
3792
|
+
return (
|
|
3793
|
+
(node.nodeType === Node.ELEMENT_NODE && node.slot === slotName) ||
|
|
3794
|
+
(node.nodeType === Node.TEXT_NODE && node.textContent.trim() && slotName === '')
|
|
3795
|
+
);
|
|
3796
|
+
});
|
|
2193
3797
|
}
|
|
2194
3798
|
|
|
2195
|
-
/**
|
|
2196
|
-
|
|
2197
|
-
|
|
3799
|
+
/**
|
|
3800
|
+
* Return a reference to the node managed by the controller.
|
|
3801
|
+
* @return {Node}
|
|
3802
|
+
*/
|
|
3803
|
+
getSlotChild() {
|
|
3804
|
+
return this.getSlotChildren()[0];
|
|
2198
3805
|
}
|
|
2199
3806
|
|
|
2200
|
-
/**
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
3807
|
+
/**
|
|
3808
|
+
* Run `slotInitializer` for the node managed by the controller.
|
|
3809
|
+
*
|
|
3810
|
+
* @param {Node} node
|
|
3811
|
+
* @protected
|
|
3812
|
+
*/
|
|
3813
|
+
initNode(node) {
|
|
3814
|
+
const { slotInitializer } = this;
|
|
3815
|
+
// Don't try to bind `this` to initializer (normally it's arrow function).
|
|
3816
|
+
// Instead, pass the host as a first argument to access component's state.
|
|
3817
|
+
if (slotInitializer) {
|
|
3818
|
+
slotInitializer(node, this.host);
|
|
2206
3819
|
}
|
|
2207
|
-
return item === selectedItem;
|
|
2208
3820
|
}
|
|
2209
3821
|
|
|
2210
|
-
/**
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
3822
|
+
/**
|
|
3823
|
+
* Override to initialize the newly added custom node.
|
|
3824
|
+
*
|
|
3825
|
+
* @param {Node} _node
|
|
3826
|
+
* @protected
|
|
3827
|
+
*/
|
|
3828
|
+
initCustomNode(_node) {}
|
|
3829
|
+
|
|
3830
|
+
/**
|
|
3831
|
+
* Override to teardown slotted node when it's removed.
|
|
3832
|
+
*
|
|
3833
|
+
* @param {Node} _node
|
|
3834
|
+
* @protected
|
|
3835
|
+
*/
|
|
3836
|
+
teardownNode(_node) {}
|
|
3837
|
+
|
|
3838
|
+
/**
|
|
3839
|
+
* Run both `initCustomNode` and `initNode` for a custom slotted node.
|
|
3840
|
+
*
|
|
3841
|
+
* @param {Node} node
|
|
3842
|
+
* @protected
|
|
3843
|
+
*/
|
|
3844
|
+
initAddedNode(node) {
|
|
3845
|
+
if (node !== this.defaultNode) {
|
|
3846
|
+
this.initCustomNode(node);
|
|
3847
|
+
this.initNode(node);
|
|
2216
3848
|
}
|
|
2217
3849
|
}
|
|
2218
3850
|
|
|
2219
|
-
/**
|
|
2220
|
-
|
|
2221
|
-
|
|
3851
|
+
/**
|
|
3852
|
+
* Setup the observer to manage slot content changes.
|
|
3853
|
+
* @protected
|
|
3854
|
+
*/
|
|
3855
|
+
observeSlot() {
|
|
3856
|
+
const { slotName } = this;
|
|
3857
|
+
const selector = slotName === '' ? 'slot:not([name])' : `slot[name=${slotName}]`;
|
|
3858
|
+
const slot = this.host.shadowRoot.querySelector(selector);
|
|
3859
|
+
|
|
3860
|
+
this.__slotObserver = new SlotObserver(slot, ({ addedNodes, removedNodes }) => {
|
|
3861
|
+
const current = this.multiple ? this.nodes : [this.node];
|
|
3862
|
+
|
|
3863
|
+
// Calling `slot.assignedNodes()` includes whitespace text nodes in case of default slot:
|
|
3864
|
+
// unlike comment nodes, they are not filtered out. So we need to manually ignore them.
|
|
3865
|
+
const newNodes = addedNodes.filter((node) => !isEmptyTextNode(node) && !current.includes(node));
|
|
3866
|
+
|
|
3867
|
+
if (removedNodes.length) {
|
|
3868
|
+
this.nodes = current.filter((node) => !removedNodes.includes(node));
|
|
3869
|
+
|
|
3870
|
+
removedNodes.forEach((node) => {
|
|
3871
|
+
this.teardownNode(node);
|
|
3872
|
+
});
|
|
3873
|
+
}
|
|
3874
|
+
|
|
3875
|
+
if (newNodes && newNodes.length > 0) {
|
|
3876
|
+
if (this.multiple) {
|
|
3877
|
+
// Remove default node if exists
|
|
3878
|
+
if (this.defaultNode) {
|
|
3879
|
+
this.defaultNode.remove();
|
|
3880
|
+
}
|
|
3881
|
+
this.nodes = [...current, ...newNodes].filter((node) => node !== this.defaultNode);
|
|
3882
|
+
newNodes.forEach((node) => {
|
|
3883
|
+
this.initAddedNode(node);
|
|
3884
|
+
});
|
|
3885
|
+
} else {
|
|
3886
|
+
// Remove previous node if exists
|
|
3887
|
+
if (this.node) {
|
|
3888
|
+
this.node.remove();
|
|
3889
|
+
}
|
|
3890
|
+
this.node = newNodes[0];
|
|
3891
|
+
this.initAddedNode(this.node);
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
});
|
|
2222
3895
|
}
|
|
3896
|
+
}
|
|
2223
3897
|
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
3898
|
+
/**
|
|
3899
|
+
* @license
|
|
3900
|
+
* Copyright (c) 2022 - 2023 Vaadin Ltd.
|
|
3901
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
3902
|
+
*/
|
|
3903
|
+
|
|
3904
|
+
/**
|
|
3905
|
+
* A controller that manages the slotted tooltip element.
|
|
3906
|
+
*/
|
|
3907
|
+
class TooltipController extends SlotController {
|
|
3908
|
+
constructor(host) {
|
|
3909
|
+
// Do not provide slot factory to create tooltip lazily.
|
|
3910
|
+
super(host, 'tooltip');
|
|
3911
|
+
|
|
3912
|
+
this.setTarget(host);
|
|
2227
3913
|
}
|
|
2228
3914
|
|
|
2229
|
-
/**
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
3915
|
+
/**
|
|
3916
|
+
* Override to initialize the newly added custom tooltip.
|
|
3917
|
+
*
|
|
3918
|
+
* @param {Node} tooltipNode
|
|
3919
|
+
* @protected
|
|
3920
|
+
* @override
|
|
3921
|
+
*/
|
|
3922
|
+
initCustomNode(tooltipNode) {
|
|
3923
|
+
tooltipNode.target = this.target;
|
|
3924
|
+
|
|
3925
|
+
if (this.ariaTarget !== undefined) {
|
|
3926
|
+
tooltipNode.ariaTarget = this.ariaTarget;
|
|
3927
|
+
}
|
|
3928
|
+
|
|
3929
|
+
if (this.context !== undefined) {
|
|
3930
|
+
tooltipNode.context = this.context;
|
|
3931
|
+
}
|
|
3932
|
+
|
|
3933
|
+
if (this.manual !== undefined) {
|
|
3934
|
+
tooltipNode.manual = this.manual;
|
|
3935
|
+
}
|
|
3936
|
+
|
|
3937
|
+
if (this.opened !== undefined) {
|
|
3938
|
+
tooltipNode.opened = this.opened;
|
|
2233
3939
|
}
|
|
2234
3940
|
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
if (index >= 0 && !this.loading) {
|
|
2238
|
-
this.scrollIntoView(index);
|
|
3941
|
+
if (this.position !== undefined) {
|
|
3942
|
+
tooltipNode._position = this.position;
|
|
2239
3943
|
}
|
|
2240
|
-
}
|
|
2241
3944
|
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
if (renderer || oldRenderer) {
|
|
2245
|
-
this.requestContentUpdate();
|
|
3945
|
+
if (this.shouldShow !== undefined) {
|
|
3946
|
+
tooltipNode.shouldShow = this.shouldShow;
|
|
2246
3947
|
}
|
|
2247
|
-
}
|
|
2248
3948
|
|
|
2249
|
-
|
|
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
|
-
});
|
|
3949
|
+
this.__notifyChange();
|
|
2259
3950
|
}
|
|
2260
3951
|
|
|
2261
|
-
/**
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
selected,
|
|
2272
|
-
renderer: this.renderer,
|
|
2273
|
-
focused: this.__isItemFocused(focusedIndex, index),
|
|
2274
|
-
});
|
|
3952
|
+
/**
|
|
3953
|
+
* Override to notify the host when the tooltip is removed.
|
|
3954
|
+
*
|
|
3955
|
+
* @param {Node} tooltipNode
|
|
3956
|
+
* @protected
|
|
3957
|
+
* @override
|
|
3958
|
+
*/
|
|
3959
|
+
teardownNode() {
|
|
3960
|
+
this.__notifyChange();
|
|
3961
|
+
}
|
|
2275
3962
|
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
3963
|
+
/**
|
|
3964
|
+
* Set an HTML element for linking with the tooltip overlay
|
|
3965
|
+
* via `aria-describedby` attribute used by screen readers.
|
|
3966
|
+
* @param {HTMLElement} ariaTarget
|
|
3967
|
+
*/
|
|
3968
|
+
setAriaTarget(ariaTarget) {
|
|
3969
|
+
this.ariaTarget = ariaTarget;
|
|
2281
3970
|
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
el.removeAttribute('theme');
|
|
3971
|
+
const tooltipNode = this.node;
|
|
3972
|
+
if (tooltipNode) {
|
|
3973
|
+
tooltipNode.ariaTarget = ariaTarget;
|
|
2286
3974
|
}
|
|
3975
|
+
}
|
|
2287
3976
|
|
|
2288
|
-
|
|
2289
|
-
|
|
3977
|
+
/**
|
|
3978
|
+
* Set a context object to be used by generator.
|
|
3979
|
+
* @param {object} context
|
|
3980
|
+
*/
|
|
3981
|
+
setContext(context) {
|
|
3982
|
+
this.context = context;
|
|
3983
|
+
|
|
3984
|
+
const tooltipNode = this.node;
|
|
3985
|
+
if (tooltipNode) {
|
|
3986
|
+
tooltipNode.context = context;
|
|
2290
3987
|
}
|
|
2291
3988
|
}
|
|
2292
3989
|
|
|
2293
|
-
/**
|
|
2294
|
-
|
|
2295
|
-
|
|
3990
|
+
/**
|
|
3991
|
+
* Toggle manual state on the slotted tooltip.
|
|
3992
|
+
* @param {boolean} manual
|
|
3993
|
+
*/
|
|
3994
|
+
setManual(manual) {
|
|
3995
|
+
this.manual = manual;
|
|
3996
|
+
|
|
3997
|
+
const tooltipNode = this.node;
|
|
3998
|
+
if (tooltipNode) {
|
|
3999
|
+
tooltipNode.manual = manual;
|
|
4000
|
+
}
|
|
2296
4001
|
}
|
|
2297
4002
|
|
|
2298
4003
|
/**
|
|
2299
|
-
*
|
|
2300
|
-
*
|
|
2301
|
-
* scrolling the parent similarly to touch scrolling.
|
|
4004
|
+
* Toggle opened state on the slotted tooltip.
|
|
4005
|
+
* @param {boolean} opened
|
|
2302
4006
|
*/
|
|
2303
|
-
|
|
2304
|
-
this
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
e.preventDefault();
|
|
2311
|
-
}
|
|
2312
|
-
});
|
|
4007
|
+
setOpened(opened) {
|
|
4008
|
+
this.opened = opened;
|
|
4009
|
+
|
|
4010
|
+
const tooltipNode = this.node;
|
|
4011
|
+
if (tooltipNode) {
|
|
4012
|
+
tooltipNode.opened = opened;
|
|
4013
|
+
}
|
|
2313
4014
|
}
|
|
2314
4015
|
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
4016
|
+
/**
|
|
4017
|
+
* Set default position for the slotted tooltip.
|
|
4018
|
+
* This can be overridden by setting the position
|
|
4019
|
+
* using corresponding property or attribute.
|
|
4020
|
+
* @param {string} position
|
|
4021
|
+
*/
|
|
4022
|
+
setPosition(position) {
|
|
4023
|
+
this.position = position;
|
|
4024
|
+
|
|
4025
|
+
const tooltipNode = this.node;
|
|
4026
|
+
if (tooltipNode) {
|
|
4027
|
+
tooltipNode._position = position;
|
|
2325
4028
|
}
|
|
4029
|
+
}
|
|
4030
|
+
|
|
4031
|
+
/**
|
|
4032
|
+
* Set function used to detect whether to show
|
|
4033
|
+
* the tooltip based on a condition.
|
|
4034
|
+
* @param {Function} shouldShow
|
|
4035
|
+
*/
|
|
4036
|
+
setShouldShow(shouldShow) {
|
|
4037
|
+
this.shouldShow = shouldShow;
|
|
2326
4038
|
|
|
2327
|
-
|
|
4039
|
+
const tooltipNode = this.node;
|
|
4040
|
+
if (tooltipNode) {
|
|
4041
|
+
tooltipNode.shouldShow = shouldShow;
|
|
4042
|
+
}
|
|
2328
4043
|
}
|
|
2329
4044
|
|
|
2330
4045
|
/**
|
|
2331
|
-
*
|
|
2332
|
-
*
|
|
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
|
|
4046
|
+
* Set an HTML element to attach the tooltip to.
|
|
4047
|
+
* @param {HTMLElement} target
|
|
2342
4048
|
*/
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
},
|
|
2351
|
-
}),
|
|
2352
|
-
);
|
|
2353
|
-
});
|
|
4049
|
+
setTarget(target) {
|
|
4050
|
+
this.target = target;
|
|
4051
|
+
|
|
4052
|
+
const tooltipNode = this.node;
|
|
4053
|
+
if (tooltipNode) {
|
|
4054
|
+
tooltipNode.target = target;
|
|
4055
|
+
}
|
|
2354
4056
|
}
|
|
2355
4057
|
|
|
2356
4058
|
/** @private */
|
|
2357
|
-
|
|
2358
|
-
|
|
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;
|
|
4059
|
+
__notifyChange() {
|
|
4060
|
+
this.dispatchEvent(new CustomEvent('tooltip-changed', { detail: { node: this.node } }));
|
|
2362
4061
|
}
|
|
2363
4062
|
}
|
|
2364
4063
|
|
|
2365
|
-
|
|
4064
|
+
/**
|
|
4065
|
+
* @license
|
|
4066
|
+
* Copyright (c) 2021 - 2023 Vaadin Ltd.
|
|
4067
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
4068
|
+
*/
|
|
4069
|
+
|
|
4070
|
+
/**
|
|
4071
|
+
* A mixin to provide `pattern` property.
|
|
4072
|
+
*
|
|
4073
|
+
* @polymerMixin
|
|
4074
|
+
* @mixes InputConstraintsMixin
|
|
4075
|
+
*/
|
|
4076
|
+
const PatternMixin = (superclass) =>
|
|
4077
|
+
class PatternMixinClass extends InputConstraintsMixin(superclass) {
|
|
4078
|
+
static get properties() {
|
|
4079
|
+
return {
|
|
4080
|
+
/**
|
|
4081
|
+
* A regular expression that the value is checked against.
|
|
4082
|
+
* The pattern must match the entire value, not just some subset.
|
|
4083
|
+
*/
|
|
4084
|
+
pattern: {
|
|
4085
|
+
type: String,
|
|
4086
|
+
},
|
|
4087
|
+
};
|
|
4088
|
+
}
|
|
4089
|
+
|
|
4090
|
+
static get delegateAttrs() {
|
|
4091
|
+
return [...super.delegateAttrs, 'pattern'];
|
|
4092
|
+
}
|
|
4093
|
+
|
|
4094
|
+
static get constraints() {
|
|
4095
|
+
return [...super.constraints, 'pattern'];
|
|
4096
|
+
}
|
|
4097
|
+
};
|
|
2366
4098
|
|
|
2367
4099
|
/**
|
|
2368
4100
|
* @license
|
|
2369
|
-
* Copyright (c) 2015 -
|
|
4101
|
+
* Copyright (c) 2015 - 2023 Vaadin Ltd.
|
|
2370
4102
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
2371
4103
|
*/
|
|
2372
4104
|
|
|
@@ -2676,20 +4408,179 @@ const ComboBoxDataProviderMixin = (superClass) =>
|
|
|
2676
4408
|
_flushPendingRequests(size) {
|
|
2677
4409
|
if (this._pendingRequests) {
|
|
2678
4410
|
const lastPage = Math.ceil(size / this.pageSize);
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
if (page >= lastPage) {
|
|
2683
|
-
this._pendingRequests[page]([], size);
|
|
4411
|
+
Object.entries(this._pendingRequests).forEach(([page, callback]) => {
|
|
4412
|
+
if (parseInt(page) >= lastPage) {
|
|
4413
|
+
callback([], size);
|
|
2684
4414
|
}
|
|
4415
|
+
});
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
};
|
|
4419
|
+
|
|
4420
|
+
/**
|
|
4421
|
+
* @license
|
|
4422
|
+
* Copyright (c) 2021 - 2023 Vaadin Ltd.
|
|
4423
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
4424
|
+
*/
|
|
4425
|
+
|
|
4426
|
+
/**
|
|
4427
|
+
* @typedef ReactiveController
|
|
4428
|
+
* @type {import('lit').ReactiveController}
|
|
4429
|
+
*/
|
|
4430
|
+
|
|
4431
|
+
/**
|
|
4432
|
+
* A mixin for connecting controllers to the element.
|
|
4433
|
+
*
|
|
4434
|
+
* @polymerMixin
|
|
4435
|
+
*/
|
|
4436
|
+
const ControllerMixin = dedupingMixin((superClass) => {
|
|
4437
|
+
// If the superclass extends from LitElement,
|
|
4438
|
+
// use its own controllers implementation.
|
|
4439
|
+
if (typeof superClass.prototype.addController === 'function') {
|
|
4440
|
+
return superClass;
|
|
4441
|
+
}
|
|
4442
|
+
|
|
4443
|
+
return class ControllerMixinClass extends superClass {
|
|
4444
|
+
constructor() {
|
|
4445
|
+
super();
|
|
4446
|
+
|
|
4447
|
+
/**
|
|
4448
|
+
* @type {Set<ReactiveController>}
|
|
4449
|
+
*/
|
|
4450
|
+
this.__controllers = new Set();
|
|
4451
|
+
}
|
|
4452
|
+
|
|
4453
|
+
/** @protected */
|
|
4454
|
+
connectedCallback() {
|
|
4455
|
+
super.connectedCallback();
|
|
4456
|
+
|
|
4457
|
+
this.__controllers.forEach((c) => {
|
|
4458
|
+
if (c.hostConnected) {
|
|
4459
|
+
c.hostConnected();
|
|
4460
|
+
}
|
|
4461
|
+
});
|
|
4462
|
+
}
|
|
4463
|
+
|
|
4464
|
+
/** @protected */
|
|
4465
|
+
disconnectedCallback() {
|
|
4466
|
+
super.disconnectedCallback();
|
|
4467
|
+
|
|
4468
|
+
this.__controllers.forEach((c) => {
|
|
4469
|
+
if (c.hostDisconnected) {
|
|
4470
|
+
c.hostDisconnected();
|
|
4471
|
+
}
|
|
4472
|
+
});
|
|
4473
|
+
}
|
|
4474
|
+
|
|
4475
|
+
/**
|
|
4476
|
+
* Registers a controller to participate in the element update cycle.
|
|
4477
|
+
*
|
|
4478
|
+
* @param {ReactiveController} controller
|
|
4479
|
+
* @protected
|
|
4480
|
+
*/
|
|
4481
|
+
addController(controller) {
|
|
4482
|
+
this.__controllers.add(controller);
|
|
4483
|
+
// Call hostConnected if a controller is added after the element is attached.
|
|
4484
|
+
if (this.$ !== undefined && this.isConnected && controller.hostConnected) {
|
|
4485
|
+
controller.hostConnected();
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
|
|
4489
|
+
/**
|
|
4490
|
+
* Removes a controller from the element.
|
|
4491
|
+
*
|
|
4492
|
+
* @param {ReactiveController} controller
|
|
4493
|
+
* @protected
|
|
4494
|
+
*/
|
|
4495
|
+
removeController(controller) {
|
|
4496
|
+
this.__controllers.delete(controller);
|
|
4497
|
+
}
|
|
4498
|
+
};
|
|
4499
|
+
});
|
|
4500
|
+
|
|
4501
|
+
/**
|
|
4502
|
+
* @license
|
|
4503
|
+
* Copyright (c) 2023 Vaadin Ltd.
|
|
4504
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
4505
|
+
*/
|
|
4506
|
+
|
|
4507
|
+
/**
|
|
4508
|
+
* A mixin that forwards CSS class names to the internal overlay element
|
|
4509
|
+
* by setting the `overlayClass` property or `overlay-class` attribute.
|
|
4510
|
+
*
|
|
4511
|
+
* @polymerMixin
|
|
4512
|
+
*/
|
|
4513
|
+
const OverlayClassMixin = (superclass) =>
|
|
4514
|
+
class OverlayClassMixinClass extends superclass {
|
|
4515
|
+
static get properties() {
|
|
4516
|
+
return {
|
|
4517
|
+
/**
|
|
4518
|
+
* A space-delimited list of CSS class names to set on the overlay element.
|
|
4519
|
+
* This property does not affect other CSS class names set manually via JS.
|
|
4520
|
+
*
|
|
4521
|
+
* Note, if the CSS class name was set with this property, clearing it will
|
|
4522
|
+
* remove it from the overlay, even if the same class name was also added
|
|
4523
|
+
* manually, e.g. by using `classList.add()` in the `renderer` function.
|
|
4524
|
+
*
|
|
4525
|
+
* @attr {string} overlay-class
|
|
4526
|
+
*/
|
|
4527
|
+
overlayClass: {
|
|
4528
|
+
type: String,
|
|
4529
|
+
},
|
|
4530
|
+
|
|
4531
|
+
/**
|
|
4532
|
+
* An overlay element on which CSS class names are set.
|
|
4533
|
+
*
|
|
4534
|
+
* @protected
|
|
4535
|
+
*/
|
|
4536
|
+
_overlayElement: {
|
|
4537
|
+
type: Object,
|
|
4538
|
+
},
|
|
4539
|
+
};
|
|
4540
|
+
}
|
|
4541
|
+
|
|
4542
|
+
static get observers() {
|
|
4543
|
+
return ['__updateOverlayClassNames(overlayClass, _overlayElement)'];
|
|
4544
|
+
}
|
|
4545
|
+
|
|
4546
|
+
/** @private */
|
|
4547
|
+
__updateOverlayClassNames(overlayClass, overlayElement) {
|
|
4548
|
+
if (!overlayElement) {
|
|
4549
|
+
return;
|
|
4550
|
+
}
|
|
4551
|
+
|
|
4552
|
+
// Overlay is set but overlayClass is not set
|
|
4553
|
+
if (overlayClass === undefined) {
|
|
4554
|
+
return;
|
|
4555
|
+
}
|
|
4556
|
+
|
|
4557
|
+
const { classList } = overlayElement;
|
|
4558
|
+
|
|
4559
|
+
if (!this.__initialClasses) {
|
|
4560
|
+
this.__initialClasses = new Set(classList);
|
|
4561
|
+
}
|
|
4562
|
+
|
|
4563
|
+
if (Array.isArray(this.__previousClasses)) {
|
|
4564
|
+
// Remove old classes that no longer apply
|
|
4565
|
+
const classesToRemove = this.__previousClasses.filter((name) => !this.__initialClasses.has(name));
|
|
4566
|
+
if (classesToRemove.length > 0) {
|
|
4567
|
+
classList.remove(...classesToRemove);
|
|
2685
4568
|
}
|
|
2686
4569
|
}
|
|
4570
|
+
|
|
4571
|
+
// Add new classes based on the overlayClass
|
|
4572
|
+
const classesToAdd = typeof overlayClass === 'string' ? overlayClass.split(' ') : [];
|
|
4573
|
+
if (classesToAdd.length > 0) {
|
|
4574
|
+
classList.add(...classesToAdd);
|
|
4575
|
+
}
|
|
4576
|
+
|
|
4577
|
+
this.__previousClasses = classesToAdd;
|
|
2687
4578
|
}
|
|
2688
4579
|
};
|
|
2689
4580
|
|
|
2690
4581
|
/**
|
|
2691
4582
|
* @license
|
|
2692
|
-
* Copyright (c) 2021 -
|
|
4583
|
+
* Copyright (c) 2021 - 2023 Vaadin Ltd.
|
|
2693
4584
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
2694
4585
|
*/
|
|
2695
4586
|
|
|
@@ -2714,7 +4605,7 @@ function processTemplates(component) {
|
|
|
2714
4605
|
|
|
2715
4606
|
/**
|
|
2716
4607
|
* @license
|
|
2717
|
-
* Copyright (c) 2015 -
|
|
4608
|
+
* Copyright (c) 2015 - 2023 Vaadin Ltd.
|
|
2718
4609
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
2719
4610
|
*/
|
|
2720
4611
|
|
|
@@ -2748,10 +4639,19 @@ function findItemIndex(items, callback) {
|
|
|
2748
4639
|
|
|
2749
4640
|
/**
|
|
2750
4641
|
* @polymerMixin
|
|
4642
|
+
* @mixes ControllerMixin
|
|
4643
|
+
* @mixes ValidateMixin
|
|
4644
|
+
* @mixes DisabledMixin
|
|
4645
|
+
* @mixes InputMixin
|
|
4646
|
+
* @mixes KeyboardMixin
|
|
4647
|
+
* @mixes FocusMixin
|
|
4648
|
+
* @mixes OverlayClassMixin
|
|
2751
4649
|
* @param {function(new:HTMLElement)} subclass
|
|
2752
4650
|
*/
|
|
2753
4651
|
const ComboBoxMixin = (subclass) =>
|
|
2754
|
-
class
|
|
4652
|
+
class ComboBoxMixinClass extends OverlayClassMixin(
|
|
4653
|
+
ControllerMixin(ValidateMixin(FocusMixin(KeyboardMixin(InputMixin(DisabledMixin(subclass)))))),
|
|
4654
|
+
) {
|
|
2755
4655
|
static get properties() {
|
|
2756
4656
|
return {
|
|
2757
4657
|
/**
|
|
@@ -2950,7 +4850,6 @@ const ComboBoxMixin = (subclass) =>
|
|
|
2950
4850
|
|
|
2951
4851
|
constructor() {
|
|
2952
4852
|
super();
|
|
2953
|
-
this._boundOnFocusout = this._onFocusout.bind(this);
|
|
2954
4853
|
this._boundOverlaySelectedItemChanged = this._overlaySelectedItemChanged.bind(this);
|
|
2955
4854
|
this._boundOnClearButtonMouseDown = this.__onClearButtonMouseDown.bind(this);
|
|
2956
4855
|
this._boundOnClick = this._onClick.bind(this);
|
|
@@ -2967,24 +4866,6 @@ const ComboBoxMixin = (subclass) =>
|
|
|
2967
4866
|
return 'vaadin-combo-box';
|
|
2968
4867
|
}
|
|
2969
4868
|
|
|
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
|
-
|
|
2988
4869
|
/**
|
|
2989
4870
|
* Get a reference to the native `<input>` element.
|
|
2990
4871
|
* Override to provide a custom input.
|
|
@@ -3035,8 +4916,6 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3035
4916
|
this._initOverlay();
|
|
3036
4917
|
this._initScroller();
|
|
3037
4918
|
|
|
3038
|
-
this.addEventListener('focusout', this._boundOnFocusout);
|
|
3039
|
-
|
|
3040
4919
|
this._lastCommittedValue = this.value;
|
|
3041
4920
|
|
|
3042
4921
|
this.addEventListener('click', this._boundOnClick);
|
|
@@ -3044,7 +4923,7 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3044
4923
|
|
|
3045
4924
|
const bringToFrontListener = () => {
|
|
3046
4925
|
requestAnimationFrame(() => {
|
|
3047
|
-
this
|
|
4926
|
+
this._overlayElement.bringToFront();
|
|
3048
4927
|
});
|
|
3049
4928
|
};
|
|
3050
4929
|
|
|
@@ -3135,6 +5014,8 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3135
5014
|
overlay.addEventListener('opened-changed', (e) => {
|
|
3136
5015
|
this._overlayOpened = e.detail.value;
|
|
3137
5016
|
});
|
|
5017
|
+
|
|
5018
|
+
this._overlayElement = overlay;
|
|
3138
5019
|
}
|
|
3139
5020
|
|
|
3140
5021
|
/**
|
|
@@ -3146,7 +5027,7 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3146
5027
|
_initScroller(host) {
|
|
3147
5028
|
const scrollerTag = `${this._tagNamePrefix}-scroller`;
|
|
3148
5029
|
|
|
3149
|
-
const overlay = this
|
|
5030
|
+
const overlay = this._overlayElement;
|
|
3150
5031
|
|
|
3151
5032
|
overlay.renderer = (root) => {
|
|
3152
5033
|
if (!root.firstChild) {
|
|
@@ -3159,7 +5040,7 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3159
5040
|
|
|
3160
5041
|
const scroller = overlay.querySelector(scrollerTag);
|
|
3161
5042
|
|
|
3162
|
-
scroller.
|
|
5043
|
+
scroller.owner = host || this;
|
|
3163
5044
|
scroller.getItemLabel = this._getItemLabel.bind(this);
|
|
3164
5045
|
scroller.addEventListener('selection-changed', this._boundOverlaySelectedItemChanged);
|
|
3165
5046
|
|
|
@@ -3254,7 +5135,7 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3254
5135
|
}
|
|
3255
5136
|
}
|
|
3256
5137
|
|
|
3257
|
-
this
|
|
5138
|
+
this._overlayElement.restoreFocusOnClose = true;
|
|
3258
5139
|
} else {
|
|
3259
5140
|
this._onClosed();
|
|
3260
5141
|
if (this._openedWithFocusRing && this._isInputFocused()) {
|
|
@@ -3288,13 +5169,19 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3288
5169
|
return event.composedPath()[0] === this.clearElement;
|
|
3289
5170
|
}
|
|
3290
5171
|
|
|
5172
|
+
/** @private */
|
|
5173
|
+
__onClearButtonMouseDown(event) {
|
|
5174
|
+
event.preventDefault(); // Prevent native focusout event
|
|
5175
|
+
this.inputElement.focus();
|
|
5176
|
+
}
|
|
5177
|
+
|
|
3291
5178
|
/**
|
|
3292
5179
|
* @param {Event} event
|
|
3293
5180
|
* @protected
|
|
3294
5181
|
*/
|
|
3295
|
-
|
|
5182
|
+
_onClearButtonClick(event) {
|
|
3296
5183
|
event.preventDefault();
|
|
3297
|
-
this.
|
|
5184
|
+
this._onClearAction();
|
|
3298
5185
|
|
|
3299
5186
|
// De-select dropdown item
|
|
3300
5187
|
if (this.opened) {
|
|
@@ -3330,15 +5217,13 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3330
5217
|
}
|
|
3331
5218
|
|
|
3332
5219
|
/** @private */
|
|
3333
|
-
_onClick(
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
if (
|
|
3337
|
-
this.
|
|
3338
|
-
} else if (path.indexOf(this._toggleElement) > -1) {
|
|
3339
|
-
this._onToggleButtonClick(e);
|
|
5220
|
+
_onClick(event) {
|
|
5221
|
+
if (this._isClearButton(event)) {
|
|
5222
|
+
this._onClearButtonClick(event);
|
|
5223
|
+
} else if (event.composedPath().includes(this._toggleElement)) {
|
|
5224
|
+
this._onToggleButtonClick(event);
|
|
3340
5225
|
} else {
|
|
3341
|
-
this._onHostClick(
|
|
5226
|
+
this._onHostClick(event);
|
|
3342
5227
|
}
|
|
3343
5228
|
}
|
|
3344
5229
|
|
|
@@ -3353,7 +5238,7 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3353
5238
|
super._onKeyDown(e);
|
|
3354
5239
|
|
|
3355
5240
|
if (e.key === 'Tab') {
|
|
3356
|
-
this
|
|
5241
|
+
this._overlayElement.restoreFocusOnClose = false;
|
|
3357
5242
|
} else if (e.key === 'ArrowDown') {
|
|
3358
5243
|
this._onArrowDown();
|
|
3359
5244
|
|
|
@@ -3369,7 +5254,7 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3369
5254
|
|
|
3370
5255
|
/** @private */
|
|
3371
5256
|
_getItemLabel(item) {
|
|
3372
|
-
let label = item && this.itemLabelPath ?
|
|
5257
|
+
let label = item && this.itemLabelPath ? get(this.itemLabelPath, item) : undefined;
|
|
3373
5258
|
if (label === undefined || label === null) {
|
|
3374
5259
|
label = item ? item.toString() : '';
|
|
3375
5260
|
}
|
|
@@ -3378,7 +5263,7 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3378
5263
|
|
|
3379
5264
|
/** @private */
|
|
3380
5265
|
_getItemValue(item) {
|
|
3381
|
-
let value = item && this.itemValuePath ?
|
|
5266
|
+
let value = item && this.itemValuePath ? get(this.itemValuePath, item) : undefined;
|
|
3382
5267
|
if (value === undefined) {
|
|
3383
5268
|
value = item ? item.toString() : '';
|
|
3384
5269
|
}
|
|
@@ -3516,7 +5401,7 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3516
5401
|
} else if (this.clearButtonVisible && !this.opened && !!this.value) {
|
|
3517
5402
|
e.stopPropagation();
|
|
3518
5403
|
// The clear button is visible and the overlay is closed, so clear the value.
|
|
3519
|
-
this.
|
|
5404
|
+
this._onClearAction();
|
|
3520
5405
|
}
|
|
3521
5406
|
} else if (this.opened) {
|
|
3522
5407
|
// Auto-open is enabled
|
|
@@ -3534,7 +5419,7 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3534
5419
|
} else if (this.clearButtonVisible && !!this.value) {
|
|
3535
5420
|
e.stopPropagation();
|
|
3536
5421
|
// The clear button is visible and the overlay is closed, so clear the value.
|
|
3537
|
-
this.
|
|
5422
|
+
this._onClearAction();
|
|
3538
5423
|
}
|
|
3539
5424
|
}
|
|
3540
5425
|
|
|
@@ -3556,7 +5441,7 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3556
5441
|
* Clears the current value.
|
|
3557
5442
|
* @protected
|
|
3558
5443
|
*/
|
|
3559
|
-
|
|
5444
|
+
_onClearAction() {
|
|
3560
5445
|
this.selectedItem = null;
|
|
3561
5446
|
|
|
3562
5447
|
if (this.allowCustomValue) {
|
|
@@ -3607,7 +5492,7 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3607
5492
|
}
|
|
3608
5493
|
} else {
|
|
3609
5494
|
// Try to find an item which label matches the input value.
|
|
3610
|
-
const items = [...(this.filteredItems || [])
|
|
5495
|
+
const items = [this.selectedItem, ...(this.filteredItems || [])];
|
|
3611
5496
|
const itemMatchingInputValue = items[this.__getItemIndexByLabel(items, this._inputElementValue)];
|
|
3612
5497
|
|
|
3613
5498
|
if (
|
|
@@ -3648,14 +5533,6 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3648
5533
|
this.filter = '';
|
|
3649
5534
|
}
|
|
3650
5535
|
|
|
3651
|
-
/**
|
|
3652
|
-
* @return {string}
|
|
3653
|
-
* @protected
|
|
3654
|
-
*/
|
|
3655
|
-
get _propertyForValue() {
|
|
3656
|
-
return 'value';
|
|
3657
|
-
}
|
|
3658
|
-
|
|
3659
5536
|
/**
|
|
3660
5537
|
* Override an event listener from `InputMixin`.
|
|
3661
5538
|
* @param {!Event} event
|
|
@@ -3802,6 +5679,12 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3802
5679
|
|
|
3803
5680
|
/** @private */
|
|
3804
5681
|
_detectAndDispatchChange() {
|
|
5682
|
+
// Do not validate when focusout is caused by document
|
|
5683
|
+
// losing focus, which happens on browser tab switch.
|
|
5684
|
+
if (document.hasFocus()) {
|
|
5685
|
+
this.validate();
|
|
5686
|
+
}
|
|
5687
|
+
|
|
3805
5688
|
if (this.value !== this._lastCommittedValue) {
|
|
3806
5689
|
this.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
|
3807
5690
|
this._lastCommittedValue = this.value;
|
|
@@ -3944,26 +5827,18 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3944
5827
|
}
|
|
3945
5828
|
}
|
|
3946
5829
|
|
|
3947
|
-
/**
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
if (event.relatedTarget && event.relatedTarget.localName === `${this._tagNamePrefix}-item`) {
|
|
3958
|
-
return;
|
|
3959
|
-
}
|
|
5830
|
+
/**
|
|
5831
|
+
* Override method inherited from `FocusMixin`
|
|
5832
|
+
* to close the overlay on blur and commit the value.
|
|
5833
|
+
*
|
|
5834
|
+
* @param {boolean} focused
|
|
5835
|
+
* @protected
|
|
5836
|
+
* @override
|
|
5837
|
+
*/
|
|
5838
|
+
_setFocused(focused) {
|
|
5839
|
+
super._setFocused(focused);
|
|
3960
5840
|
|
|
3961
|
-
|
|
3962
|
-
if (event.relatedTarget === this.$.overlay) {
|
|
3963
|
-
event.composedPath()[0].focus();
|
|
3964
|
-
return;
|
|
3965
|
-
}
|
|
3966
|
-
if (!this.readonly && !this._closeOnBlurIsPrevented) {
|
|
5841
|
+
if (!focused && !this.readonly && !this._closeOnBlurIsPrevented) {
|
|
3967
5842
|
// User's logic in `custom-value-set` event listener might cause input to blur,
|
|
3968
5843
|
// which will result in attempting to commit the same custom value once again.
|
|
3969
5844
|
if (!this.opened && this.allowCustomValue && this._inputElementValue === this._lastCustomValue) {
|
|
@@ -3975,6 +5850,32 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3975
5850
|
}
|
|
3976
5851
|
}
|
|
3977
5852
|
|
|
5853
|
+
/**
|
|
5854
|
+
* Override method inherited from `FocusMixin` to not remove focused
|
|
5855
|
+
* state when focus moves to the overlay.
|
|
5856
|
+
*
|
|
5857
|
+
* @param {FocusEvent} event
|
|
5858
|
+
* @return {boolean}
|
|
5859
|
+
* @protected
|
|
5860
|
+
* @override
|
|
5861
|
+
*/
|
|
5862
|
+
_shouldRemoveFocus(event) {
|
|
5863
|
+
// VoiceOver on iOS fires `focusout` event when moving focus to the item in the dropdown.
|
|
5864
|
+
// Do not focus the input in this case, because it would break announcement for the item.
|
|
5865
|
+
if (event.relatedTarget && event.relatedTarget.localName === `${this._tagNamePrefix}-item`) {
|
|
5866
|
+
return false;
|
|
5867
|
+
}
|
|
5868
|
+
|
|
5869
|
+
// Do not blur when focus moves to the overlay
|
|
5870
|
+
// Also, fixes the problem with `focusout` happening when clicking on the scroll bar on Edge
|
|
5871
|
+
if (event.relatedTarget === this._overlayElement) {
|
|
5872
|
+
event.composedPath()[0].focus();
|
|
5873
|
+
return false;
|
|
5874
|
+
}
|
|
5875
|
+
|
|
5876
|
+
return true;
|
|
5877
|
+
}
|
|
5878
|
+
|
|
3978
5879
|
/** @private */
|
|
3979
5880
|
_onTouchend(event) {
|
|
3980
5881
|
if (!this.clearElement || event.composedPath()[0] !== this.clearElement) {
|
|
@@ -3982,7 +5883,7 @@ const ComboBoxMixin = (subclass) =>
|
|
|
3982
5883
|
}
|
|
3983
5884
|
|
|
3984
5885
|
event.preventDefault();
|
|
3985
|
-
this.
|
|
5886
|
+
this._onClearAction();
|
|
3986
5887
|
}
|
|
3987
5888
|
|
|
3988
5889
|
/**
|
|
@@ -4028,7 +5929,7 @@ const ComboBoxMixin = (subclass) =>
|
|
|
4028
5929
|
|
|
4029
5930
|
/**
|
|
4030
5931
|
* @license
|
|
4031
|
-
* Copyright (c) 2015 -
|
|
5932
|
+
* Copyright (c) 2015 - 2023 Vaadin Ltd.
|
|
4032
5933
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
4033
5934
|
*/
|
|
4034
5935
|
|
|
@@ -4148,7 +6049,7 @@ registerStyles('vaadin-combo-box', inputFieldShared$1, { moduleId: 'vaadin-combo
|
|
|
4148
6049
|
* Note: the `theme` attribute value set on `<vaadin-combo-box>` is
|
|
4149
6050
|
* propagated to the internal components listed above.
|
|
4150
6051
|
*
|
|
4151
|
-
* See [Styling Components](https://vaadin.com/docs/latest/styling/
|
|
6052
|
+
* See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
|
|
4152
6053
|
*
|
|
4153
6054
|
* @fires {Event} change - Fired when the user commits a value change.
|
|
4154
6055
|
* @fires {CustomEvent} custom-value-set - Fired when the user sets a custom value.
|
|
@@ -4159,6 +6060,7 @@ registerStyles('vaadin-combo-box', inputFieldShared$1, { moduleId: 'vaadin-combo
|
|
|
4159
6060
|
* @fires {CustomEvent} value-changed - Fired when the `value` property changes.
|
|
4160
6061
|
* @fires {CustomEvent} validated - Fired whenever the field is validated.
|
|
4161
6062
|
*
|
|
6063
|
+
* @customElement
|
|
4162
6064
|
* @extends HTMLElement
|
|
4163
6065
|
* @mixes ElementMixin
|
|
4164
6066
|
* @mixes ThemableMixin
|
|
@@ -4261,6 +6163,7 @@ class ComboBox extends ComboBoxDataProviderMixin(
|
|
|
4261
6163
|
this._tooltipController = new TooltipController(this);
|
|
4262
6164
|
this.addController(this._tooltipController);
|
|
4263
6165
|
this._tooltipController.setPosition('top');
|
|
6166
|
+
this._tooltipController.setAriaTarget(this.inputElement);
|
|
4264
6167
|
this._tooltipController.setShouldShow((target) => !target.opened);
|
|
4265
6168
|
|
|
4266
6169
|
this._positionTarget = this.shadowRoot.querySelector('[part="input-field"]');
|
|
@@ -4268,48 +6171,17 @@ class ComboBox extends ComboBoxDataProviderMixin(
|
|
|
4268
6171
|
}
|
|
4269
6172
|
|
|
4270
6173
|
/**
|
|
4271
|
-
* Override method
|
|
4272
|
-
*
|
|
4273
|
-
*
|
|
4274
|
-
*
|
|
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.
|
|
6174
|
+
* Override the method from `InputControlMixin`
|
|
6175
|
+
* to stop event propagation to prevent `ComboBoxMixin`
|
|
6176
|
+
* from handling this click event also on its own.
|
|
6177
|
+
*
|
|
4305
6178
|
* @param {Event} event
|
|
4306
6179
|
* @protected
|
|
4307
6180
|
* @override
|
|
4308
6181
|
*/
|
|
4309
6182
|
_onClearButtonClick(event) {
|
|
4310
6183
|
event.stopPropagation();
|
|
4311
|
-
|
|
4312
|
-
this._handleClearButtonClick(event);
|
|
6184
|
+
super._onClearButtonClick(event);
|
|
4313
6185
|
}
|
|
4314
6186
|
|
|
4315
6187
|
/**
|
|
@@ -4326,4 +6198,4 @@ class ComboBox extends ComboBoxDataProviderMixin(
|
|
|
4326
6198
|
}
|
|
4327
6199
|
}
|
|
4328
6200
|
|
|
4329
|
-
|
|
6201
|
+
defineCustomElement(ComboBox);
|