@vaadin/select 24.1.5 → 24.2.0-alpha10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,640 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2017 - 2023 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { setAriaIDReference } from '@vaadin/a11y-base/src/aria-id-reference.js';
7
+ import { DelegateFocusMixin } from '@vaadin/a11y-base/src/delegate-focus-mixin.js';
8
+ import { KeyboardMixin } from '@vaadin/a11y-base/src/keyboard-mixin.js';
9
+ import { DelegateStateMixin } from '@vaadin/component-base/src/delegate-state-mixin.js';
10
+ import { MediaQueryController } from '@vaadin/component-base/src/media-query-controller.js';
11
+ import { OverlayClassMixin } from '@vaadin/component-base/src/overlay-class-mixin.js';
12
+ import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js';
13
+ import { generateUniqueId } from '@vaadin/component-base/src/unique-id-utils.js';
14
+ import { FieldMixin } from '@vaadin/field-base/src/field-mixin.js';
15
+ import { LabelController } from '@vaadin/field-base/src/label-controller.js';
16
+ import { ButtonController } from './button-controller.js';
17
+
18
+ /**
19
+ * @polymerMixin
20
+ * @mixes DelegateFocusMixin
21
+ * @mixes DelegateStateMixin
22
+ * @mixes FieldMixin
23
+ * @mixes KeyboardMixin
24
+ * @mixes OverlayClassMixin
25
+ */
26
+ export const SelectBaseMixin = (superClass) =>
27
+ class SelectBaseMixin extends OverlayClassMixin(
28
+ DelegateFocusMixin(DelegateStateMixin(KeyboardMixin(FieldMixin(superClass)))),
29
+ ) {
30
+ static get properties() {
31
+ return {
32
+ /**
33
+ * An array containing items that will be rendered as the options of the select.
34
+ *
35
+ * #### Example
36
+ * ```js
37
+ * select.items = [
38
+ * { label: 'Most recent first', value: 'recent' },
39
+ * { component: 'hr' },
40
+ * { label: 'Rating: low to high', value: 'rating-asc' },
41
+ * { label: 'Rating: high to low', value: 'rating-desc' },
42
+ * { component: 'hr' },
43
+ * { label: 'Price: low to high', value: 'price-asc', disabled: true },
44
+ * { label: 'Price: high to low', value: 'price-desc', disabled: true }
45
+ * ];
46
+ * ```
47
+ *
48
+ * Note: each item is rendered by default as the internal `<vaadin-select-item>` that is an extension of `<vaadin-item>`.
49
+ * To render the item with a custom component, provide a tag name by the `component` property.
50
+ *
51
+ * @type {!Array<!SelectItem>}
52
+ */
53
+ items: {
54
+ type: Array,
55
+ observer: '__itemsChanged',
56
+ },
57
+
58
+ /**
59
+ * Set when the select is open
60
+ * @type {boolean}
61
+ */
62
+ opened: {
63
+ type: Boolean,
64
+ value: false,
65
+ notify: true,
66
+ reflectToAttribute: true,
67
+ observer: '_openedChanged',
68
+ },
69
+
70
+ /**
71
+ * Custom function for rendering the content of the `<vaadin-select>`.
72
+ * Receives two arguments:
73
+ *
74
+ * - `root` The `<vaadin-select-overlay>` internal container
75
+ * DOM element. Append your content to it.
76
+ * - `select` The reference to the `<vaadin-select>` element.
77
+ * @type {!SelectRenderer | undefined}
78
+ */
79
+ renderer: {
80
+ type: Object,
81
+ },
82
+
83
+ /**
84
+ * The `value` property of the selected item, or an empty string
85
+ * if no item is selected.
86
+ * On change or initialization, the component finds the item which matches the
87
+ * value and displays it.
88
+ * If no value is provided to the component, it selects the first item without
89
+ * value or empty value.
90
+ * Hint: If you do not want to select any item by default, you can either set all
91
+ * the values of inner vaadin-items, or set the vaadin-select value to
92
+ * an inexistent value in the items list.
93
+ * @type {string}
94
+ */
95
+ value: {
96
+ type: String,
97
+ value: '',
98
+ notify: true,
99
+ observer: '_valueChanged',
100
+ },
101
+
102
+ /**
103
+ * The name of this element.
104
+ */
105
+ name: {
106
+ type: String,
107
+ },
108
+
109
+ /**
110
+ * A hint to the user of what can be entered in the control.
111
+ * The placeholder will be displayed in the case that there
112
+ * is no item selected, or the selected item has an empty
113
+ * string label, or the selected item has no label and it's
114
+ * DOM content is empty.
115
+ */
116
+ placeholder: {
117
+ type: String,
118
+ },
119
+
120
+ /**
121
+ * When present, it specifies that the element is read-only.
122
+ * @type {boolean}
123
+ */
124
+ readonly: {
125
+ type: Boolean,
126
+ value: false,
127
+ reflectToAttribute: true,
128
+ },
129
+
130
+ /**
131
+ * Whether the field is dirty.
132
+ *
133
+ * The field is automatically marked as dirty once the user triggers
134
+ * a `change` event. Additionally, the field can be manually marked
135
+ * as dirty by setting the property to `true`.
136
+ */
137
+ dirty: {
138
+ type: Boolean,
139
+ value: false,
140
+ notify: true,
141
+ },
142
+
143
+ /** @private */
144
+ _phone: Boolean,
145
+
146
+ /** @private */
147
+ _phoneMediaQuery: {
148
+ value: '(max-width: 420px), (max-height: 420px)',
149
+ },
150
+
151
+ /** @private */
152
+ _inputContainer: Object,
153
+
154
+ /** @private */
155
+ _items: Object,
156
+ };
157
+ }
158
+
159
+ static get delegateAttrs() {
160
+ return [...super.delegateAttrs, 'invalid'];
161
+ }
162
+
163
+ static get observers() {
164
+ return ['_updateAriaExpanded(opened, focusElement)', '_updateSelectedItem(value, _items, placeholder)'];
165
+ }
166
+
167
+ constructor() {
168
+ super();
169
+
170
+ this._itemId = `value-${this.localName}-${generateUniqueId()}`;
171
+ this._srLabelController = new LabelController(this);
172
+ this._srLabelController.slotName = 'sr-label';
173
+ }
174
+
175
+ /** @protected */
176
+ disconnectedCallback() {
177
+ super.disconnectedCallback();
178
+
179
+ // Making sure the select is closed and removed from DOM after detaching the select.
180
+ this.opened = false;
181
+ }
182
+
183
+ /** @protected */
184
+ ready() {
185
+ super.ready();
186
+
187
+ const overlay = this.shadowRoot.querySelector('vaadin-select-overlay');
188
+ overlay.owner = this;
189
+ this._overlayElement = overlay;
190
+
191
+ this._inputContainer = this.shadowRoot.querySelector('[part~="input-field"]');
192
+
193
+ this._valueButtonController = new ButtonController(this);
194
+ this.addController(this._valueButtonController);
195
+
196
+ this.addController(this._srLabelController);
197
+
198
+ this.addController(
199
+ new MediaQueryController(this._phoneMediaQuery, (matches) => {
200
+ this._phone = matches;
201
+ }),
202
+ );
203
+
204
+ this._tooltipController = new TooltipController(this);
205
+ this._tooltipController.setPosition('top');
206
+ this._tooltipController.setAriaTarget(this.focusElement);
207
+ this.addController(this._tooltipController);
208
+ }
209
+
210
+ /**
211
+ * Requests an update for the content of the select.
212
+ * While performing the update, it invokes the renderer passed in the `renderer` property.
213
+ *
214
+ * It is not guaranteed that the update happens immediately (synchronously) after it is requested.
215
+ */
216
+ requestContentUpdate() {
217
+ if (!this._overlayElement) {
218
+ return;
219
+ }
220
+
221
+ this._overlayElement.requestContentUpdate();
222
+
223
+ if (this._menuElement && this._menuElement.items) {
224
+ this._updateSelectedItem(this.value, this._menuElement.items);
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Override an observer from `FieldMixin`
230
+ * to validate when required is removed.
231
+ *
232
+ * @protected
233
+ * @override
234
+ */
235
+ _requiredChanged(required) {
236
+ super._requiredChanged(required);
237
+
238
+ if (required === false) {
239
+ this.validate();
240
+ }
241
+ }
242
+
243
+ /**
244
+ * @param {SelectItem[] | undefined | null} newItems
245
+ * @param {SelectItem[] | undefined | null} oldItems
246
+ * @private
247
+ */
248
+ __itemsChanged(newItems, oldItems) {
249
+ if (newItems || oldItems) {
250
+ this.requestContentUpdate();
251
+ }
252
+ }
253
+
254
+ /**
255
+ * @param {HTMLElement} menuElement
256
+ * @protected
257
+ */
258
+ _assignMenuElement(menuElement) {
259
+ if (menuElement && menuElement !== this.__lastMenuElement) {
260
+ this._menuElement = menuElement;
261
+
262
+ // Ensure items are initialized
263
+ this.__initMenuItems(menuElement);
264
+
265
+ menuElement.addEventListener('items-changed', () => {
266
+ this.__initMenuItems(menuElement);
267
+ });
268
+
269
+ menuElement.addEventListener('selected-changed', () => this.__updateValueButton());
270
+ // Use capture phase to make it possible for `<vaadin-grid-pro-edit-select>`
271
+ // to override and handle the keydown event before the value change happens.
272
+ menuElement.addEventListener('keydown', (e) => this._onKeyDownInside(e), true);
273
+ menuElement.addEventListener(
274
+ 'click',
275
+ () => {
276
+ this.__dispatchChangePending = true;
277
+ },
278
+ true,
279
+ );
280
+
281
+ // Store the menu element reference
282
+ this.__lastMenuElement = menuElement;
283
+ }
284
+ }
285
+
286
+ /** @private */
287
+ __initMenuItems(menuElement) {
288
+ if (menuElement.items) {
289
+ this._items = menuElement.items;
290
+ }
291
+ }
292
+
293
+ /** @private */
294
+ _valueChanged(value, oldValue) {
295
+ this.toggleAttribute('has-value', Boolean(value));
296
+
297
+ // Skip validation during initialization and when
298
+ // a change event is scheduled, as validation will be
299
+ // triggered by `__dispatchChange()` in that case.
300
+ if (oldValue !== undefined && !this.__dispatchChangePending) {
301
+ this.validate();
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Opens the overlay if the field is not read-only.
307
+ *
308
+ * @private
309
+ */
310
+ _onClick(event) {
311
+ // Prevent parent components such as `vaadin-grid`
312
+ // from handling the click event after it bubbles.
313
+ event.preventDefault();
314
+
315
+ this.opened = !this.readonly;
316
+ }
317
+
318
+ /** @private */
319
+ _onToggleMouseDown(event) {
320
+ // Prevent mousedown event to avoid blur and preserve focused state
321
+ // while opening, and to restore focus-ring attribute on closing.
322
+ event.preventDefault();
323
+ }
324
+
325
+ /**
326
+ * @param {!KeyboardEvent} e
327
+ * @protected
328
+ * @override
329
+ */
330
+ _onKeyDown(e) {
331
+ if (e.target === this.focusElement && !this.readonly && !this.opened) {
332
+ if (/^(Enter|SpaceBar|\s|ArrowDown|Down|ArrowUp|Up)$/u.test(e.key)) {
333
+ e.preventDefault();
334
+ this.opened = true;
335
+ } else if (/[\p{L}\p{Nd}]/u.test(e.key) && e.key.length === 1) {
336
+ const selected = this._menuElement.selected;
337
+ const currentIdx = selected !== undefined ? selected : -1;
338
+ const newIdx = this._menuElement._searchKey(currentIdx, e.key);
339
+ if (newIdx >= 0) {
340
+ this.__dispatchChangePending = true;
341
+
342
+ // Announce the value selected with the first letter shortcut
343
+ this._updateAriaLive(true);
344
+ this._menuElement.selected = newIdx;
345
+ }
346
+ }
347
+ }
348
+ }
349
+
350
+ /**
351
+ * @param {!KeyboardEvent} e
352
+ * @protected
353
+ */
354
+ _onKeyDownInside(e) {
355
+ if (/^(Tab)$/u.test(e.key)) {
356
+ this.opened = false;
357
+ }
358
+ }
359
+
360
+ /** @private */
361
+ _openedChanged(opened, wasOpened) {
362
+ if (opened) {
363
+ // Avoid multiple announcements when a value gets selected from the dropdown
364
+ this._updateAriaLive(false);
365
+
366
+ if (!this._overlayElement || !this._menuElement || !this.focusElement || this.disabled || this.readonly) {
367
+ this.opened = false;
368
+ return;
369
+ }
370
+
371
+ this._overlayElement.style.setProperty(
372
+ '--vaadin-select-text-field-width',
373
+ `${this._inputContainer.offsetWidth}px`,
374
+ );
375
+
376
+ // Preserve focus-ring to restore it later
377
+ const hasFocusRing = this.hasAttribute('focus-ring');
378
+ this._openedWithFocusRing = hasFocusRing;
379
+
380
+ // Opened select should not keep focus-ring
381
+ if (hasFocusRing) {
382
+ this.removeAttribute('focus-ring');
383
+ }
384
+ } else if (wasOpened) {
385
+ this.focus();
386
+ if (this._openedWithFocusRing) {
387
+ this.setAttribute('focus-ring', '');
388
+ }
389
+
390
+ // Skip validation when a change event is scheduled, as validation
391
+ // will be triggered by `__dispatchChange()` in that case.
392
+ // Also, skip validation when closed on Escape or Tab keys.
393
+ if (!this.__dispatchChangePending && !this._keyboardActive) {
394
+ this.validate();
395
+ }
396
+ }
397
+ }
398
+
399
+ /** @private */
400
+ _updateAriaExpanded(opened, focusElement) {
401
+ if (focusElement) {
402
+ focusElement.setAttribute('aria-expanded', opened ? 'true' : 'false');
403
+ }
404
+ }
405
+
406
+ /** @private */
407
+ _updateAriaLive(ariaLive) {
408
+ if (this.focusElement) {
409
+ if (ariaLive) {
410
+ this.focusElement.setAttribute('aria-live', 'polite');
411
+ } else {
412
+ this.focusElement.removeAttribute('aria-live');
413
+ }
414
+ }
415
+ }
416
+
417
+ /** @private */
418
+ __attachSelectedItem(selected) {
419
+ let labelItem;
420
+
421
+ const label = selected.getAttribute('label');
422
+ if (label) {
423
+ labelItem = this.__createItemElement({ label });
424
+ } else {
425
+ labelItem = selected.cloneNode(true);
426
+ }
427
+
428
+ // Store reference to the original item
429
+ labelItem._sourceItem = selected;
430
+
431
+ this.__appendValueItemElement(labelItem, this.focusElement);
432
+
433
+ // Ensure the item gets proper styles
434
+ labelItem.selected = true;
435
+ }
436
+
437
+ /**
438
+ * @param {!SelectItem} item
439
+ * @private
440
+ */
441
+ __createItemElement(item) {
442
+ const itemElement = document.createElement(item.component || 'vaadin-select-item');
443
+ if (item.label) {
444
+ itemElement.textContent = item.label;
445
+ }
446
+ if (item.value) {
447
+ itemElement.value = item.value;
448
+ }
449
+ if (item.disabled) {
450
+ itemElement.disabled = item.disabled;
451
+ }
452
+ return itemElement;
453
+ }
454
+
455
+ /**
456
+ * @param {!HTMLElement} itemElement
457
+ * @param {!HTMLElement} parent
458
+ * @private
459
+ */
460
+ __appendValueItemElement(itemElement, parent) {
461
+ parent.appendChild(itemElement);
462
+ itemElement.removeAttribute('tabindex');
463
+ itemElement.removeAttribute('aria-selected');
464
+ itemElement.removeAttribute('role');
465
+ itemElement.removeAttribute('focused');
466
+ itemElement.removeAttribute('focus-ring');
467
+ itemElement.removeAttribute('active');
468
+ itemElement.setAttribute('id', this._itemId);
469
+ }
470
+
471
+ /**
472
+ * @param {string} accessibleName
473
+ * @protected
474
+ */
475
+ _accessibleNameChanged(accessibleName) {
476
+ this._srLabelController.setLabel(accessibleName);
477
+ this._setCustomAriaLabelledBy(accessibleName ? this._srLabelController.defaultId : null);
478
+ }
479
+
480
+ /**
481
+ * @param {string} accessibleNameRef
482
+ * @protected
483
+ */
484
+ _accessibleNameRefChanged(accessibleNameRef) {
485
+ this._setCustomAriaLabelledBy(accessibleNameRef);
486
+ }
487
+
488
+ /**
489
+ * @param {string} ariaLabelledby
490
+ * @private
491
+ */
492
+ _setCustomAriaLabelledBy(ariaLabelledby) {
493
+ const labelId = this._getLabelIdWithItemId(ariaLabelledby);
494
+ this._fieldAriaController.setLabelId(labelId, true);
495
+ }
496
+
497
+ /**
498
+ * @param {string | null} labelId
499
+ * @returns string | null
500
+ * @private
501
+ */
502
+ _getLabelIdWithItemId(labelId) {
503
+ const selected = this._items ? this._items[this._menuElement.selected] : false;
504
+ const itemId = selected || this.placeholder ? this._itemId : '';
505
+
506
+ return labelId ? `${labelId} ${itemId}`.trim() : null;
507
+ }
508
+
509
+ /** @private */
510
+ __updateValueButton() {
511
+ const valueButton = this.focusElement;
512
+
513
+ if (!valueButton) {
514
+ return;
515
+ }
516
+
517
+ valueButton.innerHTML = '';
518
+
519
+ const selected = this._items[this._menuElement.selected];
520
+
521
+ valueButton.removeAttribute('placeholder');
522
+
523
+ if (!selected) {
524
+ if (this.placeholder) {
525
+ const item = this.__createItemElement({ label: this.placeholder });
526
+ this.__appendValueItemElement(item, valueButton);
527
+ valueButton.setAttribute('placeholder', '');
528
+ }
529
+ } else {
530
+ this.__attachSelectedItem(selected);
531
+
532
+ if (!this._valueChanging) {
533
+ this._selectedChanging = true;
534
+ this.value = selected.value || '';
535
+ if (this.__dispatchChangePending) {
536
+ this.opened = false;
537
+ this.__dispatchChange();
538
+ }
539
+ delete this._selectedChanging;
540
+ }
541
+ }
542
+
543
+ const labelledIdReferenceConfig =
544
+ selected || this.placeholder ? { newId: this._itemId } : { oldId: this._itemId };
545
+
546
+ setAriaIDReference(valueButton, 'aria-labelledby', labelledIdReferenceConfig);
547
+ if (this.accessibleName || this.accessibleNameRef) {
548
+ this._setCustomAriaLabelledBy(this.accessibleNameRef || this._srLabelController.defaultId);
549
+ }
550
+ }
551
+
552
+ /** @private */
553
+ _updateSelectedItem(value, items) {
554
+ if (items) {
555
+ const valueAsString = value == null ? value : value.toString();
556
+ this._menuElement.selected = items.reduce((prev, item, idx) => {
557
+ return prev === undefined && item.value === valueAsString ? idx : prev;
558
+ }, undefined);
559
+ if (!this._selectedChanging) {
560
+ this._valueChanging = true;
561
+ this.__updateValueButton();
562
+ delete this._valueChanging;
563
+ }
564
+ }
565
+ }
566
+
567
+ /**
568
+ * Override method inherited from `FocusMixin` to not remove focused
569
+ * state when select is opened and focus moves to list-box.
570
+ * @return {boolean}
571
+ * @protected
572
+ * @override
573
+ */
574
+ _shouldRemoveFocus() {
575
+ return !this.opened;
576
+ }
577
+
578
+ /**
579
+ * Override method inherited from `FocusMixin` to validate on blur.
580
+ * @param {boolean} focused
581
+ * @protected
582
+ * @override
583
+ */
584
+ _setFocused(focused) {
585
+ super._setFocused(focused);
586
+
587
+ // Do not validate when focusout is caused by document
588
+ // losing focus, which happens on browser tab switch.
589
+ if (!focused && document.hasFocus()) {
590
+ this.validate();
591
+ }
592
+ }
593
+
594
+ /**
595
+ * Returns true if the current value satisfies all constraints (if any)
596
+ *
597
+ * @return {boolean}
598
+ */
599
+ checkValidity() {
600
+ return !this.required || this.readonly || !!this.value;
601
+ }
602
+
603
+ /**
604
+ * Renders items when they are provided by the `items` property and clears the content otherwise.
605
+ * @param {!HTMLElement} root
606
+ * @param {!Select} _select
607
+ * @private
608
+ */
609
+ __defaultRenderer(root, _select) {
610
+ if (!this.items || this.items.length === 0) {
611
+ root.textContent = '';
612
+ return;
613
+ }
614
+
615
+ let listBox = root.firstElementChild;
616
+ if (!listBox) {
617
+ listBox = document.createElement('vaadin-select-list-box');
618
+ root.appendChild(listBox);
619
+ }
620
+
621
+ listBox.textContent = '';
622
+ this.items.forEach((item) => {
623
+ listBox.appendChild(this.__createItemElement(item));
624
+ });
625
+ }
626
+
627
+ /** @private */
628
+ async __dispatchChange() {
629
+ // Wait for the update complete to guarantee that value-changed is fired
630
+ // before validated and change events when using the Lit version of the component.
631
+ if (this.updateComplete) {
632
+ await this.updateComplete;
633
+ }
634
+
635
+ this.dirty = true;
636
+ this.validate();
637
+ this.dispatchEvent(new CustomEvent('change', { bubbles: true }));
638
+ this.__dispatchChangePending = false;
639
+ }
640
+ };
@@ -3,37 +3,56 @@
3
3
  * Copyright (c) 2017 - 2023 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
- import { Overlay } from '@vaadin/overlay/src/vaadin-overlay.js';
6
+ import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
7
+ import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
8
+ import { OverlayMixin } from '@vaadin/overlay/src/vaadin-overlay-mixin.js';
7
9
  import { PositionMixin } from '@vaadin/overlay/src/vaadin-overlay-position-mixin.js';
8
- import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
9
-
10
- registerStyles(
11
- 'vaadin-select-overlay',
12
- css`
13
- :host {
14
- align-items: flex-start;
15
- justify-content: flex-start;
16
- }
17
- @media (forced-colors: active) {
18
- [part='overlay'] {
19
- outline: 3px solid;
20
- }
10
+ import { overlayStyles } from '@vaadin/overlay/src/vaadin-overlay-styles.js';
11
+ import { css, registerStyles, ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
12
+
13
+ const selectOverlayStyles = css`
14
+ :host {
15
+ align-items: flex-start;
16
+ justify-content: flex-start;
17
+ }
18
+
19
+ @media (forced-colors: active) {
20
+ [part='overlay'] {
21
+ outline: 3px solid;
21
22
  }
22
- `,
23
- { moduleId: 'vaadin-select-overlay-styles' },
24
- );
23
+ }
24
+ `;
25
+
26
+ registerStyles('vaadin-select-overlay', [overlayStyles, selectOverlayStyles], {
27
+ moduleId: 'vaadin-select-overlay-styles',
28
+ });
25
29
 
26
30
  /**
27
31
  * An element used internally by `<vaadin-select>`. Not intended to be used separately.
28
32
  *
29
- * @extends Overlay
30
- * @protected
33
+ * @extends HTMLElement
34
+ * @mixes DirMixin
35
+ * @mixes OverlayMixin
36
+ * @mixes PositionMixin
37
+ * @mixes ThemableMixin
38
+ * @private
31
39
  */
32
- class SelectOverlay extends PositionMixin(Overlay) {
40
+ export class SelectOverlay extends PositionMixin(OverlayMixin(DirMixin(ThemableMixin(PolymerElement)))) {
33
41
  static get is() {
34
42
  return 'vaadin-select-overlay';
35
43
  }
36
44
 
45
+ static get template() {
46
+ return html`
47
+ <div id="backdrop" part="backdrop" hidden$="[[!withBackdrop]]"></div>
48
+ <div part="overlay" id="overlay" tabindex="0">
49
+ <div part="content" id="content">
50
+ <slot></slot>
51
+ </div>
52
+ </div>
53
+ `;
54
+ }
55
+
37
56
  requestContentUpdate() {
38
57
  super.requestContentUpdate();
39
58