@vaadin/overlay 25.0.0-alpha7 → 25.0.0-alpha9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/overlay",
3
- "version": "25.0.0-alpha7",
3
+ "version": "25.0.0-alpha9",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -36,17 +36,17 @@
36
36
  ],
37
37
  "dependencies": {
38
38
  "@open-wc/dedupe-mixin": "^1.3.0",
39
- "@vaadin/a11y-base": "25.0.0-alpha7",
40
- "@vaadin/component-base": "25.0.0-alpha7",
41
- "@vaadin/vaadin-lumo-styles": "25.0.0-alpha7",
42
- "@vaadin/vaadin-themable-mixin": "25.0.0-alpha7",
39
+ "@vaadin/a11y-base": "25.0.0-alpha9",
40
+ "@vaadin/component-base": "25.0.0-alpha9",
41
+ "@vaadin/vaadin-lumo-styles": "25.0.0-alpha9",
42
+ "@vaadin/vaadin-themable-mixin": "25.0.0-alpha9",
43
43
  "lit": "^3.0.0"
44
44
  },
45
45
  "devDependencies": {
46
- "@vaadin/chai-plugins": "25.0.0-alpha7",
47
- "@vaadin/test-runner-commands": "25.0.0-alpha7",
46
+ "@vaadin/chai-plugins": "25.0.0-alpha9",
47
+ "@vaadin/test-runner-commands": "25.0.0-alpha9",
48
48
  "@vaadin/testing-helpers": "^2.0.0",
49
49
  "sinon": "^18.0.0"
50
50
  },
51
- "gitHead": "87f72707ce6866892f8be5782fa0da008e87dcbc"
51
+ "gitHead": "bbe4720721e0955ffc87a79b412bee38b1f0eb1e"
52
52
  }
@@ -7,76 +7,81 @@ import '@vaadin/component-base/src/style-props.js';
7
7
  import { css } from 'lit';
8
8
 
9
9
  export const overlayStyles = css`
10
- @layer base {
11
- :host {
12
- z-index: 200;
13
- position: fixed;
10
+ :host {
11
+ z-index: 200;
12
+ position: fixed;
14
13
 
15
- /* Despite of what the names say, <vaadin-overlay> is just a container
14
+ /* Despite of what the names say, <vaadin-overlay> is just a container
16
15
  for position/sizing/alignment. The actual overlay is the overlay part. */
17
16
 
18
- /* Default position constraints. Themes can
17
+ /* Default position constraints. Themes can
19
18
  override this to adjust the gap between the overlay and the viewport. */
20
- inset: 8px;
21
- bottom: var(--vaadin-overlay-viewport-bottom);
19
+ inset: 8px;
20
+ bottom: var(--vaadin-overlay-viewport-bottom);
22
21
 
23
- /* Use flexbox alignment for the overlay part. */
24
- display: flex;
25
- flex-direction: column; /* makes dropdowns sizing easier */
26
- /* Align to center by default. */
27
- align-items: center;
28
- justify-content: center;
22
+ /* Override native [popover] user agent styles */
23
+ width: auto;
24
+ height: auto;
25
+ border: none;
26
+ padding: 0;
27
+ background-color: transparent;
28
+ overflow: visible;
29
29
 
30
- /* Allow centering when max-width/max-height applies. */
31
- margin: auto;
30
+ /* Use flexbox alignment for the overlay part. */
31
+ display: flex;
32
+ flex-direction: column; /* makes dropdowns sizing easier */
33
+ /* Align to center by default. */
34
+ align-items: center;
35
+ justify-content: center;
32
36
 
33
- /* The host is not clickable, only the overlay part is. */
34
- pointer-events: none;
37
+ /* Allow centering when max-width/max-height applies. */
38
+ margin: auto;
35
39
 
36
- /* Remove tap highlight on touch devices. */
37
- -webkit-tap-highlight-color: transparent;
40
+ /* The host is not clickable, only the overlay part is. */
41
+ pointer-events: none;
38
42
 
39
- /* CSS API for host */
40
- --vaadin-overlay-viewport-bottom: 8px;
41
- }
43
+ /* Remove tap highlight on touch devices. */
44
+ -webkit-tap-highlight-color: transparent;
42
45
 
43
- :host([hidden]),
44
- :host(:not([opened]):not([closing])),
45
- :host(:not([opened]):not([closing])) [part='overlay'] {
46
- display: none !important;
47
- }
46
+ /* CSS API for host */
47
+ --vaadin-overlay-viewport-bottom: 8px;
48
+ }
48
49
 
49
- [part='overlay'] {
50
- background: var(--vaadin-overlay-background, var(--vaadin-background-color));
51
- border: var(--vaadin-overlay-border, 1px solid var(--vaadin-border-color));
52
- border-radius: var(--vaadin-overlay-border-radius, var(--vaadin-radius-m));
53
- box-shadow: var(--vaadin-overlay-box-shadow, 0 8px 24px -4px rgba(0, 0, 0, 0.3));
54
- box-sizing: border-box;
55
- max-width: 100%;
56
- overflow: auto;
57
- overscroll-behavior: contain;
58
- pointer-events: auto;
59
- -webkit-tap-highlight-color: initial;
60
- }
50
+ :host([hidden]),
51
+ :host(:not([opened]):not([closing])),
52
+ :host(:not([opened]):not([closing])) [part='overlay'] {
53
+ display: none !important;
54
+ }
61
55
 
62
- [part='backdrop'] {
63
- background: var(--vaadin-overlay-backdrop-background, rgba(0, 0, 0, 0.5));
64
- content: '';
65
- inset: 0;
66
- pointer-events: auto;
67
- position: fixed;
68
- z-index: -1;
69
- }
56
+ [part='overlay'] {
57
+ background: var(--vaadin-overlay-background, var(--vaadin-background-color));
58
+ border: var(--vaadin-overlay-border-width, 1px) solid var(--vaadin-overlay-border-color, var(--vaadin-border-color));
59
+ border-radius: var(--vaadin-overlay-border-radius, var(--vaadin-radius-m));
60
+ box-shadow: var(--vaadin-overlay-box-shadow, 0 8px 24px -4px rgba(0, 0, 0, 0.3));
61
+ box-sizing: border-box;
62
+ max-width: 100%;
63
+ overflow: auto;
64
+ overscroll-behavior: contain;
65
+ pointer-events: auto;
66
+ -webkit-tap-highlight-color: initial;
67
+ }
68
+
69
+ [part='backdrop'] {
70
+ background: var(--vaadin-overlay-backdrop-background, rgba(0, 0, 0, 0.5));
71
+ content: '';
72
+ inset: 0;
73
+ pointer-events: auto;
74
+ position: fixed;
75
+ z-index: -1;
76
+ }
70
77
 
71
- @media (forced-colors: active) {
72
- [part='overlay'] {
73
- border: 3px solid;
74
- }
78
+ [part='overlay']:focus-visible {
79
+ outline: var(--vaadin-focus-ring-width) solid var(--vaadin-focus-ring-color);
80
+ }
75
81
 
76
- [part='overlay']:focus-visible {
77
- outline: var(--vaadin-focus-ring-width) solid;
78
- outline-offset: 1px;
79
- }
82
+ @media (forced-colors: active) {
83
+ [part='overlay'] {
84
+ border: 3px solid;
80
85
  }
81
86
  }
82
87
  `;
@@ -18,6 +18,14 @@ export const overlayStyles = css`
18
18
  inset: 0;
19
19
  bottom: var(--vaadin-overlay-viewport-bottom);
20
20
 
21
+ /* Override native [popover] user agent styles */
22
+ width: auto;
23
+ height: auto;
24
+ border: none;
25
+ padding: 0;
26
+ background-color: transparent;
27
+ overflow: visible;
28
+
21
29
  /* Use flexbox alignment for the overlay part. */
22
30
  display: flex;
23
31
  flex-direction: column; /* makes dropdowns sizing easier */
@@ -48,11 +48,20 @@ export const OverlayFocusMixin = (superClass) =>
48
48
  constructor() {
49
49
  super();
50
50
 
51
- this.__ariaModalController = new AriaModalController(this);
51
+ this.__ariaModalController = new AriaModalController(this, () => this._modalRoot);
52
52
  this.__focusTrapController = new FocusTrapController(this);
53
53
  this.__focusRestorationController = new FocusRestorationController();
54
54
  }
55
55
 
56
+ /**
57
+ * Override to specify another element used as a content root,
58
+ * e.g. slotted into the overlay, rather than overlay itself.
59
+ * @protected
60
+ */
61
+ get _contentRoot() {
62
+ return this;
63
+ }
64
+
56
65
  /** @protected */
57
66
  ready() {
58
67
  super.ready();
@@ -62,6 +71,15 @@ export const OverlayFocusMixin = (superClass) =>
62
71
  this.addController(this.__focusRestorationController);
63
72
  }
64
73
 
74
+ /**
75
+ * Override to specify another element used as a modality root,
76
+ * e.g. the overlay's owner element, rather than overlay itself.
77
+ * @protected
78
+ */
79
+ get _modalRoot() {
80
+ return this;
81
+ }
82
+
65
83
  /**
66
84
  * Release focus and restore focus after the overlay is closed.
67
85
  *
@@ -127,15 +145,15 @@ export const OverlayFocusMixin = (superClass) =>
127
145
  * @protected
128
146
  */
129
147
  _deepContains(node) {
130
- if (this.contains(node)) {
148
+ if (this._contentRoot.contains(node)) {
131
149
  return true;
132
150
  }
133
151
  let n = node;
134
152
  const doc = node.ownerDocument;
135
- // Walk from node to `this` or `document`
136
- while (n && n !== doc && n !== this) {
153
+ // Walk from node to content root or `document`
154
+ while (n && n !== doc && n !== this._contentRoot) {
137
155
  n = n.parentNode || n.host;
138
156
  }
139
- return n === this;
157
+ return n === this._contentRoot;
140
158
  }
141
159
  };
@@ -6,6 +6,7 @@
6
6
  import { isIOS } from '@vaadin/component-base/src/browser-utils.js';
7
7
  import { OverlayFocusMixin } from './vaadin-overlay-focus-mixin.js';
8
8
  import { OverlayStackMixin } from './vaadin-overlay-stack-mixin.js';
9
+ import { setOverlayStateAttribute } from './vaadin-overlay-utils.js';
9
10
 
10
11
  /**
11
12
  * @polymerMixin
@@ -91,6 +92,7 @@ export const OverlayMixin = (superClass) =>
91
92
  type: Boolean,
92
93
  value: false,
93
94
  reflectToAttribute: true,
95
+ observer: '_withBackdropChanged',
94
96
  sync: true,
95
97
  },
96
98
  };
@@ -115,15 +117,20 @@ export const OverlayMixin = (superClass) =>
115
117
  }
116
118
 
117
119
  /** @protected */
118
- ready() {
119
- super.ready();
120
+ firstUpdated() {
121
+ super.firstUpdated();
122
+
123
+ // Set popover in firstUpdated before opened observers are called
124
+ this.popover = 'manual';
120
125
 
121
126
  // Need to add dummy click listeners to this and the backdrop or else
122
127
  // the document click event listener (_outsideClickListener) may never
123
128
  // get invoked on iOS Safari (reproducible in <vaadin-dialog>
124
129
  // and <vaadin-context-menu>).
125
130
  this.addEventListener('click', () => {});
126
- this.$.backdrop.addEventListener('click', () => {});
131
+ if (this.$.backdrop) {
132
+ this.$.backdrop.addEventListener('click', () => {});
133
+ }
127
134
 
128
135
  this.addEventListener('mouseup', () => {
129
136
  // In Chrome, focus moves to body on overlay content mousedown
@@ -163,7 +170,7 @@ export const OverlayMixin = (superClass) =>
163
170
  */
164
171
  requestContentUpdate() {
165
172
  if (this.renderer) {
166
- this.renderer.call(this.owner, this, this.owner, this.model);
173
+ this.renderer.call(this.owner, this._contentRoot, this.owner, this.model);
167
174
  }
168
175
  }
169
176
 
@@ -171,13 +178,17 @@ export const OverlayMixin = (superClass) =>
171
178
  * @param {Event=} sourceEvent
172
179
  */
173
180
  close(sourceEvent) {
174
- const evt = new CustomEvent('vaadin-overlay-close', {
181
+ // Dispatch the event on the overlay. Not using composed, as propagating the event through shadow roots could have
182
+ // side effects when nesting overlays
183
+ const event = new CustomEvent('vaadin-overlay-close', {
175
184
  bubbles: true,
176
185
  cancelable: true,
177
186
  detail: { sourceEvent },
178
187
  });
179
- this.dispatchEvent(evt);
180
- if (!evt.defaultPrevented) {
188
+ this.dispatchEvent(event);
189
+ // To allow listening for the event globally, also dispatch it on the document body
190
+ document.body.dispatchEvent(event);
191
+ if (!event.defaultPrevented) {
181
192
  this.opened = false;
182
193
  }
183
194
  }
@@ -256,11 +267,11 @@ export const OverlayMixin = (superClass) =>
256
267
  this._oldOpened = opened;
257
268
 
258
269
  if (rendererChanged && hasOldRenderer) {
259
- this.innerHTML = '';
270
+ this._contentRoot.innerHTML = '';
260
271
  // Whenever a Lit-based renderer is used, it assigns a Lit part to the node it was rendered into.
261
272
  // When clearing the rendered content, this part needs to be manually disposed of.
262
273
  // Otherwise, using a Lit-based renderer on the same node will throw an exception or render nothing afterward.
263
- delete this._$litPart$;
274
+ delete this._contentRoot._$litPart$;
264
275
  }
265
276
 
266
277
  if (opened && renderer && (rendererChanged || openedChanged || ownerOrModelChanged)) {
@@ -279,11 +290,23 @@ export const OverlayMixin = (superClass) =>
279
290
  this._removeGlobalListeners();
280
291
  this._exitModalState();
281
292
  }
293
+ setOverlayStateAttribute(this, 'modeless', modeless);
294
+ }
295
+
296
+ /** @private */
297
+ _withBackdropChanged(withBackdrop) {
298
+ setOverlayStateAttribute(this, 'with-backdrop', withBackdrop);
282
299
  }
283
300
 
284
301
  /** @private */
285
302
  _openedChanged(opened, wasOpened) {
286
303
  if (opened) {
304
+ // Prevent possible errors on setting `opened` to `true` while disconnected
305
+ if (!this.isConnected) {
306
+ this.opened = false;
307
+ return;
308
+ }
309
+
287
310
  this._saveFocus();
288
311
 
289
312
  this._animatedOpening();
@@ -292,7 +315,12 @@ export const OverlayMixin = (superClass) =>
292
315
  setTimeout(() => {
293
316
  this._trapFocus();
294
317
 
295
- this.dispatchEvent(new CustomEvent('vaadin-overlay-open', { bubbles: true }));
318
+ // Dispatch the event on the overlay. Not using composed, as propagating the event through shadow roots
319
+ // could have side effects when nesting overlays
320
+ const event = new CustomEvent('vaadin-overlay-open', { bubbles: true });
321
+ this.dispatchEvent(event);
322
+ // To allow listening for the event globally, also dispatch it on the document body
323
+ document.body.dispatchEvent(event);
296
324
  });
297
325
  });
298
326
 
@@ -369,14 +397,16 @@ export const OverlayMixin = (superClass) =>
369
397
 
370
398
  /** @private */
371
399
  _animatedOpening() {
372
- if (this.parentNode === document.body && this.hasAttribute('closing')) {
400
+ if (this._isAttached && this.hasAttribute('closing')) {
373
401
  this._flushAnimation('closing');
374
402
  }
375
403
  this._attachOverlay();
404
+ this._appendAttachedInstance();
405
+ this.bringToFront();
376
406
  if (!this.modeless) {
377
407
  this._enterModalState();
378
408
  }
379
- this.setAttribute('opening', '');
409
+ setOverlayStateAttribute(this, 'opening', true);
380
410
 
381
411
  if (this._shouldAnimate()) {
382
412
  this._enqueueAnimation('opening', () => {
@@ -389,22 +419,20 @@ export const OverlayMixin = (superClass) =>
389
419
 
390
420
  /** @private */
391
421
  _attachOverlay() {
392
- this._placeholder = document.createComment('vaadin-overlay-placeholder');
393
- this.parentNode.insertBefore(this._placeholder, this);
394
- document.body.appendChild(this);
395
- this.bringToFront();
422
+ this.showPopover();
396
423
  }
397
424
 
398
425
  /** @private */
399
426
  _finishOpening() {
400
- this.removeAttribute('opening');
427
+ setOverlayStateAttribute(this, 'opening', false);
401
428
  }
402
429
 
403
430
  /** @private */
404
431
  _finishClosing() {
405
432
  this._detachOverlay();
433
+ this._removeAttachedInstance();
406
434
  this.$.overlay.style.removeProperty('pointer-events');
407
- this.removeAttribute('closing');
435
+ setOverlayStateAttribute(this, 'closing', false);
408
436
  this.dispatchEvent(new CustomEvent('vaadin-overlay-closed'));
409
437
  }
410
438
 
@@ -413,9 +441,9 @@ export const OverlayMixin = (superClass) =>
413
441
  if (this.hasAttribute('opening')) {
414
442
  this._flushAnimation('opening');
415
443
  }
416
- if (this._placeholder) {
444
+ if (this._isAttached) {
417
445
  this._exitModalState();
418
- this.setAttribute('closing', '');
446
+ setOverlayStateAttribute(this, 'closing', true);
419
447
  this.dispatchEvent(new CustomEvent('vaadin-overlay-closing'));
420
448
 
421
449
  if (this._shouldAnimate()) {
@@ -430,8 +458,7 @@ export const OverlayMixin = (superClass) =>
430
458
 
431
459
  /** @private */
432
460
  _detachOverlay() {
433
- this._placeholder.parentNode.insertBefore(this, this._placeholder);
434
- this._placeholder.parentNode.removeChild(this._placeholder);
461
+ this.hidePopover();
435
462
  }
436
463
 
437
464
  /** @private */
@@ -475,7 +502,6 @@ export const OverlayMixin = (superClass) =>
475
502
  }
476
503
 
477
504
  const evt = new CustomEvent('vaadin-overlay-outside-click', {
478
- bubbles: true,
479
505
  cancelable: true,
480
506
  detail: { sourceEvent: event },
481
507
  });
@@ -502,7 +528,6 @@ export const OverlayMixin = (superClass) =>
502
528
 
503
529
  if (event.key === 'Escape') {
504
530
  const evt = new CustomEvent('vaadin-overlay-escape-press', {
505
- bubbles: true,
506
531
  cancelable: true,
507
532
  detail: { sourceEvent: event },
508
533
  });
@@ -4,7 +4,7 @@
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { getAncestorRootNodes } from '@vaadin/component-base/src/dom-utils.js';
7
- import { observeMove } from './vaadin-overlay-utils.js';
7
+ import { observeMove, setOverlayStateAttribute } from './vaadin-overlay-utils.js';
8
8
 
9
9
  const PROP_NAMES_VERTICAL = {
10
10
  start: 'top',
@@ -271,11 +271,11 @@ export const PositionMixin = (superClass) =>
271
271
  // Apply the positioning properties to the overlay
272
272
  Object.assign(this.style, verticalProps, horizontalProps);
273
273
 
274
- this.toggleAttribute('bottom-aligned', !shouldAlignStartVertically);
275
- this.toggleAttribute('top-aligned', shouldAlignStartVertically);
274
+ setOverlayStateAttribute(this, 'bottom-aligned', !shouldAlignStartVertically);
275
+ setOverlayStateAttribute(this, 'top-aligned', shouldAlignStartVertically);
276
276
 
277
- this.toggleAttribute('end-aligned', !flexStart);
278
- this.toggleAttribute('start-aligned', flexStart);
277
+ setOverlayStateAttribute(this, 'end-aligned', !flexStart);
278
+ setOverlayStateAttribute(this, 'start-aligned', flexStart);
279
279
  }
280
280
 
281
281
  __shouldAlignStartHorizontally(targetRect, rtl) {
@@ -4,21 +4,32 @@
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
 
7
+ /** @type {Set<HTMLElement>} */
8
+ const attachedInstances = new Set();
9
+
7
10
  /**
8
11
  * Returns all attached overlays in visual stacking order.
9
12
  * @private
10
13
  */
11
- const getAttachedInstances = () =>
12
- Array.from(document.body.children)
13
- .filter((el) => el instanceof HTMLElement && el._hasOverlayStackMixin && !el.hasAttribute('closing'))
14
- .sort((a, b) => a.__zIndex - b.__zIndex || 0);
14
+ const getAttachedInstances = () => [...attachedInstances].filter((el) => !el.hasAttribute('closing'));
15
15
 
16
16
  /**
17
- * Returns all attached overlay instances excluding notification container,
18
- * which only needs to be in the stack for zIndex but not pointer-events.
17
+ * Returns true if all the instances on top of the overlay are nested overlays.
19
18
  * @private
20
19
  */
21
- const getOverlayInstances = () => getAttachedInstances().filter((el) => el.$.overlay);
20
+ const hasOnlyNestedOverlays = (overlay) => {
21
+ const instances = getAttachedInstances();
22
+ const next = instances[instances.indexOf(overlay) + 1];
23
+ if (!next) {
24
+ return true;
25
+ }
26
+
27
+ if (!overlay._deepContains(next)) {
28
+ return false;
29
+ }
30
+
31
+ return hasOnlyNestedOverlays(next);
32
+ };
22
33
 
23
34
  /**
24
35
  * Returns true if the overlay is the last one in the opened overlays stack.
@@ -28,38 +39,15 @@ const getOverlayInstances = () => getAttachedInstances().filter((el) => el.$.ove
28
39
  * @protected
29
40
  */
30
41
  export const isLastOverlay = (overlay, filter = (_overlay) => true) => {
31
- const filteredOverlays = getOverlayInstances().filter(filter);
42
+ const filteredOverlays = getAttachedInstances().filter(filter);
32
43
  return overlay === filteredOverlays.pop();
33
44
  };
34
45
 
35
- const overlayMap = new WeakMap();
36
-
37
- /**
38
- * Stores the reference to the nested overlay for given parent,
39
- * or removes it when the nested overlay is null.
40
- * @param {HTMLElement} parent
41
- * @param {HTMLElement} nested
42
- * @protected
43
- */
44
- export const setNestedOverlay = (parent, nested) => {
45
- if (nested != null) {
46
- overlayMap.set(parent, nested);
47
- } else {
48
- overlayMap.delete(parent);
49
- }
50
- };
51
-
52
46
  /**
53
47
  * @polymerMixin
54
48
  */
55
49
  export const OverlayStackMixin = (superClass) =>
56
50
  class OverlayStackMixin extends superClass {
57
- constructor() {
58
- super();
59
-
60
- this._hasOverlayStackMixin = true;
61
- }
62
-
63
51
  /**
64
52
  * Returns true if this is the last one in the opened overlays stack.
65
53
  *
@@ -70,25 +58,36 @@ export const OverlayStackMixin = (superClass) =>
70
58
  return isLastOverlay(this);
71
59
  }
72
60
 
61
+ /**
62
+ * Returns true if this is overlay is attached.
63
+ *
64
+ * @return {boolean}
65
+ * @protected
66
+ */
67
+ get _isAttached() {
68
+ return attachedInstances.has(this);
69
+ }
70
+
73
71
  /**
74
72
  * Brings the overlay as visually the frontmost one.
75
73
  */
76
74
  bringToFront() {
77
- let zIndex = '';
78
- const frontmost = getAttachedInstances()
79
- .filter((o) => o !== this)
80
- .pop();
81
- if (frontmost) {
82
- const frontmostZIndex = frontmost.__zIndex;
83
- zIndex = frontmostZIndex + 1;
75
+ // If the overlay is the last one, or if all other overlays shown above
76
+ // are nested overlays (e.g. date-picker inside a dialog), do not call
77
+ // `showPopover()` unnecessarily to avoid scroll position being reset.
78
+ if (isLastOverlay(this) || hasOnlyNestedOverlays(this)) {
79
+ return;
84
80
  }
85
- this.style.zIndex = zIndex;
86
- this.__zIndex = zIndex || parseFloat(getComputedStyle(this).zIndex);
87
81
 
88
- // If there is a nested overlay, call `bringToFront()` for it as well.
89
- if (overlayMap.has(this)) {
90
- overlayMap.get(this).bringToFront();
82
+ // Update stacking order of native popover-based overlays
83
+ if (this.matches(':popover-open')) {
84
+ this.hidePopover();
85
+ this.showPopover();
91
86
  }
87
+
88
+ // Update order of attached instances
89
+ this._removeAttachedInstance();
90
+ this._appendAttachedInstance();
92
91
  }
93
92
 
94
93
  /** @protected */
@@ -101,7 +100,7 @@ export const OverlayStackMixin = (superClass) =>
101
100
  }
102
101
 
103
102
  // Disable pointer events in other attached overlays
104
- getOverlayInstances().forEach((el) => {
103
+ getAttachedInstances().forEach((el) => {
105
104
  if (el !== this) {
106
105
  el.$.overlay.style.pointerEvents = 'none';
107
106
  }
@@ -117,7 +116,7 @@ export const OverlayStackMixin = (superClass) =>
117
116
  }
118
117
 
119
118
  // Restore pointer events in the previous overlay(s)
120
- const instances = getOverlayInstances();
119
+ const instances = getAttachedInstances();
121
120
 
122
121
  let el;
123
122
  // Use instances.pop() to ensure the reverse order
@@ -133,4 +132,16 @@ export const OverlayStackMixin = (superClass) =>
133
132
  }
134
133
  }
135
134
  }
135
+
136
+ /** @protected */
137
+ _appendAttachedInstance() {
138
+ attachedInstances.add(this);
139
+ }
140
+
141
+ /** @protected */
142
+ _removeAttachedInstance() {
143
+ if (this._isAttached) {
144
+ attachedInstances.delete(this);
145
+ }
146
+ }
136
147
  };
@@ -11,3 +11,9 @@
11
11
  * https://github.com/floating-ui/floating-ui/blob/58ed169/packages/dom/src/autoUpdate.ts#L45
12
12
  */
13
13
  export function observeMove(element: HTMLElement, callback: () => void): () => void;
14
+
15
+ /**
16
+ * Toggle the state attribute on the overlay element and also its owner element. This allows targeting state attributes
17
+ * in the light DOM in case the overlay is in the shadow DOM of its owner.
18
+ */
19
+ export function setOverlayStateAttribute(overlay: HTMLElement, name: string, value: string | boolean): void;
@@ -87,3 +87,32 @@ export function observeMove(element, callback) {
87
87
 
88
88
  return cleanup;
89
89
  }
90
+
91
+ /**
92
+ * Toggle the state attribute on the overlay element and also its owner element. This allows targeting state attributes
93
+ * in the light DOM in case the overlay is in the shadow DOM of its owner.
94
+ * @param {HTMLElement} overlay The overlay element on which to toggle the attribute.
95
+ * @param {string} name The name of the attribute to toggle.
96
+ * @param {string|boolean} value The value of the attribute. If a string is provided, it will be set as the attribute
97
+ * value. Otherwise, the attribute will be added or removed depending on whether `value` is truthy or falsy.
98
+ */
99
+ export function setOverlayStateAttribute(overlay, name, value) {
100
+ const elements = [overlay];
101
+ if (overlay.owner) {
102
+ elements.push(overlay.owner);
103
+ }
104
+
105
+ if (typeof value === 'string') {
106
+ elements.forEach((element) => {
107
+ element.setAttribute(name, value);
108
+ });
109
+ } else if (value) {
110
+ elements.forEach((element) => {
111
+ element.setAttribute(name, '');
112
+ });
113
+ } else {
114
+ elements.forEach((element) => {
115
+ element.removeAttribute(name);
116
+ });
117
+ }
118
+ }
@@ -77,7 +77,7 @@ import { OverlayMixin } from './vaadin-overlay-mixin.js';
77
77
  * @mixes DirMixin
78
78
  * @mixes OverlayMixin
79
79
  */
80
- class Overlay extends OverlayMixin(DirMixin(ThemableMixin(LumoInjectionMixin(PolylitMixin(LitElement))))) {
80
+ class Overlay extends OverlayMixin(DirMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(LitElement))))) {
81
81
  static get is() {
82
82
  return 'vaadin-overlay';
83
83
  }