@vaadin/overlay 24.8.4 → 25.0.0-alpha10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +12 -13
- package/src/styles/vaadin-overlay-base-styles.js +87 -0
- package/src/{vaadin-overlay-core-styles.js → styles/vaadin-overlay-core-styles.js} +8 -1
- package/src/vaadin-overlay-focus-mixin.d.ts +1 -2
- package/src/vaadin-overlay-focus-mixin.js +24 -8
- package/src/vaadin-overlay-mixin.d.ts +13 -6
- package/src/vaadin-overlay-mixin.js +72 -24
- package/src/vaadin-overlay-position-mixin.js +5 -5
- package/src/vaadin-overlay-stack-mixin.js +56 -45
- package/src/vaadin-overlay-utils.d.ts +6 -0
- package/src/vaadin-overlay-utils.js +29 -0
- package/src/vaadin-overlay.js +17 -20
- package/src/vaadin-lit-overlay.js +0 -47
- package/src/vaadin-overlay-styles.js +0 -6
- package/theme/material/vaadin-overlay-styles.d.ts +0 -1
- package/theme/material/vaadin-overlay-styles.js +0 -4
- package/theme/material/vaadin-overlay.d.ts +0 -2
- package/theme/material/vaadin-overlay.js +0 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/overlay",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "25.0.0-alpha10",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
"type": "module",
|
|
22
22
|
"files": [
|
|
23
23
|
"src",
|
|
24
|
+
"!src/styles/*-base-styles.d.ts",
|
|
25
|
+
"!src/styles/*-base-styles.js",
|
|
24
26
|
"theme",
|
|
25
27
|
"vaadin-*.d.ts",
|
|
26
28
|
"vaadin-*.js"
|
|
@@ -30,24 +32,21 @@
|
|
|
30
32
|
"vaadin-overlay",
|
|
31
33
|
"overlay",
|
|
32
34
|
"web-components",
|
|
33
|
-
"web-component"
|
|
34
|
-
"polymer"
|
|
35
|
+
"web-component"
|
|
35
36
|
],
|
|
36
37
|
"dependencies": {
|
|
37
38
|
"@open-wc/dedupe-mixin": "^1.3.0",
|
|
38
|
-
"@
|
|
39
|
-
"@vaadin/
|
|
40
|
-
"@vaadin/
|
|
41
|
-
"@vaadin/vaadin-
|
|
42
|
-
"@vaadin/vaadin-material-styles": "~24.8.4",
|
|
43
|
-
"@vaadin/vaadin-themable-mixin": "~24.8.4",
|
|
39
|
+
"@vaadin/a11y-base": "25.0.0-alpha10",
|
|
40
|
+
"@vaadin/component-base": "25.0.0-alpha10",
|
|
41
|
+
"@vaadin/vaadin-lumo-styles": "25.0.0-alpha10",
|
|
42
|
+
"@vaadin/vaadin-themable-mixin": "25.0.0-alpha10",
|
|
44
43
|
"lit": "^3.0.0"
|
|
45
44
|
},
|
|
46
45
|
"devDependencies": {
|
|
47
|
-
"@vaadin/chai-plugins": "
|
|
48
|
-
"@vaadin/test-runner-commands": "
|
|
49
|
-
"@vaadin/testing-helpers": "^
|
|
46
|
+
"@vaadin/chai-plugins": "25.0.0-alpha10",
|
|
47
|
+
"@vaadin/test-runner-commands": "25.0.0-alpha10",
|
|
48
|
+
"@vaadin/testing-helpers": "^2.0.0",
|
|
50
49
|
"sinon": "^18.0.0"
|
|
51
50
|
},
|
|
52
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "6cc6c94079e805fa5b2f0af4dbf3b2a7485e57d0"
|
|
53
52
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
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
|
+
: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: 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 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
|
+
}
|
|
77
|
+
|
|
78
|
+
[part='overlay']:focus-visible {
|
|
79
|
+
outline: var(--vaadin-focus-ring-width) solid var(--vaadin-focus-ring-color);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@media (forced-colors: active) {
|
|
83
|
+
[part='overlay'] {
|
|
84
|
+
border: 3px solid;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
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 */
|
|
@@ -45,7 +53,6 @@ export const overlayStyles = css`
|
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
[part='overlay'] {
|
|
48
|
-
-webkit-overflow-scrolling: touch;
|
|
49
56
|
overflow: auto;
|
|
50
57
|
pointer-events: auto;
|
|
51
58
|
|
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
import type { Constructor } from '@open-wc/dedupe-mixin';
|
|
7
|
-
import type { ControllerMixinClass } from '@vaadin/component-base/src/controller-mixin.js';
|
|
8
7
|
|
|
9
8
|
export declare function OverlayFocusMixin<T extends Constructor<HTMLElement>>(
|
|
10
9
|
base: T,
|
|
11
|
-
): Constructor<
|
|
10
|
+
): Constructor<OverlayFocusMixinClass> & T;
|
|
12
11
|
|
|
13
12
|
export declare class OverlayFocusMixinClass {
|
|
14
13
|
/**
|
|
@@ -7,14 +7,12 @@ import { AriaModalController } from '@vaadin/a11y-base/src/aria-modal-controller
|
|
|
7
7
|
import { FocusRestorationController } from '@vaadin/a11y-base/src/focus-restoration-controller.js';
|
|
8
8
|
import { FocusTrapController } from '@vaadin/a11y-base/src/focus-trap-controller.js';
|
|
9
9
|
import { getDeepActiveElement, isKeyboardActive } from '@vaadin/a11y-base/src/focus-utils.js';
|
|
10
|
-
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* @polymerMixin
|
|
14
|
-
* @mixes ControllerMixin
|
|
15
13
|
*/
|
|
16
14
|
export const OverlayFocusMixin = (superClass) =>
|
|
17
|
-
class OverlayFocusMixin extends
|
|
15
|
+
class OverlayFocusMixin extends superClass {
|
|
18
16
|
static get properties() {
|
|
19
17
|
return {
|
|
20
18
|
/**
|
|
@@ -50,11 +48,20 @@ export const OverlayFocusMixin = (superClass) =>
|
|
|
50
48
|
constructor() {
|
|
51
49
|
super();
|
|
52
50
|
|
|
53
|
-
this.__ariaModalController = new AriaModalController(this);
|
|
51
|
+
this.__ariaModalController = new AriaModalController(this, () => this._modalRoot);
|
|
54
52
|
this.__focusTrapController = new FocusTrapController(this);
|
|
55
53
|
this.__focusRestorationController = new FocusRestorationController();
|
|
56
54
|
}
|
|
57
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
|
+
|
|
58
65
|
/** @protected */
|
|
59
66
|
ready() {
|
|
60
67
|
super.ready();
|
|
@@ -64,6 +71,15 @@ export const OverlayFocusMixin = (superClass) =>
|
|
|
64
71
|
this.addController(this.__focusRestorationController);
|
|
65
72
|
}
|
|
66
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
|
+
|
|
67
83
|
/**
|
|
68
84
|
* Release focus and restore focus after the overlay is closed.
|
|
69
85
|
*
|
|
@@ -129,15 +145,15 @@ export const OverlayFocusMixin = (superClass) =>
|
|
|
129
145
|
* @protected
|
|
130
146
|
*/
|
|
131
147
|
_deepContains(node) {
|
|
132
|
-
if (this.contains(node)) {
|
|
148
|
+
if (this._contentRoot.contains(node)) {
|
|
133
149
|
return true;
|
|
134
150
|
}
|
|
135
151
|
let n = node;
|
|
136
152
|
const doc = node.ownerDocument;
|
|
137
|
-
// Walk from node to
|
|
138
|
-
while (n && n !== doc && n !== this) {
|
|
153
|
+
// Walk from node to content root or `document`
|
|
154
|
+
while (n && n !== doc && n !== this._contentRoot) {
|
|
139
155
|
n = n.parentNode || n.host;
|
|
140
156
|
}
|
|
141
|
-
return n === this;
|
|
157
|
+
return n === this._contentRoot;
|
|
142
158
|
}
|
|
143
159
|
};
|
|
@@ -4,19 +4,21 @@
|
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
import type { Constructor } from '@open-wc/dedupe-mixin';
|
|
7
|
-
import type { ControllerMixinClass } from '@vaadin/component-base/src/controller-mixin.js';
|
|
8
7
|
import type { OverlayFocusMixinClass } from './vaadin-overlay-focus-mixin.js';
|
|
9
8
|
import type { OverlayStackMixinClass } from './vaadin-overlay-stack-mixin.js';
|
|
10
9
|
|
|
10
|
+
export type OverlayBounds = {
|
|
11
|
+
top?: number | string;
|
|
12
|
+
left?: number | string;
|
|
13
|
+
width?: number | string;
|
|
14
|
+
height?: number | string;
|
|
15
|
+
};
|
|
16
|
+
|
|
11
17
|
export type OverlayRenderer = (root: HTMLElement, owner: HTMLElement, model?: object) => void;
|
|
12
18
|
|
|
13
19
|
export declare function OverlayMixin<T extends Constructor<HTMLElement>>(
|
|
14
20
|
base: T,
|
|
15
|
-
): Constructor<
|
|
16
|
-
Constructor<OverlayFocusMixinClass> &
|
|
17
|
-
Constructor<OverlayMixinClass> &
|
|
18
|
-
Constructor<OverlayStackMixinClass> &
|
|
19
|
-
T;
|
|
21
|
+
): Constructor<OverlayFocusMixinClass> & Constructor<OverlayMixinClass> & Constructor<OverlayStackMixinClass> & T;
|
|
20
22
|
|
|
21
23
|
export declare class OverlayMixinClass {
|
|
22
24
|
/**
|
|
@@ -63,6 +65,11 @@ export declare class OverlayMixinClass {
|
|
|
63
65
|
|
|
64
66
|
close(sourceEvent?: Event | null): void;
|
|
65
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Updates the coordinates of the overlay.
|
|
70
|
+
*/
|
|
71
|
+
setBounds(bounds: OverlayBounds, absolute?: boolean): void;
|
|
72
|
+
|
|
66
73
|
/**
|
|
67
74
|
* Requests an update for the content of the overlay.
|
|
68
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
|
|
@@ -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,17 +178,44 @@ export const OverlayMixin = (superClass) =>
|
|
|
171
178
|
* @param {Event=} sourceEvent
|
|
172
179
|
*/
|
|
173
180
|
close(sourceEvent) {
|
|
174
|
-
|
|
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(
|
|
180
|
-
|
|
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
|
}
|
|
184
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Updates the coordinates of the overlay.
|
|
198
|
+
* @param {!OverlayBoundsParam} bounds
|
|
199
|
+
* @param {boolean} absolute
|
|
200
|
+
*/
|
|
201
|
+
setBounds(bounds, absolute = true) {
|
|
202
|
+
const overlay = this.$.overlay;
|
|
203
|
+
const parsedBounds = { ...bounds };
|
|
204
|
+
|
|
205
|
+
if (absolute && overlay.style.position !== 'absolute') {
|
|
206
|
+
overlay.style.position = 'absolute';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
Object.keys(parsedBounds).forEach((arg) => {
|
|
210
|
+
// Allow setting width or height to `null`
|
|
211
|
+
if (parsedBounds[arg] !== null && !isNaN(parsedBounds[arg])) {
|
|
212
|
+
parsedBounds[arg] = `${parsedBounds[arg]}px`;
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
Object.assign(overlay.style, parsedBounds);
|
|
217
|
+
}
|
|
218
|
+
|
|
185
219
|
/** @private */
|
|
186
220
|
_detectIosNavbar() {
|
|
187
221
|
/* c8 ignore next 15 */
|
|
@@ -233,11 +267,11 @@ export const OverlayMixin = (superClass) =>
|
|
|
233
267
|
this._oldOpened = opened;
|
|
234
268
|
|
|
235
269
|
if (rendererChanged && hasOldRenderer) {
|
|
236
|
-
this.innerHTML = '';
|
|
270
|
+
this._contentRoot.innerHTML = '';
|
|
237
271
|
// Whenever a Lit-based renderer is used, it assigns a Lit part to the node it was rendered into.
|
|
238
272
|
// When clearing the rendered content, this part needs to be manually disposed of.
|
|
239
273
|
// Otherwise, using a Lit-based renderer on the same node will throw an exception or render nothing afterward.
|
|
240
|
-
delete this._$litPart$;
|
|
274
|
+
delete this._contentRoot._$litPart$;
|
|
241
275
|
}
|
|
242
276
|
|
|
243
277
|
if (opened && renderer && (rendererChanged || openedChanged || ownerOrModelChanged)) {
|
|
@@ -256,11 +290,23 @@ export const OverlayMixin = (superClass) =>
|
|
|
256
290
|
this._removeGlobalListeners();
|
|
257
291
|
this._exitModalState();
|
|
258
292
|
}
|
|
293
|
+
setOverlayStateAttribute(this, 'modeless', modeless);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** @private */
|
|
297
|
+
_withBackdropChanged(withBackdrop) {
|
|
298
|
+
setOverlayStateAttribute(this, 'with-backdrop', withBackdrop);
|
|
259
299
|
}
|
|
260
300
|
|
|
261
301
|
/** @private */
|
|
262
302
|
_openedChanged(opened, wasOpened) {
|
|
263
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
|
+
|
|
264
310
|
this._saveFocus();
|
|
265
311
|
|
|
266
312
|
this._animatedOpening();
|
|
@@ -269,7 +315,12 @@ export const OverlayMixin = (superClass) =>
|
|
|
269
315
|
setTimeout(() => {
|
|
270
316
|
this._trapFocus();
|
|
271
317
|
|
|
272
|
-
|
|
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);
|
|
273
324
|
});
|
|
274
325
|
});
|
|
275
326
|
|
|
@@ -346,14 +397,16 @@ export const OverlayMixin = (superClass) =>
|
|
|
346
397
|
|
|
347
398
|
/** @private */
|
|
348
399
|
_animatedOpening() {
|
|
349
|
-
if (this.
|
|
400
|
+
if (this._isAttached && this.hasAttribute('closing')) {
|
|
350
401
|
this._flushAnimation('closing');
|
|
351
402
|
}
|
|
352
403
|
this._attachOverlay();
|
|
404
|
+
this._appendAttachedInstance();
|
|
405
|
+
this.bringToFront();
|
|
353
406
|
if (!this.modeless) {
|
|
354
407
|
this._enterModalState();
|
|
355
408
|
}
|
|
356
|
-
this
|
|
409
|
+
setOverlayStateAttribute(this, 'opening', true);
|
|
357
410
|
|
|
358
411
|
if (this._shouldAnimate()) {
|
|
359
412
|
this._enqueueAnimation('opening', () => {
|
|
@@ -366,22 +419,20 @@ export const OverlayMixin = (superClass) =>
|
|
|
366
419
|
|
|
367
420
|
/** @private */
|
|
368
421
|
_attachOverlay() {
|
|
369
|
-
this.
|
|
370
|
-
this.parentNode.insertBefore(this._placeholder, this);
|
|
371
|
-
document.body.appendChild(this);
|
|
372
|
-
this.bringToFront();
|
|
422
|
+
this.showPopover();
|
|
373
423
|
}
|
|
374
424
|
|
|
375
425
|
/** @private */
|
|
376
426
|
_finishOpening() {
|
|
377
|
-
this
|
|
427
|
+
setOverlayStateAttribute(this, 'opening', false);
|
|
378
428
|
}
|
|
379
429
|
|
|
380
430
|
/** @private */
|
|
381
431
|
_finishClosing() {
|
|
382
432
|
this._detachOverlay();
|
|
433
|
+
this._removeAttachedInstance();
|
|
383
434
|
this.$.overlay.style.removeProperty('pointer-events');
|
|
384
|
-
this
|
|
435
|
+
setOverlayStateAttribute(this, 'closing', false);
|
|
385
436
|
this.dispatchEvent(new CustomEvent('vaadin-overlay-closed'));
|
|
386
437
|
}
|
|
387
438
|
|
|
@@ -390,9 +441,9 @@ export const OverlayMixin = (superClass) =>
|
|
|
390
441
|
if (this.hasAttribute('opening')) {
|
|
391
442
|
this._flushAnimation('opening');
|
|
392
443
|
}
|
|
393
|
-
if (this.
|
|
444
|
+
if (this._isAttached) {
|
|
394
445
|
this._exitModalState();
|
|
395
|
-
this
|
|
446
|
+
setOverlayStateAttribute(this, 'closing', true);
|
|
396
447
|
this.dispatchEvent(new CustomEvent('vaadin-overlay-closing'));
|
|
397
448
|
|
|
398
449
|
if (this._shouldAnimate()) {
|
|
@@ -407,8 +458,7 @@ export const OverlayMixin = (superClass) =>
|
|
|
407
458
|
|
|
408
459
|
/** @private */
|
|
409
460
|
_detachOverlay() {
|
|
410
|
-
this.
|
|
411
|
-
this._placeholder.parentNode.removeChild(this._placeholder);
|
|
461
|
+
this.hidePopover();
|
|
412
462
|
}
|
|
413
463
|
|
|
414
464
|
/** @private */
|
|
@@ -452,7 +502,6 @@ export const OverlayMixin = (superClass) =>
|
|
|
452
502
|
}
|
|
453
503
|
|
|
454
504
|
const evt = new CustomEvent('vaadin-overlay-outside-click', {
|
|
455
|
-
bubbles: true,
|
|
456
505
|
cancelable: true,
|
|
457
506
|
detail: { sourceEvent: event },
|
|
458
507
|
});
|
|
@@ -479,7 +528,6 @@ export const OverlayMixin = (superClass) =>
|
|
|
479
528
|
|
|
480
529
|
if (event.key === 'Escape') {
|
|
481
530
|
const evt = new CustomEvent('vaadin-overlay-escape-press', {
|
|
482
|
-
bubbles: true,
|
|
483
531
|
cancelable: true,
|
|
484
532
|
detail: { sourceEvent: event },
|
|
485
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
|
|
275
|
-
this
|
|
274
|
+
setOverlayStateAttribute(this, 'bottom-aligned', !shouldAlignStartVertically);
|
|
275
|
+
setOverlayStateAttribute(this, 'top-aligned', shouldAlignStartVertically);
|
|
276
276
|
|
|
277
|
-
this
|
|
278
|
-
this
|
|
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
|
|
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.
|
|
@@ -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 =
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
//
|
|
89
|
-
if (
|
|
90
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
+
}
|
package/src/vaadin-overlay.js
CHANGED
|
@@ -3,15 +3,14 @@
|
|
|
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 { html,
|
|
6
|
+
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
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
|
|
10
|
+
import { LumoInjectionMixin } from '@vaadin/vaadin-themable-mixin/lumo-injection-mixin.js';
|
|
11
|
+
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
12
|
+
import { overlayStyles } from './styles/vaadin-overlay-core-styles.js';
|
|
11
13
|
import { OverlayMixin } from './vaadin-overlay-mixin.js';
|
|
12
|
-
import { overlayStyles } from './vaadin-overlay-styles.js';
|
|
13
|
-
|
|
14
|
-
registerStyles('vaadin-overlay', overlayStyles, { moduleId: 'vaadin-overlay-styles' });
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* `<vaadin-overlay>` is a Web Component for creating overlays. The content of the overlay
|
|
@@ -78,10 +77,19 @@ registerStyles('vaadin-overlay', overlayStyles, { moduleId: 'vaadin-overlay-styl
|
|
|
78
77
|
* @mixes DirMixin
|
|
79
78
|
* @mixes OverlayMixin
|
|
80
79
|
*/
|
|
81
|
-
class Overlay extends OverlayMixin(ThemableMixin(
|
|
82
|
-
static get
|
|
80
|
+
class Overlay extends OverlayMixin(DirMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(LitElement))))) {
|
|
81
|
+
static get is() {
|
|
82
|
+
return 'vaadin-overlay';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static get styles() {
|
|
86
|
+
return overlayStyles;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** @protected */
|
|
90
|
+
render() {
|
|
83
91
|
return html`
|
|
84
|
-
<div id="backdrop" part="backdrop" hidden
|
|
92
|
+
<div id="backdrop" part="backdrop" ?hidden="${!this.withBackdrop}"></div>
|
|
85
93
|
<div part="overlay" id="overlay" tabindex="0">
|
|
86
94
|
<div part="content" id="content">
|
|
87
95
|
<slot></slot>
|
|
@@ -90,17 +98,6 @@ class Overlay extends OverlayMixin(ThemableMixin(DirMixin(PolymerElement))) {
|
|
|
90
98
|
`;
|
|
91
99
|
}
|
|
92
100
|
|
|
93
|
-
static get is() {
|
|
94
|
-
return 'vaadin-overlay';
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** @protected */
|
|
98
|
-
ready() {
|
|
99
|
-
super.ready();
|
|
100
|
-
|
|
101
|
-
processTemplates(this);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
101
|
/**
|
|
105
102
|
* @event vaadin-overlay-open
|
|
106
103
|
* Fired after the overlay is opened.
|
|
@@ -1,47 +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 { html, LitElement } from 'lit';
|
|
7
|
-
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
|
|
8
|
-
import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
|
|
9
|
-
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
|
|
10
|
-
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
11
|
-
import { OverlayMixin } from './vaadin-overlay-mixin.js';
|
|
12
|
-
import { overlayStyles } from './vaadin-overlay-styles.js';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* LitElement based version of `<vaadin-overlay>` web component.
|
|
16
|
-
*
|
|
17
|
-
* ## Disclaimer
|
|
18
|
-
*
|
|
19
|
-
* This component is an experiment and not yet a part of Vaadin platform.
|
|
20
|
-
* There is no ETA regarding specific Vaadin version where it'll land.
|
|
21
|
-
* Feel free to try this code in your apps as per Apache 2.0 license.
|
|
22
|
-
*/
|
|
23
|
-
class Overlay extends OverlayMixin(DirMixin(ThemableMixin(PolylitMixin(LitElement)))) {
|
|
24
|
-
static get is() {
|
|
25
|
-
return 'vaadin-overlay';
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
static get styles() {
|
|
29
|
-
return overlayStyles;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** @protected */
|
|
33
|
-
render() {
|
|
34
|
-
return html`
|
|
35
|
-
<div id="backdrop" part="backdrop" ?hidden="${!this.withBackdrop}"></div>
|
|
36
|
-
<div part="overlay" id="overlay" tabindex="0">
|
|
37
|
-
<div part="content" id="content">
|
|
38
|
-
<slot></slot>
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
`;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
defineCustomElement(Overlay);
|
|
46
|
-
|
|
47
|
-
export { Overlay };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|