@vaadin/overlay 25.0.0-alpha8 → 25.0.0-beta1

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-alpha8",
3
+ "version": "25.0.0-beta1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -21,9 +21,6 @@
21
21
  "type": "module",
22
22
  "files": [
23
23
  "src",
24
- "!src/styles/*-base-styles.d.ts",
25
- "!src/styles/*-base-styles.js",
26
- "theme",
27
24
  "vaadin-*.d.ts",
28
25
  "vaadin-*.js"
29
26
  ],
@@ -36,17 +33,17 @@
36
33
  ],
37
34
  "dependencies": {
38
35
  "@open-wc/dedupe-mixin": "^1.3.0",
39
- "@vaadin/a11y-base": "25.0.0-alpha8",
40
- "@vaadin/component-base": "25.0.0-alpha8",
41
- "@vaadin/vaadin-lumo-styles": "25.0.0-alpha8",
42
- "@vaadin/vaadin-themable-mixin": "25.0.0-alpha8",
36
+ "@vaadin/a11y-base": "25.0.0-beta1",
37
+ "@vaadin/component-base": "25.0.0-beta1",
38
+ "@vaadin/vaadin-themable-mixin": "25.0.0-beta1",
43
39
  "lit": "^3.0.0"
44
40
  },
45
41
  "devDependencies": {
46
- "@vaadin/chai-plugins": "25.0.0-alpha8",
47
- "@vaadin/test-runner-commands": "25.0.0-alpha8",
42
+ "@vaadin/chai-plugins": "25.0.0-beta1",
43
+ "@vaadin/test-runner-commands": "25.0.0-beta1",
48
44
  "@vaadin/testing-helpers": "^2.0.0",
49
- "sinon": "^18.0.0"
45
+ "@vaadin/vaadin-lumo-styles": "25.0.0-beta1",
46
+ "sinon": "^21.0.0"
50
47
  },
51
- "gitHead": "ebf53673d5f639d2b1b6f2b31f640f530643ee2f"
48
+ "gitHead": "1d20cf54e582d1f2e209126d4586f8b4c01c50e0"
52
49
  }
@@ -3,7 +3,7 @@
3
3
  * Copyright (c) 2017 - 2025 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
- import '@vaadin/component-base/src/style-props.js';
6
+ import '@vaadin/component-base/src/styles/style-props.js';
7
7
  import { css } from 'lit';
8
8
 
9
9
  export const overlayStyles = css`
@@ -16,7 +16,7 @@ export const overlayStyles = css`
16
16
 
17
17
  /* Default position constraints. Themes can
18
18
  override this to adjust the gap between the overlay and the viewport. */
19
- inset: 8px;
19
+ inset: var(--vaadin-overlay-viewport-inset, 8px);
20
20
  bottom: var(--vaadin-overlay-viewport-bottom);
21
21
 
22
22
  /* Override native [popover] user agent styles */
@@ -55,19 +55,32 @@ export const overlayStyles = css`
55
55
 
56
56
  [part='overlay'] {
57
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));
58
+ border: var(--vaadin-overlay-border-width, 1px) solid
59
+ var(--vaadin-overlay-border-color, var(--vaadin-border-color-secondary));
59
60
  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-shadow: var(--vaadin-overlay-shadow, 0 8px 24px -4px rgba(0, 0, 0, 0.3));
61
62
  box-sizing: border-box;
62
63
  max-width: 100%;
63
64
  overflow: auto;
64
65
  overscroll-behavior: contain;
65
66
  pointer-events: auto;
66
67
  -webkit-tap-highlight-color: initial;
68
+
69
+ /* CSS reset for font styles */
70
+ color: initial;
71
+ font: initial;
72
+ letter-spacing: initial;
73
+ text-align: initial;
74
+ text-decoration: initial;
75
+ text-indent: initial;
76
+ text-transform: initial;
77
+ user-select: text;
78
+ white-space: initial;
79
+ word-spacing: initial;
67
80
  }
68
81
 
69
82
  [part='backdrop'] {
70
- background: var(--vaadin-overlay-backdrop-background, rgba(0, 0, 0, 0.5));
83
+ background: var(--vaadin-overlay-backdrop-background, rgba(0, 0, 0, 0.2));
71
84
  content: '';
72
85
  inset: 0;
73
86
  pointer-events: auto;
@@ -81,7 +94,7 @@ export const overlayStyles = css`
81
94
 
82
95
  @media (forced-colors: active) {
83
96
  [part='overlay'] {
84
- border: 3px solid;
97
+ border: 3px solid !important;
85
98
  }
86
99
  }
87
100
  `;
@@ -3,7 +3,6 @@
3
3
  * Copyright (c) 2017 - 2025 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
- import { AriaModalController } from '@vaadin/a11y-base/src/aria-modal-controller.js';
7
6
  import { FocusRestorationController } from '@vaadin/a11y-base/src/focus-restoration-controller.js';
8
7
  import { FocusTrapController } from '@vaadin/a11y-base/src/focus-trap-controller.js';
9
8
  import { getDeepActiveElement, isKeyboardActive } from '@vaadin/a11y-base/src/focus-utils.js';
@@ -48,7 +47,6 @@ export const OverlayFocusMixin = (superClass) =>
48
47
  constructor() {
49
48
  super();
50
49
 
51
- this.__ariaModalController = new AriaModalController(this, () => this._modalRoot);
52
50
  this.__focusTrapController = new FocusTrapController(this);
53
51
  this.__focusRestorationController = new FocusRestorationController();
54
52
  }
@@ -66,18 +64,17 @@ export const OverlayFocusMixin = (superClass) =>
66
64
  ready() {
67
65
  super.ready();
68
66
 
69
- this.addController(this.__ariaModalController);
70
67
  this.addController(this.__focusTrapController);
71
68
  this.addController(this.__focusRestorationController);
72
69
  }
73
70
 
74
71
  /**
75
- * Override to specify another element used as a modality root,
76
- * e.g. the overlay's owner element, rather than overlay itself.
72
+ * Override to specify another element used as a focus trap root,
73
+ * e.g. the overlay's owner element, rather than overlay part.
77
74
  * @protected
78
75
  */
79
- get _modalRoot() {
80
- return this;
76
+ get _focusTrapRoot() {
77
+ return this.$.overlay;
81
78
  }
82
79
 
83
80
  /**
@@ -87,13 +84,13 @@ export const OverlayFocusMixin = (superClass) =>
87
84
  */
88
85
  _resetFocus() {
89
86
  if (this.focusTrap) {
90
- this.__ariaModalController.close();
91
87
  this.__focusTrapController.releaseFocus();
92
88
  }
93
89
 
94
90
  if (this.restoreFocusOnClose && this._shouldRestoreFocus()) {
95
- const preventScroll = !isKeyboardActive();
96
- this.__focusRestorationController.restoreFocus({ preventScroll });
91
+ const focusVisible = isKeyboardActive();
92
+ const preventScroll = !focusVisible;
93
+ this.__focusRestorationController.restoreFocus({ preventScroll, focusVisible });
97
94
  }
98
95
  }
99
96
 
@@ -115,8 +112,7 @@ export const OverlayFocusMixin = (superClass) =>
115
112
  */
116
113
  _trapFocus() {
117
114
  if (this.focusTrap) {
118
- this.__ariaModalController.showModal();
119
- this.__focusTrapController.trapFocus(this.$.overlay);
115
+ this.__focusTrapController.trapFocus(this._focusTrapRoot);
120
116
  }
121
117
  }
122
118
 
@@ -117,8 +117,11 @@ export const OverlayMixin = (superClass) =>
117
117
  }
118
118
 
119
119
  /** @protected */
120
- ready() {
121
- super.ready();
120
+ firstUpdated() {
121
+ super.firstUpdated();
122
+
123
+ // Set popover in firstUpdated before opened observers are called
124
+ this.popover = 'manual';
122
125
 
123
126
  // Need to add dummy click listeners to this and the backdrop or else
124
127
  // the document click event listener (_outsideClickListener) may never
@@ -153,6 +156,11 @@ export const OverlayMixin = (superClass) =>
153
156
  disconnectedCallback() {
154
157
  super.disconnectedCallback();
155
158
 
159
+ if (this.__scheduledOpen) {
160
+ cancelAnimationFrame(this.__scheduledOpen);
161
+ this.__scheduledOpen = null;
162
+ }
163
+
156
164
  /* c8 ignore next 3 */
157
165
  if (this._boundIosResizeListener) {
158
166
  window.removeEventListener('resize', this._boundIosResizeListener);
@@ -175,13 +183,17 @@ export const OverlayMixin = (superClass) =>
175
183
  * @param {Event=} sourceEvent
176
184
  */
177
185
  close(sourceEvent) {
178
- const evt = new CustomEvent('vaadin-overlay-close', {
186
+ // Dispatch the event on the overlay. Not using composed, as propagating the event through shadow roots could have
187
+ // side effects when nesting overlays
188
+ const event = new CustomEvent('vaadin-overlay-close', {
179
189
  bubbles: true,
180
190
  cancelable: true,
181
- detail: { sourceEvent },
191
+ detail: { overlay: this, sourceEvent },
182
192
  });
183
- this.dispatchEvent(evt);
184
- if (!evt.defaultPrevented) {
193
+ this.dispatchEvent(event);
194
+ // To allow listening for the event globally, also dispatch it on the document body
195
+ document.body.dispatchEvent(event);
196
+ if (!event.defaultPrevented) {
185
197
  this.opened = false;
186
198
  }
187
199
  }
@@ -230,8 +242,25 @@ export const OverlayMixin = (superClass) =>
230
242
  }
231
243
  }
232
244
 
245
+ /**
246
+ * Whether to add global listeners for closing on outside click.
247
+ * By default, listeners are not added for a modeless overlay.
248
+ *
249
+ * @return {boolean}
250
+ * @protected
251
+ */
252
+ _shouldAddGlobalListeners() {
253
+ return !this.modeless;
254
+ }
255
+
233
256
  /** @private */
234
257
  _addGlobalListeners() {
258
+ if (this.__hasGlobalListeners) {
259
+ return;
260
+ }
261
+
262
+ this.__hasGlobalListeners = true;
263
+
235
264
  document.addEventListener('mousedown', this._boundMouseDownListener);
236
265
  document.addEventListener('mouseup', this._boundMouseUpListener);
237
266
  // Firefox leaks click to document on contextmenu even if prevented
@@ -241,6 +270,12 @@ export const OverlayMixin = (superClass) =>
241
270
 
242
271
  /** @private */
243
272
  _removeGlobalListeners() {
273
+ if (!this.__hasGlobalListeners) {
274
+ return;
275
+ }
276
+
277
+ this.__hasGlobalListeners = false;
278
+
244
279
  document.removeEventListener('mousedown', this._boundMouseDownListener);
245
280
  document.removeEventListener('mouseup', this._boundMouseUpListener);
246
281
  document.documentElement.removeEventListener('click', this._boundOutsideClickListener, true);
@@ -274,13 +309,20 @@ export const OverlayMixin = (superClass) =>
274
309
 
275
310
  /** @private */
276
311
  _modelessChanged(modeless) {
312
+ if (this.opened) {
313
+ // Add / remove listeners if modeless is changed while opened
314
+ if (this._shouldAddGlobalListeners()) {
315
+ this._addGlobalListeners();
316
+ } else {
317
+ this._removeGlobalListeners();
318
+ }
319
+ }
320
+
277
321
  if (!modeless) {
278
322
  if (this.opened) {
279
- this._addGlobalListeners();
280
323
  this._enterModalState();
281
324
  }
282
325
  } else {
283
- this._removeGlobalListeners();
284
326
  this._exitModalState();
285
327
  }
286
328
  setOverlayStateAttribute(this, 'modeless', modeless);
@@ -308,13 +350,18 @@ export const OverlayMixin = (superClass) =>
308
350
  setTimeout(() => {
309
351
  this._trapFocus();
310
352
 
311
- this.dispatchEvent(new CustomEvent('vaadin-overlay-open', { bubbles: true, composed: true }));
353
+ // Dispatch the event on the overlay. Not using composed, as propagating the event through shadow roots
354
+ // could have side effects when nesting overlays
355
+ const event = new CustomEvent('vaadin-overlay-open', { detail: { overlay: this }, bubbles: true });
356
+ this.dispatchEvent(event);
357
+ // To allow listening for the event globally, also dispatch it on the document body
358
+ document.body.dispatchEvent(event);
312
359
  });
313
360
  });
314
361
 
315
362
  document.addEventListener('keydown', this._boundKeydownListener);
316
363
 
317
- if (!this.modeless) {
364
+ if (this._shouldAddGlobalListeners()) {
318
365
  this._addGlobalListeners();
319
366
  }
320
367
  } else if (wasOpened) {
@@ -329,7 +376,7 @@ export const OverlayMixin = (superClass) =>
329
376
 
330
377
  document.removeEventListener('keydown', this._boundKeydownListener);
331
378
 
332
- if (!this.modeless) {
379
+ if (this._shouldAddGlobalListeners()) {
333
380
  this._removeGlobalListeners();
334
381
  }
335
382
  }
@@ -407,9 +454,7 @@ export const OverlayMixin = (superClass) =>
407
454
 
408
455
  /** @private */
409
456
  _attachOverlay() {
410
- this._placeholder = document.createComment('vaadin-overlay-placeholder');
411
- this.parentNode.insertBefore(this._placeholder, this);
412
- document.body.appendChild(this);
457
+ this.showPopover();
413
458
  }
414
459
 
415
460
  /** @private */
@@ -448,8 +493,7 @@ export const OverlayMixin = (superClass) =>
448
493
 
449
494
  /** @private */
450
495
  _detachOverlay() {
451
- this._placeholder.parentNode.insertBefore(this, this._placeholder);
452
- this._placeholder.parentNode.removeChild(this._placeholder);
496
+ this.hidePopover();
453
497
  }
454
498
 
455
499
  /** @private */
@@ -493,7 +537,6 @@ export const OverlayMixin = (superClass) =>
493
537
  }
494
538
 
495
539
  const evt = new CustomEvent('vaadin-overlay-outside-click', {
496
- bubbles: true,
497
540
  cancelable: true,
498
541
  detail: { sourceEvent: event },
499
542
  });
@@ -514,13 +557,12 @@ export const OverlayMixin = (superClass) =>
514
557
  }
515
558
 
516
559
  // Only close modeless overlay on Esc press when it contains focus
517
- if (this.modeless && !event.composedPath().includes(this.$.overlay)) {
560
+ if (!this._shouldAddGlobalListeners() && !event.composedPath().includes(this._focusTrapRoot)) {
518
561
  return;
519
562
  }
520
563
 
521
564
  if (event.key === 'Escape') {
522
565
  const evt = new CustomEvent('vaadin-overlay-escape-press', {
523
- bubbles: true,
524
566
  cancelable: true,
525
567
  detail: { sourceEvent: event },
526
568
  });
@@ -116,13 +116,6 @@ export const PositionMixin = (superClass) =>
116
116
  };
117
117
  }
118
118
 
119
- static get observers() {
120
- return [
121
- '__positionSettingsChanged(horizontalAlign, verticalAlign, noHorizontalOverlap, noVerticalOverlap, requiredVerticalSpace)',
122
- '__overlayOpenedChanged(opened, positionTarget)',
123
- ];
124
- }
125
-
126
119
  constructor() {
127
120
  super();
128
121
 
@@ -145,6 +138,36 @@ export const PositionMixin = (superClass) =>
145
138
  this.__removeUpdatePositionEventListeners();
146
139
  }
147
140
 
141
+ /** @protected */
142
+ updated(props) {
143
+ super.updated(props);
144
+
145
+ if (props.has('positionTarget')) {
146
+ const oldTarget = props.get('positionTarget');
147
+
148
+ // 1. When position target is removed, always reset position settings
149
+ // 2. When position target is set, reset if overlay was opened before
150
+ if ((!this.positionTarget && oldTarget) || (this.positionTarget && !oldTarget && !!this.__margins)) {
151
+ this.__resetPosition();
152
+ }
153
+ }
154
+
155
+ if (props.has('opened') || props.has('positionTarget')) {
156
+ this.__updatePositionSettings(this.opened, this.positionTarget);
157
+ }
158
+
159
+ const positionProps = [
160
+ 'horizontalAlign',
161
+ 'verticalAlign',
162
+ 'noHorizontalOverlap',
163
+ 'noVerticalOverlap',
164
+ 'requiredVerticalSpace',
165
+ ];
166
+ if (positionProps.some((prop) => props.has(prop))) {
167
+ this._updatePosition();
168
+ }
169
+ }
170
+
148
171
  /** @private */
149
172
  __addUpdatePositionEventListeners() {
150
173
  window.visualViewport.addEventListener('resize', this._updatePosition);
@@ -181,7 +204,7 @@ export const PositionMixin = (superClass) =>
181
204
  }
182
205
 
183
206
  /** @private */
184
- __overlayOpenedChanged(opened, positionTarget) {
207
+ __updatePositionSettings(opened, positionTarget) {
185
208
  this.__removeUpdatePositionEventListeners();
186
209
 
187
210
  if (positionTarget) {
@@ -210,20 +233,35 @@ export const PositionMixin = (superClass) =>
210
233
  }
211
234
  }
212
235
 
213
- __positionSettingsChanged() {
214
- this._updatePosition();
215
- }
216
-
217
236
  /** @private */
218
237
  __onScroll(e) {
219
238
  // If the scroll event occurred inside the overlay, ignore it.
220
- if (e.target instanceof Node && this.contains(e.target)) {
239
+ if (e.target instanceof Node && this._deepContains(e.target)) {
221
240
  return;
222
241
  }
223
242
 
224
243
  this._updatePosition();
225
244
  }
226
245
 
246
+ /** @private */
247
+ __resetPosition() {
248
+ this.__margins = null;
249
+
250
+ Object.assign(this.style, {
251
+ justifyContent: '',
252
+ alignItems: '',
253
+ top: '',
254
+ bottom: '',
255
+ left: '',
256
+ right: '',
257
+ });
258
+
259
+ setOverlayStateAttribute(this, 'bottom-aligned', false);
260
+ setOverlayStateAttribute(this, 'top-aligned', false);
261
+ setOverlayStateAttribute(this, 'end-aligned', false);
262
+ setOverlayStateAttribute(this, 'start-aligned', false);
263
+ }
264
+
227
265
  _updatePosition() {
228
266
  if (!this.positionTarget || !this.opened || !this.__margins) {
229
267
  return;
@@ -11,23 +11,13 @@ const attachedInstances = new Set();
11
11
  * Returns all attached overlays in visual stacking order.
12
12
  * @private
13
13
  */
14
- const getAttachedInstances = () =>
15
- [...attachedInstances].filter(
16
- (el) => el instanceof HTMLElement && el._hasOverlayStackMixin && !el.hasAttribute('closing'),
17
- );
18
-
19
- /**
20
- * Returns all attached overlay instances excluding notification container,
21
- * which only needs to be in the stack for zIndex but not pointer-events.
22
- * @private
23
- */
24
- const getOverlayInstances = () => getAttachedInstances().filter((el) => el.$.overlay);
14
+ const getAttachedInstances = () => [...attachedInstances].filter((el) => !el.hasAttribute('closing'));
25
15
 
26
16
  /**
27
17
  * Returns true if all the instances on top of the overlay are nested overlays.
28
18
  * @private
29
19
  */
30
- const hasOnlyNestedOverlays = (overlay) => {
20
+ export const hasOnlyNestedOverlays = (overlay) => {
31
21
  const instances = getAttachedInstances();
32
22
  const next = instances[instances.indexOf(overlay) + 1];
33
23
  if (!next) {
@@ -49,38 +39,15 @@ const hasOnlyNestedOverlays = (overlay) => {
49
39
  * @protected
50
40
  */
51
41
  export const isLastOverlay = (overlay, filter = (_overlay) => true) => {
52
- const filteredOverlays = getOverlayInstances().filter(filter);
42
+ const filteredOverlays = getAttachedInstances().filter(filter);
53
43
  return overlay === filteredOverlays.pop();
54
44
  };
55
45
 
56
- const overlayMap = new WeakMap();
57
-
58
- /**
59
- * Stores the reference to the nested overlay for given parent,
60
- * or removes it when the nested overlay is null.
61
- * @param {HTMLElement} parent
62
- * @param {HTMLElement} nested
63
- * @protected
64
- */
65
- export const setNestedOverlay = (parent, nested) => {
66
- if (nested != null) {
67
- overlayMap.set(parent, nested);
68
- } else {
69
- overlayMap.delete(parent);
70
- }
71
- };
72
-
73
46
  /**
74
47
  * @polymerMixin
75
48
  */
76
49
  export const OverlayStackMixin = (superClass) =>
77
50
  class OverlayStackMixin extends superClass {
78
- constructor() {
79
- super();
80
-
81
- this._hasOverlayStackMixin = true;
82
- }
83
-
84
51
  /**
85
52
  * Returns true if this is the last one in the opened overlays stack.
86
53
  *
@@ -105,18 +72,6 @@ export const OverlayStackMixin = (superClass) =>
105
72
  * Brings the overlay as visually the frontmost one.
106
73
  */
107
74
  bringToFront() {
108
- // Update z-index to be the highest among all attached overlays
109
- // TODO: Can be removed after switching all overlays to be based on native popover
110
- let zIndex = '';
111
- const frontmost = getAttachedInstances()
112
- .filter((o) => o !== this)
113
- .pop();
114
- if (frontmost) {
115
- const frontmostZIndex = parseFloat(getComputedStyle(frontmost).zIndex);
116
- zIndex = frontmostZIndex + 1;
117
- }
118
- this.style.zIndex = zIndex;
119
-
120
75
  // If the overlay is the last one, or if all other overlays shown above
121
76
  // are nested overlays (e.g. date-picker inside a dialog), do not call
122
77
  // `showPopover()` unnecessarily to avoid scroll position being reset.
@@ -133,11 +88,6 @@ export const OverlayStackMixin = (superClass) =>
133
88
  // Update order of attached instances
134
89
  this._removeAttachedInstance();
135
90
  this._appendAttachedInstance();
136
-
137
- // If there is a nested overlay, call `bringToFront()` for it as well.
138
- if (overlayMap.has(this)) {
139
- overlayMap.get(this).bringToFront();
140
- }
141
91
  }
142
92
 
143
93
  /** @protected */
@@ -150,7 +100,7 @@ export const OverlayStackMixin = (superClass) =>
150
100
  }
151
101
 
152
102
  // Disable pointer events in other attached overlays
153
- getOverlayInstances().forEach((el) => {
103
+ getAttachedInstances().forEach((el) => {
154
104
  if (el !== this) {
155
105
  el.$.overlay.style.pointerEvents = 'none';
156
106
  }
@@ -166,7 +116,7 @@ export const OverlayStackMixin = (superClass) =>
166
116
  }
167
117
 
168
118
  // Restore pointer events in the previous overlay(s)
169
- const instances = getOverlayInstances();
119
+ const instances = getAttachedInstances();
170
120
 
171
121
  let el;
172
122
  // Use instances.pop() to ensure the reverse order
@@ -16,10 +16,12 @@
16
16
  */
17
17
  export function observeMove(element, callback) {
18
18
  let io = null;
19
+ let timeout;
19
20
 
20
21
  const root = document.documentElement;
21
22
 
22
23
  function cleanup() {
24
+ timeout && clearTimeout(timeout);
23
25
  io && io.disconnect();
24
26
  io = null;
25
27
  }
@@ -52,27 +54,22 @@ export function observeMove(element, callback) {
52
54
  let isFirstUpdate = true;
53
55
 
54
56
  function handleObserve(entries) {
55
- let ratio = entries[0].intersectionRatio;
57
+ const ratio = entries[0].intersectionRatio;
56
58
 
57
59
  if (ratio !== threshold) {
58
60
  if (!isFirstUpdate) {
59
61
  return refresh();
60
62
  }
61
63
 
62
- // It's possible for the watched element to not be at perfect 1.0 visibility when we create
63
- // the IntersectionObserver. This has a couple of causes:
64
- // - elements being on partial pixels
65
- // - elements being hidden offscreen (e.g., <html> has `overflow: hidden`)
66
- // - delays: if your DOM change occurs due to e.g., page resize, you can see elements
67
- // behind their actual position
68
- //
69
- // In all of these cases, refresh but with this lower ratio of threshold. When the element
70
- // moves beneath _that_ new value, the user will get notified.
71
- if (ratio === 0.0) {
72
- ratio = 0.0000001; // Just needs to be non-zero
64
+ if (!ratio) {
65
+ // If the reference is clipped, the ratio is 0. Throttle the refresh
66
+ // to prevent an infinite loop of updates.
67
+ timeout = setTimeout(() => {
68
+ refresh(false, 1e-7);
69
+ }, 1000);
70
+ } else {
71
+ refresh(false, ratio);
73
72
  }
74
-
75
- refresh(false, ratio);
76
73
  }
77
74
 
78
75
  isFirstUpdate = false;
@@ -17,13 +17,13 @@ export type OverlayOpenedChangedEvent = CustomEvent<{ value: boolean }>;
17
17
  /**
18
18
  * Fired after the overlay is opened.
19
19
  */
20
- export type OverlayOpenEvent = CustomEvent;
20
+ export type OverlayOpenEvent = CustomEvent<{ overlay: HTMLElement }>;
21
21
 
22
22
  /**
23
23
  * Fired when the opened overlay is about to be closed.
24
24
  * Calling `preventDefault()` on the event cancels the closing.
25
25
  */
26
- export type OverlayCloseEvent = CustomEvent;
26
+ export type OverlayCloseEvent = CustomEvent<{ overlay: HTMLElement }>;
27
27
 
28
28
  /**
29
29
  * Fired after the overlay is closed.
@@ -98,10 +98,10 @@ export type OverlayEventMap = HTMLElementEventMap & OverlayCustomEventMap;
98
98
  *
99
99
  * The following state attributes are available for styling:
100
100
  *
101
- * Attribute | Description | Part
102
- * ---|---|---
103
- * `opening` | Applied just after the overlay is attached to the DOM. You can apply a CSS @keyframe animation for this state. | `:host`
104
- * `closing` | Applied just before the overlay is detached from the DOM. You can apply a CSS @keyframe animation for this state. | `:host`
101
+ * Attribute | Description
102
+ * ----------|------------
103
+ * `opening` | Applied just after the overlay is opened. You can apply a CSS animation for this state.
104
+ * `closing` | Applied just before the overlay is closed. You can apply a CSS animation for this state.
105
105
  *
106
106
  * The following custom CSS properties are available for styling:
107
107
  *
@@ -9,7 +9,7 @@ import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
9
9
  import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
10
10
  import { LumoInjectionMixin } from '@vaadin/vaadin-themable-mixin/lumo-injection-mixin.js';
11
11
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
12
- import { overlayStyles } from './styles/vaadin-overlay-core-styles.js';
12
+ import { overlayStyles } from './styles/vaadin-overlay-base-styles.js';
13
13
  import { OverlayMixin } from './vaadin-overlay-mixin.js';
14
14
 
15
15
  /**
@@ -50,10 +50,10 @@ import { OverlayMixin } from './vaadin-overlay-mixin.js';
50
50
  *
51
51
  * The following state attributes are available for styling:
52
52
  *
53
- * Attribute | Description | Part
54
- * ---|---|---
55
- * `opening` | Applied just after the overlay is attached to the DOM. You can apply a CSS @keyframe animation for this state. | `:host`
56
- * `closing` | Applied just before the overlay is detached from the DOM. You can apply a CSS @keyframe animation for this state. | `:host`
53
+ * Attribute | Description
54
+ * ----------|------------
55
+ * `opening` | Applied just after the overlay is opened. You can apply a CSS animation for this state.
56
+ * `closing` | Applied just before the overlay is closed. You can apply a CSS animation for this state.
57
57
  *
58
58
  * The following custom CSS properties are available for styling:
59
59
  *
package/vaadin-overlay.js CHANGED
@@ -1,2 +1,2 @@
1
- import './theme/lumo/vaadin-overlay.js';
1
+ import './src/vaadin-overlay.js';
2
2
  export * from './src/vaadin-overlay.js';
@@ -1,74 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2017 - 2025 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import { css } from 'lit';
7
-
8
- export const overlayStyles = css`
9
- :host {
10
- z-index: 200;
11
- position: fixed;
12
-
13
- /* Despite of what the names say, <vaadin-overlay> is just a container
14
- for position/sizing/alignment. The actual overlay is the overlay part. */
15
-
16
- /* Default position constraints: the entire viewport. Note: themes can
17
- override this to introduce gaps between the overlay and the viewport. */
18
- inset: 0;
19
- bottom: var(--vaadin-overlay-viewport-bottom);
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
-
29
- /* Use flexbox alignment for the overlay part. */
30
- display: flex;
31
- flex-direction: column; /* makes dropdowns sizing easier */
32
- /* Align to center by default. */
33
- align-items: center;
34
- justify-content: center;
35
-
36
- /* Allow centering when max-width/max-height applies. */
37
- margin: auto;
38
-
39
- /* The host is not clickable, only the overlay part is. */
40
- pointer-events: none;
41
-
42
- /* Remove tap highlight on touch devices. */
43
- -webkit-tap-highlight-color: transparent;
44
-
45
- /* CSS API for host */
46
- --vaadin-overlay-viewport-bottom: 0;
47
- }
48
-
49
- :host([hidden]),
50
- :host(:not([opened]):not([closing])),
51
- :host(:not([opened]):not([closing])) [part='overlay'] {
52
- display: none !important;
53
- }
54
-
55
- [part='overlay'] {
56
- overflow: auto;
57
- pointer-events: auto;
58
-
59
- /* Prevent overflowing the host */
60
- max-width: 100%;
61
- box-sizing: border-box;
62
-
63
- -webkit-tap-highlight-color: initial; /* reenable tap highlight inside */
64
- }
65
-
66
- [part='backdrop'] {
67
- z-index: -1;
68
- content: '';
69
- background: rgba(0, 0, 0, 0.5);
70
- position: fixed;
71
- inset: 0;
72
- pointer-events: auto;
73
- }
74
- `;
@@ -1 +0,0 @@
1
- export {};
@@ -1,4 +0,0 @@
1
- import { overlay } from '@vaadin/vaadin-lumo-styles/mixins/overlay.js';
2
- import { registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
3
-
4
- registerStyles('vaadin-overlay', overlay, { moduleId: 'lumo-vaadin-overlay' });
@@ -1,2 +0,0 @@
1
- import './vaadin-overlay-styles.js';
2
- import '../../src/vaadin-overlay.js';
@@ -1,2 +0,0 @@
1
- import './vaadin-overlay-styles.js';
2
- import '../../src/vaadin-overlay.js';