@vaadin/overlay 25.0.0-alpha2 → 25.0.0-alpha21
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 +9 -12
- package/src/styles/vaadin-overlay-base-styles.js +100 -0
- package/src/vaadin-overlay-focus-mixin.js +26 -12
- package/src/vaadin-overlay-mixin.d.ts +12 -0
- package/src/vaadin-overlay-mixin.js +113 -30
- package/src/vaadin-overlay-position-mixin.js +56 -18
- package/src/vaadin-overlay-stack-mixin.js +59 -44
- package/src/vaadin-overlay-utils.d.ts +6 -0
- package/src/vaadin-overlay-utils.js +40 -14
- package/src/vaadin-overlay.d.ts +6 -6
- package/src/vaadin-overlay.js +7 -6
- package/vaadin-overlay.js +1 -1
- package/src/vaadin-overlay-base-styles.js +0 -82
- package/src/vaadin-overlay-core-styles.js +0 -67
- package/src/vaadin-overlay-styles.js +0 -6
- package/theme/lumo/vaadin-overlay-styles.d.ts +0 -1
- package/theme/lumo/vaadin-overlay-styles.js +0 -4
- package/theme/lumo/vaadin-overlay.d.ts +0 -2
- package/theme/lumo/vaadin-overlay.js +0 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/overlay",
|
|
3
|
-
"version": "25.0.0-
|
|
3
|
+
"version": "25.0.0-alpha21",
|
|
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/*-base-styles.d.ts",
|
|
25
|
-
"!src/*-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-
|
|
40
|
-
"@vaadin/component-base": "25.0.0-
|
|
41
|
-
"@vaadin/vaadin-
|
|
42
|
-
"@vaadin/vaadin-themable-mixin": "25.0.0-alpha2",
|
|
36
|
+
"@vaadin/a11y-base": "25.0.0-alpha21",
|
|
37
|
+
"@vaadin/component-base": "25.0.0-alpha21",
|
|
38
|
+
"@vaadin/vaadin-themable-mixin": "25.0.0-alpha21",
|
|
43
39
|
"lit": "^3.0.0"
|
|
44
40
|
},
|
|
45
41
|
"devDependencies": {
|
|
46
|
-
"@vaadin/chai-plugins": "25.0.0-
|
|
47
|
-
"@vaadin/test-runner-commands": "25.0.0-
|
|
42
|
+
"@vaadin/chai-plugins": "25.0.0-alpha21",
|
|
43
|
+
"@vaadin/test-runner-commands": "25.0.0-alpha21",
|
|
48
44
|
"@vaadin/testing-helpers": "^2.0.0",
|
|
49
|
-
"
|
|
45
|
+
"@vaadin/vaadin-lumo-styles": "25.0.0-alpha21",
|
|
46
|
+
"sinon": "^21.0.0"
|
|
50
47
|
},
|
|
51
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "8fb9e9710c01449edf623a1aaac4655cdc11a933"
|
|
52
49
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
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 '@vaadin/component-base/src/styles/style-props.js';
|
|
7
|
+
import { css } from 'lit';
|
|
8
|
+
|
|
9
|
+
export const overlayStyles = css`
|
|
10
|
+
:host {
|
|
11
|
+
z-index: 200;
|
|
12
|
+
position: fixed;
|
|
13
|
+
|
|
14
|
+
/* Despite of what the names say, <vaadin-overlay> is just a container
|
|
15
|
+
for position/sizing/alignment. The actual overlay is the overlay part. */
|
|
16
|
+
|
|
17
|
+
/* Default position constraints. Themes can
|
|
18
|
+
override this to adjust the gap between the overlay and the viewport. */
|
|
19
|
+
inset: var(--vaadin-overlay-viewport-inset, 8px);
|
|
20
|
+
bottom: var(--vaadin-overlay-viewport-bottom);
|
|
21
|
+
|
|
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
|
+
|
|
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;
|
|
36
|
+
|
|
37
|
+
/* Allow centering when max-width/max-height applies. */
|
|
38
|
+
margin: auto;
|
|
39
|
+
|
|
40
|
+
/* The host is not clickable, only the overlay part is. */
|
|
41
|
+
pointer-events: none;
|
|
42
|
+
|
|
43
|
+
/* Remove tap highlight on touch devices. */
|
|
44
|
+
-webkit-tap-highlight-color: transparent;
|
|
45
|
+
|
|
46
|
+
/* CSS API for host */
|
|
47
|
+
--vaadin-overlay-viewport-bottom: 8px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
:host([hidden]),
|
|
51
|
+
:host(:not([opened]):not([closing])),
|
|
52
|
+
:host(:not([opened]):not([closing])) [part='overlay'] {
|
|
53
|
+
display: none !important;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
[part='overlay'] {
|
|
57
|
+
background: var(--vaadin-overlay-background, var(--vaadin-background-color));
|
|
58
|
+
border: var(--vaadin-overlay-border-width, 1px) solid
|
|
59
|
+
var(--vaadin-overlay-border-color, var(--vaadin-border-color-secondary));
|
|
60
|
+
border-radius: var(--vaadin-overlay-border-radius, var(--vaadin-radius-m));
|
|
61
|
+
box-shadow: var(--vaadin-overlay-shadow, 0 8px 24px -4px rgba(0, 0, 0, 0.3));
|
|
62
|
+
box-sizing: border-box;
|
|
63
|
+
max-width: 100%;
|
|
64
|
+
overflow: auto;
|
|
65
|
+
overscroll-behavior: contain;
|
|
66
|
+
pointer-events: auto;
|
|
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;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
[part='backdrop'] {
|
|
83
|
+
background: var(--vaadin-overlay-backdrop-background, rgba(0, 0, 0, 0.2));
|
|
84
|
+
content: '';
|
|
85
|
+
inset: 0;
|
|
86
|
+
pointer-events: auto;
|
|
87
|
+
position: fixed;
|
|
88
|
+
z-index: -1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
[part='overlay']:focus-visible {
|
|
92
|
+
outline: var(--vaadin-focus-ring-width) solid var(--vaadin-focus-ring-color);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@media (forced-colors: active) {
|
|
96
|
+
[part='overlay'] {
|
|
97
|
+
border: 3px solid !important;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
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,20 +47,36 @@ export const OverlayFocusMixin = (superClass) =>
|
|
|
48
47
|
constructor() {
|
|
49
48
|
super();
|
|
50
49
|
|
|
51
|
-
this.__ariaModalController = new AriaModalController(this);
|
|
52
50
|
this.__focusTrapController = new FocusTrapController(this);
|
|
53
51
|
this.__focusRestorationController = new FocusRestorationController();
|
|
54
52
|
}
|
|
55
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Override to specify another element used as a content root,
|
|
56
|
+
* e.g. slotted into the overlay, rather than overlay itself.
|
|
57
|
+
* @protected
|
|
58
|
+
*/
|
|
59
|
+
get _contentRoot() {
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
56
63
|
/** @protected */
|
|
57
64
|
ready() {
|
|
58
65
|
super.ready();
|
|
59
66
|
|
|
60
|
-
this.addController(this.__ariaModalController);
|
|
61
67
|
this.addController(this.__focusTrapController);
|
|
62
68
|
this.addController(this.__focusRestorationController);
|
|
63
69
|
}
|
|
64
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Override to specify another element used as a focus trap root,
|
|
73
|
+
* e.g. the overlay's owner element, rather than overlay part.
|
|
74
|
+
* @protected
|
|
75
|
+
*/
|
|
76
|
+
get _focusTrapRoot() {
|
|
77
|
+
return this.$.overlay;
|
|
78
|
+
}
|
|
79
|
+
|
|
65
80
|
/**
|
|
66
81
|
* Release focus and restore focus after the overlay is closed.
|
|
67
82
|
*
|
|
@@ -69,13 +84,13 @@ export const OverlayFocusMixin = (superClass) =>
|
|
|
69
84
|
*/
|
|
70
85
|
_resetFocus() {
|
|
71
86
|
if (this.focusTrap) {
|
|
72
|
-
this.__ariaModalController.close();
|
|
73
87
|
this.__focusTrapController.releaseFocus();
|
|
74
88
|
}
|
|
75
89
|
|
|
76
90
|
if (this.restoreFocusOnClose && this._shouldRestoreFocus()) {
|
|
77
|
-
const
|
|
78
|
-
|
|
91
|
+
const focusVisible = isKeyboardActive();
|
|
92
|
+
const preventScroll = !focusVisible;
|
|
93
|
+
this.__focusRestorationController.restoreFocus({ preventScroll, focusVisible });
|
|
79
94
|
}
|
|
80
95
|
}
|
|
81
96
|
|
|
@@ -97,8 +112,7 @@ export const OverlayFocusMixin = (superClass) =>
|
|
|
97
112
|
*/
|
|
98
113
|
_trapFocus() {
|
|
99
114
|
if (this.focusTrap) {
|
|
100
|
-
this.
|
|
101
|
-
this.__focusTrapController.trapFocus(this.$.overlay);
|
|
115
|
+
this.__focusTrapController.trapFocus(this._focusTrapRoot);
|
|
102
116
|
}
|
|
103
117
|
}
|
|
104
118
|
|
|
@@ -127,15 +141,15 @@ export const OverlayFocusMixin = (superClass) =>
|
|
|
127
141
|
* @protected
|
|
128
142
|
*/
|
|
129
143
|
_deepContains(node) {
|
|
130
|
-
if (this.contains(node)) {
|
|
144
|
+
if (this._contentRoot.contains(node)) {
|
|
131
145
|
return true;
|
|
132
146
|
}
|
|
133
147
|
let n = node;
|
|
134
148
|
const doc = node.ownerDocument;
|
|
135
|
-
// Walk from node to
|
|
136
|
-
while (n && n !== doc && n !== this) {
|
|
149
|
+
// Walk from node to content root or `document`
|
|
150
|
+
while (n && n !== doc && n !== this._contentRoot) {
|
|
137
151
|
n = n.parentNode || n.host;
|
|
138
152
|
}
|
|
139
|
-
return n === this;
|
|
153
|
+
return n === this._contentRoot;
|
|
140
154
|
}
|
|
141
155
|
};
|
|
@@ -7,6 +7,13 @@ import type { Constructor } from '@open-wc/dedupe-mixin';
|
|
|
7
7
|
import type { OverlayFocusMixinClass } from './vaadin-overlay-focus-mixin.js';
|
|
8
8
|
import type { OverlayStackMixinClass } from './vaadin-overlay-stack-mixin.js';
|
|
9
9
|
|
|
10
|
+
export type OverlayBounds = {
|
|
11
|
+
top?: number | string;
|
|
12
|
+
left?: number | string;
|
|
13
|
+
width?: number | string;
|
|
14
|
+
height?: number | string;
|
|
15
|
+
};
|
|
16
|
+
|
|
10
17
|
export type OverlayRenderer = (root: HTMLElement, owner: HTMLElement, model?: object) => void;
|
|
11
18
|
|
|
12
19
|
export declare function OverlayMixin<T extends Constructor<HTMLElement>>(
|
|
@@ -58,6 +65,11 @@ export declare class OverlayMixinClass {
|
|
|
58
65
|
|
|
59
66
|
close(sourceEvent?: Event | null): void;
|
|
60
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Updates the coordinates of the overlay.
|
|
70
|
+
*/
|
|
71
|
+
setBounds(bounds: OverlayBounds, absolute?: boolean): void;
|
|
72
|
+
|
|
61
73
|
/**
|
|
62
74
|
* Requests an update for the content of the overlay.
|
|
63
75
|
* While performing the update, it invokes the renderer passed in the `renderer` property.
|
|
@@ -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
|
-
|
|
119
|
-
super.
|
|
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
|
|
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
|
|
@@ -149,6 +156,11 @@ export const OverlayMixin = (superClass) =>
|
|
|
149
156
|
disconnectedCallback() {
|
|
150
157
|
super.disconnectedCallback();
|
|
151
158
|
|
|
159
|
+
if (this.__scheduledOpen) {
|
|
160
|
+
cancelAnimationFrame(this.__scheduledOpen);
|
|
161
|
+
this.__scheduledOpen = null;
|
|
162
|
+
}
|
|
163
|
+
|
|
152
164
|
/* c8 ignore next 3 */
|
|
153
165
|
if (this._boundIosResizeListener) {
|
|
154
166
|
window.removeEventListener('resize', this._boundIosResizeListener);
|
|
@@ -163,7 +175,7 @@ export const OverlayMixin = (superClass) =>
|
|
|
163
175
|
*/
|
|
164
176
|
requestContentUpdate() {
|
|
165
177
|
if (this.renderer) {
|
|
166
|
-
this.renderer.call(this.owner, this, this.owner, this.model);
|
|
178
|
+
this.renderer.call(this.owner, this._contentRoot, this.owner, this.model);
|
|
167
179
|
}
|
|
168
180
|
}
|
|
169
181
|
|
|
@@ -171,17 +183,44 @@ export const OverlayMixin = (superClass) =>
|
|
|
171
183
|
* @param {Event=} sourceEvent
|
|
172
184
|
*/
|
|
173
185
|
close(sourceEvent) {
|
|
174
|
-
|
|
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', {
|
|
175
189
|
bubbles: true,
|
|
176
190
|
cancelable: true,
|
|
177
|
-
detail: { sourceEvent },
|
|
191
|
+
detail: { overlay: this, sourceEvent },
|
|
178
192
|
});
|
|
179
|
-
this.dispatchEvent(
|
|
180
|
-
|
|
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) {
|
|
181
197
|
this.opened = false;
|
|
182
198
|
}
|
|
183
199
|
}
|
|
184
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Updates the coordinates of the overlay.
|
|
203
|
+
* @param {!OverlayBoundsParam} bounds
|
|
204
|
+
* @param {boolean} absolute
|
|
205
|
+
*/
|
|
206
|
+
setBounds(bounds, absolute = true) {
|
|
207
|
+
const overlay = this.$.overlay;
|
|
208
|
+
const parsedBounds = { ...bounds };
|
|
209
|
+
|
|
210
|
+
if (absolute && overlay.style.position !== 'absolute') {
|
|
211
|
+
overlay.style.position = 'absolute';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
Object.keys(parsedBounds).forEach((arg) => {
|
|
215
|
+
// Allow setting width or height to `null`
|
|
216
|
+
if (parsedBounds[arg] !== null && !isNaN(parsedBounds[arg])) {
|
|
217
|
+
parsedBounds[arg] = `${parsedBounds[arg]}px`;
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
Object.assign(overlay.style, parsedBounds);
|
|
222
|
+
}
|
|
223
|
+
|
|
185
224
|
/** @private */
|
|
186
225
|
_detectIosNavbar() {
|
|
187
226
|
/* c8 ignore next 15 */
|
|
@@ -203,8 +242,25 @@ export const OverlayMixin = (superClass) =>
|
|
|
203
242
|
}
|
|
204
243
|
}
|
|
205
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
|
+
|
|
206
256
|
/** @private */
|
|
207
257
|
_addGlobalListeners() {
|
|
258
|
+
if (this.__hasGlobalListeners) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
this.__hasGlobalListeners = true;
|
|
263
|
+
|
|
208
264
|
document.addEventListener('mousedown', this._boundMouseDownListener);
|
|
209
265
|
document.addEventListener('mouseup', this._boundMouseUpListener);
|
|
210
266
|
// Firefox leaks click to document on contextmenu even if prevented
|
|
@@ -214,6 +270,12 @@ export const OverlayMixin = (superClass) =>
|
|
|
214
270
|
|
|
215
271
|
/** @private */
|
|
216
272
|
_removeGlobalListeners() {
|
|
273
|
+
if (!this.__hasGlobalListeners) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
this.__hasGlobalListeners = false;
|
|
278
|
+
|
|
217
279
|
document.removeEventListener('mousedown', this._boundMouseDownListener);
|
|
218
280
|
document.removeEventListener('mouseup', this._boundMouseUpListener);
|
|
219
281
|
document.documentElement.removeEventListener('click', this._boundOutsideClickListener, true);
|
|
@@ -233,11 +295,11 @@ export const OverlayMixin = (superClass) =>
|
|
|
233
295
|
this._oldOpened = opened;
|
|
234
296
|
|
|
235
297
|
if (rendererChanged && hasOldRenderer) {
|
|
236
|
-
this.innerHTML = '';
|
|
298
|
+
this._contentRoot.innerHTML = '';
|
|
237
299
|
// Whenever a Lit-based renderer is used, it assigns a Lit part to the node it was rendered into.
|
|
238
300
|
// When clearing the rendered content, this part needs to be manually disposed of.
|
|
239
301
|
// Otherwise, using a Lit-based renderer on the same node will throw an exception or render nothing afterward.
|
|
240
|
-
delete this._$litPart$;
|
|
302
|
+
delete this._contentRoot._$litPart$;
|
|
241
303
|
}
|
|
242
304
|
|
|
243
305
|
if (opened && renderer && (rendererChanged || openedChanged || ownerOrModelChanged)) {
|
|
@@ -247,20 +309,39 @@ export const OverlayMixin = (superClass) =>
|
|
|
247
309
|
|
|
248
310
|
/** @private */
|
|
249
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
|
+
|
|
250
321
|
if (!modeless) {
|
|
251
322
|
if (this.opened) {
|
|
252
|
-
this._addGlobalListeners();
|
|
253
323
|
this._enterModalState();
|
|
254
324
|
}
|
|
255
325
|
} else {
|
|
256
|
-
this._removeGlobalListeners();
|
|
257
326
|
this._exitModalState();
|
|
258
327
|
}
|
|
328
|
+
setOverlayStateAttribute(this, 'modeless', modeless);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** @private */
|
|
332
|
+
_withBackdropChanged(withBackdrop) {
|
|
333
|
+
setOverlayStateAttribute(this, 'with-backdrop', withBackdrop);
|
|
259
334
|
}
|
|
260
335
|
|
|
261
336
|
/** @private */
|
|
262
337
|
_openedChanged(opened, wasOpened) {
|
|
263
338
|
if (opened) {
|
|
339
|
+
// Prevent possible errors on setting `opened` to `true` while disconnected
|
|
340
|
+
if (!this.isConnected) {
|
|
341
|
+
this.opened = false;
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
264
345
|
this._saveFocus();
|
|
265
346
|
|
|
266
347
|
this._animatedOpening();
|
|
@@ -269,13 +350,18 @@ export const OverlayMixin = (superClass) =>
|
|
|
269
350
|
setTimeout(() => {
|
|
270
351
|
this._trapFocus();
|
|
271
352
|
|
|
272
|
-
|
|
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);
|
|
273
359
|
});
|
|
274
360
|
});
|
|
275
361
|
|
|
276
362
|
document.addEventListener('keydown', this._boundKeydownListener);
|
|
277
363
|
|
|
278
|
-
if (
|
|
364
|
+
if (this._shouldAddGlobalListeners()) {
|
|
279
365
|
this._addGlobalListeners();
|
|
280
366
|
}
|
|
281
367
|
} else if (wasOpened) {
|
|
@@ -290,7 +376,7 @@ export const OverlayMixin = (superClass) =>
|
|
|
290
376
|
|
|
291
377
|
document.removeEventListener('keydown', this._boundKeydownListener);
|
|
292
378
|
|
|
293
|
-
if (
|
|
379
|
+
if (this._shouldAddGlobalListeners()) {
|
|
294
380
|
this._removeGlobalListeners();
|
|
295
381
|
}
|
|
296
382
|
}
|
|
@@ -346,14 +432,16 @@ export const OverlayMixin = (superClass) =>
|
|
|
346
432
|
|
|
347
433
|
/** @private */
|
|
348
434
|
_animatedOpening() {
|
|
349
|
-
if (this.
|
|
435
|
+
if (this._isAttached && this.hasAttribute('closing')) {
|
|
350
436
|
this._flushAnimation('closing');
|
|
351
437
|
}
|
|
352
438
|
this._attachOverlay();
|
|
439
|
+
this._appendAttachedInstance();
|
|
440
|
+
this.bringToFront();
|
|
353
441
|
if (!this.modeless) {
|
|
354
442
|
this._enterModalState();
|
|
355
443
|
}
|
|
356
|
-
this
|
|
444
|
+
setOverlayStateAttribute(this, 'opening', true);
|
|
357
445
|
|
|
358
446
|
if (this._shouldAnimate()) {
|
|
359
447
|
this._enqueueAnimation('opening', () => {
|
|
@@ -366,22 +454,20 @@ export const OverlayMixin = (superClass) =>
|
|
|
366
454
|
|
|
367
455
|
/** @private */
|
|
368
456
|
_attachOverlay() {
|
|
369
|
-
this.
|
|
370
|
-
this.parentNode.insertBefore(this._placeholder, this);
|
|
371
|
-
document.body.appendChild(this);
|
|
372
|
-
this.bringToFront();
|
|
457
|
+
this.showPopover();
|
|
373
458
|
}
|
|
374
459
|
|
|
375
460
|
/** @private */
|
|
376
461
|
_finishOpening() {
|
|
377
|
-
this
|
|
462
|
+
setOverlayStateAttribute(this, 'opening', false);
|
|
378
463
|
}
|
|
379
464
|
|
|
380
465
|
/** @private */
|
|
381
466
|
_finishClosing() {
|
|
382
467
|
this._detachOverlay();
|
|
468
|
+
this._removeAttachedInstance();
|
|
383
469
|
this.$.overlay.style.removeProperty('pointer-events');
|
|
384
|
-
this
|
|
470
|
+
setOverlayStateAttribute(this, 'closing', false);
|
|
385
471
|
this.dispatchEvent(new CustomEvent('vaadin-overlay-closed'));
|
|
386
472
|
}
|
|
387
473
|
|
|
@@ -390,9 +476,9 @@ export const OverlayMixin = (superClass) =>
|
|
|
390
476
|
if (this.hasAttribute('opening')) {
|
|
391
477
|
this._flushAnimation('opening');
|
|
392
478
|
}
|
|
393
|
-
if (this.
|
|
479
|
+
if (this._isAttached) {
|
|
394
480
|
this._exitModalState();
|
|
395
|
-
this
|
|
481
|
+
setOverlayStateAttribute(this, 'closing', true);
|
|
396
482
|
this.dispatchEvent(new CustomEvent('vaadin-overlay-closing'));
|
|
397
483
|
|
|
398
484
|
if (this._shouldAnimate()) {
|
|
@@ -407,8 +493,7 @@ export const OverlayMixin = (superClass) =>
|
|
|
407
493
|
|
|
408
494
|
/** @private */
|
|
409
495
|
_detachOverlay() {
|
|
410
|
-
this.
|
|
411
|
-
this._placeholder.parentNode.removeChild(this._placeholder);
|
|
496
|
+
this.hidePopover();
|
|
412
497
|
}
|
|
413
498
|
|
|
414
499
|
/** @private */
|
|
@@ -452,7 +537,6 @@ export const OverlayMixin = (superClass) =>
|
|
|
452
537
|
}
|
|
453
538
|
|
|
454
539
|
const evt = new CustomEvent('vaadin-overlay-outside-click', {
|
|
455
|
-
bubbles: true,
|
|
456
540
|
cancelable: true,
|
|
457
541
|
detail: { sourceEvent: event },
|
|
458
542
|
});
|
|
@@ -473,13 +557,12 @@ export const OverlayMixin = (superClass) =>
|
|
|
473
557
|
}
|
|
474
558
|
|
|
475
559
|
// Only close modeless overlay on Esc press when it contains focus
|
|
476
|
-
if (this.
|
|
560
|
+
if (!this._shouldAddGlobalListeners() && !event.composedPath().includes(this._focusTrapRoot)) {
|
|
477
561
|
return;
|
|
478
562
|
}
|
|
479
563
|
|
|
480
564
|
if (event.key === 'Escape') {
|
|
481
565
|
const evt = new CustomEvent('vaadin-overlay-escape-press', {
|
|
482
|
-
bubbles: true,
|
|
483
566
|
cancelable: true,
|
|
484
567
|
detail: { sourceEvent: event },
|
|
485
568
|
});
|
|
@@ -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',
|
|
@@ -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
|
-
|
|
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.
|
|
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;
|
|
@@ -271,11 +309,11 @@ export const PositionMixin = (superClass) =>
|
|
|
271
309
|
// Apply the positioning properties to the overlay
|
|
272
310
|
Object.assign(this.style, verticalProps, horizontalProps);
|
|
273
311
|
|
|
274
|
-
this
|
|
275
|
-
this
|
|
312
|
+
setOverlayStateAttribute(this, 'bottom-aligned', !shouldAlignStartVertically);
|
|
313
|
+
setOverlayStateAttribute(this, 'top-aligned', shouldAlignStartVertically);
|
|
276
314
|
|
|
277
|
-
this
|
|
278
|
-
this
|
|
315
|
+
setOverlayStateAttribute(this, 'end-aligned', !flexStart);
|
|
316
|
+
setOverlayStateAttribute(this, 'start-aligned', flexStart);
|
|
279
317
|
}
|
|
280
318
|
|
|
281
319
|
__shouldAlignStartHorizontally(targetRect, rtl) {
|
|
@@ -4,45 +4,43 @@
|
|
|
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
|
|
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
|
|
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.
|
|
25
36
|
* @param {HTMLElement} overlay
|
|
37
|
+
* @param {function(HTMLElement): boolean} filter
|
|
26
38
|
* @return {boolean}
|
|
27
39
|
* @protected
|
|
28
40
|
*/
|
|
29
|
-
export const isLastOverlay = (overlay) =>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Stores the reference to the nested overlay for given parent,
|
|
35
|
-
* or removes it when the nested overlay is null.
|
|
36
|
-
* @param {HTMLElement} parent
|
|
37
|
-
* @param {HTMLElement} nested
|
|
38
|
-
* @protected
|
|
39
|
-
*/
|
|
40
|
-
export const setNestedOverlay = (parent, nested) => {
|
|
41
|
-
if (nested != null) {
|
|
42
|
-
overlayMap.set(parent, nested);
|
|
43
|
-
} else {
|
|
44
|
-
overlayMap.delete(parent);
|
|
45
|
-
}
|
|
41
|
+
export const isLastOverlay = (overlay, filter = (_overlay) => true) => {
|
|
42
|
+
const filteredOverlays = getAttachedInstances().filter(filter);
|
|
43
|
+
return overlay === filteredOverlays.pop();
|
|
46
44
|
};
|
|
47
45
|
|
|
48
46
|
/**
|
|
@@ -50,12 +48,6 @@ export const setNestedOverlay = (parent, nested) => {
|
|
|
50
48
|
*/
|
|
51
49
|
export const OverlayStackMixin = (superClass) =>
|
|
52
50
|
class OverlayStackMixin extends superClass {
|
|
53
|
-
constructor() {
|
|
54
|
-
super();
|
|
55
|
-
|
|
56
|
-
this._hasOverlayStackMixin = true;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
51
|
/**
|
|
60
52
|
* Returns true if this is the last one in the opened overlays stack.
|
|
61
53
|
*
|
|
@@ -66,25 +58,36 @@ export const OverlayStackMixin = (superClass) =>
|
|
|
66
58
|
return isLastOverlay(this);
|
|
67
59
|
}
|
|
68
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
|
+
|
|
69
71
|
/**
|
|
70
72
|
* Brings the overlay as visually the frontmost one.
|
|
71
73
|
*/
|
|
72
74
|
bringToFront() {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const frontmostZIndex = frontmost.__zIndex;
|
|
79
|
-
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;
|
|
80
80
|
}
|
|
81
|
-
this.style.zIndex = zIndex;
|
|
82
|
-
this.__zIndex = zIndex || parseFloat(getComputedStyle(this).zIndex);
|
|
83
81
|
|
|
84
|
-
//
|
|
85
|
-
if (
|
|
86
|
-
|
|
82
|
+
// Update stacking order of native popover-based overlays
|
|
83
|
+
if (this.matches(':popover-open')) {
|
|
84
|
+
this.hidePopover();
|
|
85
|
+
this.showPopover();
|
|
87
86
|
}
|
|
87
|
+
|
|
88
|
+
// Update order of attached instances
|
|
89
|
+
this._removeAttachedInstance();
|
|
90
|
+
this._appendAttachedInstance();
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
/** @protected */
|
|
@@ -97,7 +100,7 @@ export const OverlayStackMixin = (superClass) =>
|
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
// Disable pointer events in other attached overlays
|
|
100
|
-
|
|
103
|
+
getAttachedInstances().forEach((el) => {
|
|
101
104
|
if (el !== this) {
|
|
102
105
|
el.$.overlay.style.pointerEvents = 'none';
|
|
103
106
|
}
|
|
@@ -113,7 +116,7 @@ export const OverlayStackMixin = (superClass) =>
|
|
|
113
116
|
}
|
|
114
117
|
|
|
115
118
|
// Restore pointer events in the previous overlay(s)
|
|
116
|
-
const instances =
|
|
119
|
+
const instances = getAttachedInstances();
|
|
117
120
|
|
|
118
121
|
let el;
|
|
119
122
|
// Use instances.pop() to ensure the reverse order
|
|
@@ -129,4 +132,16 @@ export const OverlayStackMixin = (superClass) =>
|
|
|
129
132
|
}
|
|
130
133
|
}
|
|
131
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
|
+
}
|
|
132
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;
|
|
@@ -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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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;
|
|
@@ -87,3 +84,32 @@ export function observeMove(element, callback) {
|
|
|
87
84
|
|
|
88
85
|
return cleanup;
|
|
89
86
|
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Toggle the state attribute on the overlay element and also its owner element. This allows targeting state attributes
|
|
90
|
+
* in the light DOM in case the overlay is in the shadow DOM of its owner.
|
|
91
|
+
* @param {HTMLElement} overlay The overlay element on which to toggle the attribute.
|
|
92
|
+
* @param {string} name The name of the attribute to toggle.
|
|
93
|
+
* @param {string|boolean} value The value of the attribute. If a string is provided, it will be set as the attribute
|
|
94
|
+
* value. Otherwise, the attribute will be added or removed depending on whether `value` is truthy or falsy.
|
|
95
|
+
*/
|
|
96
|
+
export function setOverlayStateAttribute(overlay, name, value) {
|
|
97
|
+
const elements = [overlay];
|
|
98
|
+
if (overlay.owner) {
|
|
99
|
+
elements.push(overlay.owner);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (typeof value === 'string') {
|
|
103
|
+
elements.forEach((element) => {
|
|
104
|
+
element.setAttribute(name, value);
|
|
105
|
+
});
|
|
106
|
+
} else if (value) {
|
|
107
|
+
elements.forEach((element) => {
|
|
108
|
+
element.setAttribute(name, '');
|
|
109
|
+
});
|
|
110
|
+
} else {
|
|
111
|
+
elements.forEach((element) => {
|
|
112
|
+
element.removeAttribute(name);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
package/src/vaadin-overlay.d.ts
CHANGED
|
@@ -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
|
|
102
|
-
*
|
|
103
|
-
* `opening` | Applied just after the overlay is
|
|
104
|
-
* `closing` | Applied just before the overlay is
|
|
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
|
*
|
package/src/vaadin-overlay.js
CHANGED
|
@@ -7,9 +7,10 @@ import { html, LitElement } from 'lit';
|
|
|
7
7
|
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
|
|
8
8
|
import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
|
|
9
9
|
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
|
|
10
|
+
import { LumoInjectionMixin } from '@vaadin/vaadin-themable-mixin/lumo-injection-mixin.js';
|
|
10
11
|
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
12
|
+
import { overlayStyles } from './styles/vaadin-overlay-base-styles.js';
|
|
11
13
|
import { OverlayMixin } from './vaadin-overlay-mixin.js';
|
|
12
|
-
import { overlayStyles } from './vaadin-overlay-styles.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* `<vaadin-overlay>` is a Web Component for creating overlays. The content of the overlay
|
|
@@ -49,10 +50,10 @@ import { overlayStyles } from './vaadin-overlay-styles.js';
|
|
|
49
50
|
*
|
|
50
51
|
* The following state attributes are available for styling:
|
|
51
52
|
*
|
|
52
|
-
* Attribute | Description
|
|
53
|
-
*
|
|
54
|
-
* `opening` | Applied just after the overlay is
|
|
55
|
-
* `closing` | Applied just before the overlay is
|
|
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.
|
|
56
57
|
*
|
|
57
58
|
* The following custom CSS properties are available for styling:
|
|
58
59
|
*
|
|
@@ -76,7 +77,7 @@ import { overlayStyles } from './vaadin-overlay-styles.js';
|
|
|
76
77
|
* @mixes DirMixin
|
|
77
78
|
* @mixes OverlayMixin
|
|
78
79
|
*/
|
|
79
|
-
class Overlay extends OverlayMixin(DirMixin(ThemableMixin(PolylitMixin(LitElement)))) {
|
|
80
|
+
class Overlay extends OverlayMixin(DirMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(LitElement))))) {
|
|
80
81
|
static get is() {
|
|
81
82
|
return 'vaadin-overlay';
|
|
82
83
|
}
|
package/vaadin-overlay.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import './
|
|
1
|
+
import './src/vaadin-overlay.js';
|
|
2
2
|
export * from './src/vaadin-overlay.js';
|
|
@@ -1,82 +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 '@vaadin/component-base/src/style-props.js';
|
|
7
|
-
import { css } from 'lit';
|
|
8
|
-
|
|
9
|
-
export const overlayStyles = css`
|
|
10
|
-
@layer base {
|
|
11
|
-
:host {
|
|
12
|
-
z-index: 200;
|
|
13
|
-
position: fixed;
|
|
14
|
-
|
|
15
|
-
/* Despite of what the names say, <vaadin-overlay> is just a container
|
|
16
|
-
for position/sizing/alignment. The actual overlay is the overlay part. */
|
|
17
|
-
|
|
18
|
-
/* Default position constraints. Themes can
|
|
19
|
-
override this to adjust the gap between the overlay and the viewport. */
|
|
20
|
-
inset: 8px;
|
|
21
|
-
bottom: var(--vaadin-overlay-viewport-bottom);
|
|
22
|
-
|
|
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;
|
|
29
|
-
|
|
30
|
-
/* Allow centering when max-width/max-height applies. */
|
|
31
|
-
margin: auto;
|
|
32
|
-
|
|
33
|
-
/* The host is not clickable, only the overlay part is. */
|
|
34
|
-
pointer-events: none;
|
|
35
|
-
|
|
36
|
-
/* Remove tap highlight on touch devices. */
|
|
37
|
-
-webkit-tap-highlight-color: transparent;
|
|
38
|
-
|
|
39
|
-
/* CSS API for host */
|
|
40
|
-
--vaadin-overlay-viewport-bottom: 8px;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
:host([hidden]),
|
|
44
|
-
:host(:not([opened]):not([closing])),
|
|
45
|
-
:host(:not([opened]):not([closing])) [part='overlay'] {
|
|
46
|
-
display: none !important;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
[part='overlay'] {
|
|
50
|
-
background: var(--vaadin-overlay-background, var(--_vaadin-background));
|
|
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 hsl(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
|
-
}
|
|
61
|
-
|
|
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
|
-
}
|
|
70
|
-
|
|
71
|
-
@media (forced-colors: active) {
|
|
72
|
-
[part='overlay'] {
|
|
73
|
-
border: 3px solid;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
[part='overlay']:focus-visible {
|
|
77
|
-
outline: var(--vaadin-focus-ring-width) solid;
|
|
78
|
-
outline-offset: 1px;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
`;
|
|
@@ -1,67 +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
|
-
/* Use flexbox alignment for the overlay part. */
|
|
22
|
-
display: flex;
|
|
23
|
-
flex-direction: column; /* makes dropdowns sizing easier */
|
|
24
|
-
/* Align to center by default. */
|
|
25
|
-
align-items: center;
|
|
26
|
-
justify-content: center;
|
|
27
|
-
|
|
28
|
-
/* Allow centering when max-width/max-height applies. */
|
|
29
|
-
margin: auto;
|
|
30
|
-
|
|
31
|
-
/* The host is not clickable, only the overlay part is. */
|
|
32
|
-
pointer-events: none;
|
|
33
|
-
|
|
34
|
-
/* Remove tap highlight on touch devices. */
|
|
35
|
-
-webkit-tap-highlight-color: transparent;
|
|
36
|
-
|
|
37
|
-
/* CSS API for host */
|
|
38
|
-
--vaadin-overlay-viewport-bottom: 0;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
:host([hidden]),
|
|
42
|
-
:host(:not([opened]):not([closing])),
|
|
43
|
-
:host(:not([opened]):not([closing])) [part='overlay'] {
|
|
44
|
-
display: none !important;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
[part='overlay'] {
|
|
48
|
-
-webkit-overflow-scrolling: touch;
|
|
49
|
-
overflow: auto;
|
|
50
|
-
pointer-events: auto;
|
|
51
|
-
|
|
52
|
-
/* Prevent overflowing the host */
|
|
53
|
-
max-width: 100%;
|
|
54
|
-
box-sizing: border-box;
|
|
55
|
-
|
|
56
|
-
-webkit-tap-highlight-color: initial; /* reenable tap highlight inside */
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
[part='backdrop'] {
|
|
60
|
-
z-index: -1;
|
|
61
|
-
content: '';
|
|
62
|
-
background: rgba(0, 0, 0, 0.5);
|
|
63
|
-
position: fixed;
|
|
64
|
-
inset: 0;
|
|
65
|
-
pointer-events: auto;
|
|
66
|
-
}
|
|
67
|
-
`;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|