@vaadin/overlay 24.2.0-alpha2 → 24.2.0-alpha4
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 +7 -7
- package/src/vaadin-overlay-mixin.d.ts +81 -0
- package/src/vaadin-overlay-mixin.js +472 -0
- package/src/vaadin-overlay-styles.js +66 -0
- package/src/vaadin-overlay.d.ts +3 -64
- package/src/vaadin-overlay.js +7 -538
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/overlay",
|
|
3
|
-
"version": "24.2.0-
|
|
3
|
+
"version": "24.2.0-alpha4",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@open-wc/dedupe-mixin": "^1.3.0",
|
|
38
38
|
"@polymer/polymer": "^3.0.0",
|
|
39
|
-
"@vaadin/a11y-base": "24.2.0-
|
|
40
|
-
"@vaadin/component-base": "24.2.0-
|
|
41
|
-
"@vaadin/vaadin-lumo-styles": "24.2.0-
|
|
42
|
-
"@vaadin/vaadin-material-styles": "24.2.0-
|
|
43
|
-
"@vaadin/vaadin-themable-mixin": "24.2.0-
|
|
39
|
+
"@vaadin/a11y-base": "24.2.0-alpha4",
|
|
40
|
+
"@vaadin/component-base": "24.2.0-alpha4",
|
|
41
|
+
"@vaadin/vaadin-lumo-styles": "24.2.0-alpha4",
|
|
42
|
+
"@vaadin/vaadin-material-styles": "24.2.0-alpha4",
|
|
43
|
+
"@vaadin/vaadin-themable-mixin": "24.2.0-alpha4"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@esm-bundle/chai": "^4.3.4",
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"lit": "^2.0.0",
|
|
49
49
|
"sinon": "^13.0.2"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "aaf7c5ebfea62628210eead4229be1718ac6b129"
|
|
52
52
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2017 - 2023 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
import type { Constructor } from '@open-wc/dedupe-mixin';
|
|
7
|
+
import type { ControllerMixinClass } from '@vaadin/component-base/src/controller-mixin.js';
|
|
8
|
+
import type { OverlayFocusMixinClass } from './vaadin-overlay-focus-mixin.js';
|
|
9
|
+
import type { OverlayStackMixinClass } from './vaadin-overlay-stack-mixin.js';
|
|
10
|
+
|
|
11
|
+
export type OverlayRenderer = (root: HTMLElement, owner: HTMLElement, model?: object) => void;
|
|
12
|
+
|
|
13
|
+
export declare function OverlayMixin<T extends Constructor<HTMLElement>>(
|
|
14
|
+
base: T,
|
|
15
|
+
): Constructor<ControllerMixinClass> &
|
|
16
|
+
Constructor<OverlayFocusMixinClass> &
|
|
17
|
+
Constructor<OverlayMixinClass> &
|
|
18
|
+
Constructor<OverlayStackMixinClass> &
|
|
19
|
+
T;
|
|
20
|
+
|
|
21
|
+
export declare class OverlayMixinClass {
|
|
22
|
+
/**
|
|
23
|
+
* Owner element passed with renderer function
|
|
24
|
+
*/
|
|
25
|
+
owner: HTMLElement | null;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Custom function for rendering the content of the overlay.
|
|
29
|
+
* Receives three arguments:
|
|
30
|
+
*
|
|
31
|
+
* - `root` The root container DOM element. Append your content to it.
|
|
32
|
+
* - `owner` The host element of the renderer function.
|
|
33
|
+
* - `model` The object with the properties related with rendering.
|
|
34
|
+
*/
|
|
35
|
+
renderer: OverlayRenderer | null | undefined;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* When true the overlay has backdrop on top of content when opened.
|
|
39
|
+
*/
|
|
40
|
+
withBackdrop: boolean;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Object with properties that is passed to `renderer` function
|
|
44
|
+
*/
|
|
45
|
+
model: object | null | undefined;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* When true, the overlay is visible and attached to body.
|
|
49
|
+
*/
|
|
50
|
+
opened: boolean | null | undefined;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* When true the overlay won't disable the main content, showing
|
|
54
|
+
* it doesn't change the functionality of the user interface.
|
|
55
|
+
*/
|
|
56
|
+
modeless: boolean;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* When set to true, the overlay is hidden. This also closes the overlay
|
|
60
|
+
* immediately in case there is a closing animation in progress.
|
|
61
|
+
*/
|
|
62
|
+
hidden: boolean;
|
|
63
|
+
|
|
64
|
+
close(sourceEvent?: Event | null): void;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Requests an update for the content of the overlay.
|
|
68
|
+
* While performing the update, it invokes the renderer passed in the `renderer` property.
|
|
69
|
+
*
|
|
70
|
+
* It is not guaranteed that the update happens immediately (synchronously) after it is requested.
|
|
71
|
+
*/
|
|
72
|
+
requestContentUpdate(): void;
|
|
73
|
+
|
|
74
|
+
protected _flushAnimation(type: 'closing' | 'opening'): void;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Whether to close the overlay on outside click or not.
|
|
78
|
+
* Override this method to customize the closing logic.
|
|
79
|
+
*/
|
|
80
|
+
protected _shouldCloseOnOutsideClick(event: Event): boolean;
|
|
81
|
+
}
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2017 - 2023 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js';
|
|
7
|
+
import { isIOS } from '@vaadin/component-base/src/browser-utils.js';
|
|
8
|
+
import { OverlayFocusMixin } from './vaadin-overlay-focus-mixin.js';
|
|
9
|
+
import { OverlayStackMixin } from './vaadin-overlay-stack-mixin.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @polymerMixin
|
|
13
|
+
* @mixes OverlayFocusMixin
|
|
14
|
+
* @mixes OverlayStackMixin
|
|
15
|
+
*/
|
|
16
|
+
export const OverlayMixin = (superClass) =>
|
|
17
|
+
class OverlayMixin extends OverlayFocusMixin(OverlayStackMixin(superClass)) {
|
|
18
|
+
static get properties() {
|
|
19
|
+
return {
|
|
20
|
+
/**
|
|
21
|
+
* When true, the overlay is visible and attached to body.
|
|
22
|
+
*/
|
|
23
|
+
opened: {
|
|
24
|
+
type: Boolean,
|
|
25
|
+
notify: true,
|
|
26
|
+
observer: '_openedChanged',
|
|
27
|
+
reflectToAttribute: true,
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Owner element passed with renderer function
|
|
32
|
+
* @type {HTMLElement}
|
|
33
|
+
*/
|
|
34
|
+
owner: {
|
|
35
|
+
type: Object,
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Object with properties that is passed to `renderer` function
|
|
40
|
+
*/
|
|
41
|
+
model: {
|
|
42
|
+
type: Object,
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Custom function for rendering the content of the overlay.
|
|
47
|
+
* Receives three arguments:
|
|
48
|
+
*
|
|
49
|
+
* - `root` The root container DOM element. Append your content to it.
|
|
50
|
+
* - `owner` The host element of the renderer function.
|
|
51
|
+
* - `model` The object with the properties related with rendering.
|
|
52
|
+
* @type {OverlayRenderer | null | undefined}
|
|
53
|
+
*/
|
|
54
|
+
renderer: {
|
|
55
|
+
type: Object,
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* When true the overlay won't disable the main content, showing
|
|
60
|
+
* it doesn't change the functionality of the user interface.
|
|
61
|
+
* @type {boolean}
|
|
62
|
+
*/
|
|
63
|
+
modeless: {
|
|
64
|
+
type: Boolean,
|
|
65
|
+
value: false,
|
|
66
|
+
reflectToAttribute: true,
|
|
67
|
+
observer: '_modelessChanged',
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* When set to true, the overlay is hidden. This also closes the overlay
|
|
72
|
+
* immediately in case there is a closing animation in progress.
|
|
73
|
+
* @type {boolean}
|
|
74
|
+
*/
|
|
75
|
+
hidden: {
|
|
76
|
+
type: Boolean,
|
|
77
|
+
reflectToAttribute: true,
|
|
78
|
+
observer: '_hiddenChanged',
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* When true the overlay has backdrop on top of content when opened.
|
|
83
|
+
* @type {boolean}
|
|
84
|
+
*/
|
|
85
|
+
withBackdrop: {
|
|
86
|
+
type: Boolean,
|
|
87
|
+
value: false,
|
|
88
|
+
reflectToAttribute: true,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static get observers() {
|
|
94
|
+
return ['_rendererOrDataChanged(renderer, owner, model, opened)'];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
constructor() {
|
|
98
|
+
super();
|
|
99
|
+
|
|
100
|
+
this._boundMouseDownListener = this._mouseDownListener.bind(this);
|
|
101
|
+
this._boundMouseUpListener = this._mouseUpListener.bind(this);
|
|
102
|
+
this._boundOutsideClickListener = this._outsideClickListener.bind(this);
|
|
103
|
+
this._boundKeydownListener = this._keydownListener.bind(this);
|
|
104
|
+
|
|
105
|
+
/* c8 ignore next 3 */
|
|
106
|
+
if (isIOS) {
|
|
107
|
+
this._boundIosResizeListener = () => this._detectIosNavbar();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** @protected */
|
|
112
|
+
ready() {
|
|
113
|
+
super.ready();
|
|
114
|
+
|
|
115
|
+
// Need to add dummy click listeners to this and the backdrop or else
|
|
116
|
+
// the document click event listener (_outsideClickListener) may never
|
|
117
|
+
// get invoked on iOS Safari (reproducible in <vaadin-dialog>
|
|
118
|
+
// and <vaadin-context-menu>).
|
|
119
|
+
this.addEventListener('click', () => {});
|
|
120
|
+
this.$.backdrop.addEventListener('click', () => {});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** @protected */
|
|
124
|
+
connectedCallback() {
|
|
125
|
+
super.connectedCallback();
|
|
126
|
+
|
|
127
|
+
/* c8 ignore next 3 */
|
|
128
|
+
if (this._boundIosResizeListener) {
|
|
129
|
+
this._detectIosNavbar();
|
|
130
|
+
window.addEventListener('resize', this._boundIosResizeListener);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** @protected */
|
|
135
|
+
disconnectedCallback() {
|
|
136
|
+
super.disconnectedCallback();
|
|
137
|
+
|
|
138
|
+
/* c8 ignore next 3 */
|
|
139
|
+
if (this._boundIosResizeListener) {
|
|
140
|
+
window.removeEventListener('resize', this._boundIosResizeListener);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Requests an update for the content of the overlay.
|
|
146
|
+
* While performing the update, it invokes the renderer passed in the `renderer` property.
|
|
147
|
+
*
|
|
148
|
+
* It is not guaranteed that the update happens immediately (synchronously) after it is requested.
|
|
149
|
+
*/
|
|
150
|
+
requestContentUpdate() {
|
|
151
|
+
if (this.renderer) {
|
|
152
|
+
this.renderer.call(this.owner, this, this.owner, this.model);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @param {Event=} sourceEvent
|
|
158
|
+
*/
|
|
159
|
+
close(sourceEvent) {
|
|
160
|
+
const evt = new CustomEvent('vaadin-overlay-close', {
|
|
161
|
+
bubbles: true,
|
|
162
|
+
cancelable: true,
|
|
163
|
+
detail: { sourceEvent },
|
|
164
|
+
});
|
|
165
|
+
this.dispatchEvent(evt);
|
|
166
|
+
if (!evt.defaultPrevented) {
|
|
167
|
+
this.opened = false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** @private */
|
|
172
|
+
_detectIosNavbar() {
|
|
173
|
+
/* c8 ignore next 15 */
|
|
174
|
+
if (!this.opened) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const innerHeight = window.innerHeight;
|
|
179
|
+
const innerWidth = window.innerWidth;
|
|
180
|
+
|
|
181
|
+
const landscape = innerWidth > innerHeight;
|
|
182
|
+
|
|
183
|
+
const clientHeight = document.documentElement.clientHeight;
|
|
184
|
+
|
|
185
|
+
if (landscape && clientHeight > innerHeight) {
|
|
186
|
+
this.style.setProperty('--vaadin-overlay-viewport-bottom', `${clientHeight - innerHeight}px`);
|
|
187
|
+
} else {
|
|
188
|
+
this.style.setProperty('--vaadin-overlay-viewport-bottom', '0');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** @private */
|
|
193
|
+
_addGlobalListeners() {
|
|
194
|
+
document.addEventListener('mousedown', this._boundMouseDownListener);
|
|
195
|
+
document.addEventListener('mouseup', this._boundMouseUpListener);
|
|
196
|
+
// Firefox leaks click to document on contextmenu even if prevented
|
|
197
|
+
// https://bugzilla.mozilla.org/show_bug.cgi?id=990614
|
|
198
|
+
document.documentElement.addEventListener('click', this._boundOutsideClickListener, true);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** @private */
|
|
202
|
+
_removeGlobalListeners() {
|
|
203
|
+
document.removeEventListener('mousedown', this._boundMouseDownListener);
|
|
204
|
+
document.removeEventListener('mouseup', this._boundMouseUpListener);
|
|
205
|
+
document.documentElement.removeEventListener('click', this._boundOutsideClickListener, true);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** @private */
|
|
209
|
+
_rendererOrDataChanged(renderer, owner, model, opened) {
|
|
210
|
+
const ownerOrModelChanged = this._oldOwner !== owner || this._oldModel !== model;
|
|
211
|
+
this._oldModel = model;
|
|
212
|
+
this._oldOwner = owner;
|
|
213
|
+
|
|
214
|
+
const rendererChanged = this._oldRenderer !== renderer;
|
|
215
|
+
this._oldRenderer = renderer;
|
|
216
|
+
|
|
217
|
+
const openedChanged = this._oldOpened !== opened;
|
|
218
|
+
this._oldOpened = opened;
|
|
219
|
+
|
|
220
|
+
if (rendererChanged) {
|
|
221
|
+
this.innerHTML = '';
|
|
222
|
+
// Whenever a Lit-based renderer is used, it assigns a Lit part to the node it was rendered into.
|
|
223
|
+
// When clearing the rendered content, this part needs to be manually disposed of.
|
|
224
|
+
// Otherwise, using a Lit-based renderer on the same node will throw an exception or render nothing afterward.
|
|
225
|
+
delete this._$litPart$;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (opened && renderer && (rendererChanged || openedChanged || ownerOrModelChanged)) {
|
|
229
|
+
this.requestContentUpdate();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** @private */
|
|
234
|
+
_modelessChanged(modeless) {
|
|
235
|
+
if (!modeless) {
|
|
236
|
+
if (this.opened) {
|
|
237
|
+
this._addGlobalListeners();
|
|
238
|
+
this._enterModalState();
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
this._removeGlobalListeners();
|
|
242
|
+
this._exitModalState();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** @private */
|
|
247
|
+
_openedChanged(opened, wasOpened) {
|
|
248
|
+
if (opened) {
|
|
249
|
+
this._saveFocus();
|
|
250
|
+
|
|
251
|
+
this._animatedOpening();
|
|
252
|
+
|
|
253
|
+
afterNextRender(this, () => {
|
|
254
|
+
this._trapFocus();
|
|
255
|
+
|
|
256
|
+
const evt = new CustomEvent('vaadin-overlay-open', { bubbles: true });
|
|
257
|
+
this.dispatchEvent(evt);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
document.addEventListener('keydown', this._boundKeydownListener);
|
|
261
|
+
|
|
262
|
+
if (!this.modeless) {
|
|
263
|
+
this._addGlobalListeners();
|
|
264
|
+
}
|
|
265
|
+
} else if (wasOpened) {
|
|
266
|
+
this._resetFocus();
|
|
267
|
+
|
|
268
|
+
this._animatedClosing();
|
|
269
|
+
|
|
270
|
+
document.removeEventListener('keydown', this._boundKeydownListener);
|
|
271
|
+
|
|
272
|
+
if (!this.modeless) {
|
|
273
|
+
this._removeGlobalListeners();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** @private */
|
|
279
|
+
_hiddenChanged(hidden) {
|
|
280
|
+
if (hidden && this.hasAttribute('closing')) {
|
|
281
|
+
this._flushAnimation('closing');
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @return {boolean}
|
|
287
|
+
* @private
|
|
288
|
+
*/
|
|
289
|
+
_shouldAnimate() {
|
|
290
|
+
const style = getComputedStyle(this);
|
|
291
|
+
const name = style.getPropertyValue('animation-name');
|
|
292
|
+
const hidden = style.getPropertyValue('display') === 'none';
|
|
293
|
+
return !hidden && name && name !== 'none';
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* @param {string} type
|
|
298
|
+
* @param {Function} callback
|
|
299
|
+
* @private
|
|
300
|
+
*/
|
|
301
|
+
_enqueueAnimation(type, callback) {
|
|
302
|
+
const handler = `__${type}Handler`;
|
|
303
|
+
const listener = (event) => {
|
|
304
|
+
if (event && event.target !== this) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
callback();
|
|
308
|
+
this.removeEventListener('animationend', listener);
|
|
309
|
+
delete this[handler];
|
|
310
|
+
};
|
|
311
|
+
this[handler] = listener;
|
|
312
|
+
this.addEventListener('animationend', listener);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* @param {string} type
|
|
317
|
+
* @protected
|
|
318
|
+
*/
|
|
319
|
+
_flushAnimation(type) {
|
|
320
|
+
const handler = `__${type}Handler`;
|
|
321
|
+
if (typeof this[handler] === 'function') {
|
|
322
|
+
this[handler]();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** @private */
|
|
327
|
+
_animatedOpening() {
|
|
328
|
+
if (this.parentNode === document.body && this.hasAttribute('closing')) {
|
|
329
|
+
this._flushAnimation('closing');
|
|
330
|
+
}
|
|
331
|
+
this._attachOverlay();
|
|
332
|
+
if (!this.modeless) {
|
|
333
|
+
this._enterModalState();
|
|
334
|
+
}
|
|
335
|
+
this.setAttribute('opening', '');
|
|
336
|
+
|
|
337
|
+
if (this._shouldAnimate()) {
|
|
338
|
+
this._enqueueAnimation('opening', () => {
|
|
339
|
+
this._finishOpening();
|
|
340
|
+
});
|
|
341
|
+
} else {
|
|
342
|
+
this._finishOpening();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** @private */
|
|
347
|
+
_attachOverlay() {
|
|
348
|
+
this._placeholder = document.createComment('vaadin-overlay-placeholder');
|
|
349
|
+
this.parentNode.insertBefore(this._placeholder, this);
|
|
350
|
+
document.body.appendChild(this);
|
|
351
|
+
this.bringToFront();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** @private */
|
|
355
|
+
_finishOpening() {
|
|
356
|
+
this.removeAttribute('opening');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** @private */
|
|
360
|
+
_finishClosing() {
|
|
361
|
+
this._detachOverlay();
|
|
362
|
+
this.$.overlay.style.removeProperty('pointer-events');
|
|
363
|
+
this.removeAttribute('closing');
|
|
364
|
+
this.dispatchEvent(new CustomEvent('vaadin-overlay-closed'));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** @private */
|
|
368
|
+
_animatedClosing() {
|
|
369
|
+
if (this.hasAttribute('opening')) {
|
|
370
|
+
this._flushAnimation('opening');
|
|
371
|
+
}
|
|
372
|
+
if (this._placeholder) {
|
|
373
|
+
this._exitModalState();
|
|
374
|
+
this.setAttribute('closing', '');
|
|
375
|
+
this.dispatchEvent(new CustomEvent('vaadin-overlay-closing'));
|
|
376
|
+
|
|
377
|
+
if (this._shouldAnimate()) {
|
|
378
|
+
this._enqueueAnimation('closing', () => {
|
|
379
|
+
this._finishClosing();
|
|
380
|
+
});
|
|
381
|
+
} else {
|
|
382
|
+
this._finishClosing();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** @private */
|
|
388
|
+
_detachOverlay() {
|
|
389
|
+
this._placeholder.parentNode.insertBefore(this, this._placeholder);
|
|
390
|
+
this._placeholder.parentNode.removeChild(this._placeholder);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** @private */
|
|
394
|
+
_mouseDownListener(event) {
|
|
395
|
+
this._mouseDownInside = event.composedPath().indexOf(this.$.overlay) >= 0;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** @private */
|
|
399
|
+
_mouseUpListener(event) {
|
|
400
|
+
this._mouseUpInside = event.composedPath().indexOf(this.$.overlay) >= 0;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Whether to close the overlay on outside click or not.
|
|
405
|
+
* Override this method to customize the closing logic.
|
|
406
|
+
*
|
|
407
|
+
* @param {Event} _event
|
|
408
|
+
* @return {boolean}
|
|
409
|
+
* @protected
|
|
410
|
+
*/
|
|
411
|
+
_shouldCloseOnOutsideClick(_event) {
|
|
412
|
+
return this._last;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Outside click listener used in capture phase to close the overlay before
|
|
417
|
+
* propagating the event to the listener on the element that triggered it.
|
|
418
|
+
* Otherwise, calling `open()` would result in closing and re-opening.
|
|
419
|
+
*
|
|
420
|
+
* @private
|
|
421
|
+
*/
|
|
422
|
+
_outsideClickListener(event) {
|
|
423
|
+
if (event.composedPath().includes(this.$.overlay) || this._mouseDownInside || this._mouseUpInside) {
|
|
424
|
+
this._mouseDownInside = false;
|
|
425
|
+
this._mouseUpInside = false;
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (!this._shouldCloseOnOutsideClick(event)) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const evt = new CustomEvent('vaadin-overlay-outside-click', {
|
|
434
|
+
bubbles: true,
|
|
435
|
+
cancelable: true,
|
|
436
|
+
detail: { sourceEvent: event },
|
|
437
|
+
});
|
|
438
|
+
this.dispatchEvent(evt);
|
|
439
|
+
|
|
440
|
+
if (this.opened && !evt.defaultPrevented) {
|
|
441
|
+
this.close(event);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Listener used to close whe overlay on Escape press, if it is the last one.
|
|
447
|
+
* @private
|
|
448
|
+
*/
|
|
449
|
+
_keydownListener(event) {
|
|
450
|
+
if (!this._last) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Only close modeless overlay on Esc press when it contains focus
|
|
455
|
+
if (this.modeless && !event.composedPath().includes(this.$.overlay)) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (event.key === 'Escape') {
|
|
460
|
+
const evt = new CustomEvent('vaadin-overlay-escape-press', {
|
|
461
|
+
bubbles: true,
|
|
462
|
+
cancelable: true,
|
|
463
|
+
detail: { sourceEvent: event },
|
|
464
|
+
});
|
|
465
|
+
this.dispatchEvent(evt);
|
|
466
|
+
|
|
467
|
+
if (this.opened && !evt.defaultPrevented) {
|
|
468
|
+
this.close(event);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2017 - 2023 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
import { 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
|
+
display: none !important;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
[part='overlay'] {
|
|
47
|
+
-webkit-overflow-scrolling: touch;
|
|
48
|
+
overflow: auto;
|
|
49
|
+
pointer-events: auto;
|
|
50
|
+
|
|
51
|
+
/* Prevent overflowing the host */
|
|
52
|
+
max-width: 100%;
|
|
53
|
+
box-sizing: border-box;
|
|
54
|
+
|
|
55
|
+
-webkit-tap-highlight-color: initial; /* reenable tap highlight inside */
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
[part='backdrop'] {
|
|
59
|
+
z-index: -1;
|
|
60
|
+
content: '';
|
|
61
|
+
background: rgba(0, 0, 0, 0.5);
|
|
62
|
+
position: fixed;
|
|
63
|
+
inset: 0;
|
|
64
|
+
pointer-events: auto;
|
|
65
|
+
}
|
|
66
|
+
`;
|
package/src/vaadin-overlay.d.ts
CHANGED
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
|
|
7
7
|
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
8
|
-
import {
|
|
9
|
-
import { OverlayStackMixin } from './vaadin-overlay-stack-mixin.js';
|
|
8
|
+
import { OverlayMixin } from './vaadin-overlay-mixin.js';
|
|
10
9
|
|
|
11
|
-
export
|
|
10
|
+
export { OverlayRenderer } from './vaadin-overlay-mixin.js';
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* Fired when the `opened` property changes.
|
|
@@ -120,59 +119,7 @@ export type OverlayEventMap = HTMLElementEventMap & OverlayCustomEventMap;
|
|
|
120
119
|
* @fires {CustomEvent} vaadin-overlay-outside-click - Fired before the overlay is closed on outside click. Calling `preventDefault()` on the event cancels the closing.
|
|
121
120
|
* @fires {CustomEvent} vaadin-overlay-escape-press - Fired before the overlay is closed on Escape key press. Calling `preventDefault()` on the event cancels the closing.
|
|
122
121
|
*/
|
|
123
|
-
declare class Overlay extends
|
|
124
|
-
/**
|
|
125
|
-
* When true, the overlay is visible and attached to body.
|
|
126
|
-
*/
|
|
127
|
-
opened: boolean | null | undefined;
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Owner element passed with renderer function
|
|
131
|
-
*/
|
|
132
|
-
owner: HTMLElement | null;
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Custom function for rendering the content of the overlay.
|
|
136
|
-
* Receives three arguments:
|
|
137
|
-
*
|
|
138
|
-
* - `root` The root container DOM element. Append your content to it.
|
|
139
|
-
* - `owner` The host element of the renderer function.
|
|
140
|
-
* - `model` The object with the properties related with rendering.
|
|
141
|
-
*/
|
|
142
|
-
renderer: OverlayRenderer | null | undefined;
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* When true the overlay has backdrop on top of content when opened.
|
|
146
|
-
*/
|
|
147
|
-
withBackdrop: boolean;
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Object with properties that is passed to `renderer` function
|
|
151
|
-
*/
|
|
152
|
-
model: object | null | undefined;
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* When true the overlay won't disable the main content, showing
|
|
156
|
-
* it doesn't change the functionality of the user interface.
|
|
157
|
-
*/
|
|
158
|
-
modeless: boolean;
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* When set to true, the overlay is hidden. This also closes the overlay
|
|
162
|
-
* immediately in case there is a closing animation in progress.
|
|
163
|
-
*/
|
|
164
|
-
hidden: boolean;
|
|
165
|
-
|
|
166
|
-
close(sourceEvent?: Event | null): void;
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Requests an update for the content of the overlay.
|
|
170
|
-
* While performing the update, it invokes the renderer passed in the `renderer` property.
|
|
171
|
-
*
|
|
172
|
-
* It is not guaranteed that the update happens immediately (synchronously) after it is requested.
|
|
173
|
-
*/
|
|
174
|
-
requestContentUpdate(): void;
|
|
175
|
-
|
|
122
|
+
declare class Overlay extends OverlayMixin(ThemableMixin(DirMixin(HTMLElement))) {
|
|
176
123
|
addEventListener<K extends keyof OverlayEventMap>(
|
|
177
124
|
type: K,
|
|
178
125
|
listener: (this: Overlay, ev: OverlayEventMap[K]) => void,
|
|
@@ -184,14 +131,6 @@ declare class Overlay extends OverlayStackMixin(OverlayFocusMixin(ThemableMixin(
|
|
|
184
131
|
listener: (this: Overlay, ev: OverlayEventMap[K]) => void,
|
|
185
132
|
options?: EventListenerOptions | boolean,
|
|
186
133
|
): void;
|
|
187
|
-
|
|
188
|
-
protected _flushAnimation(type: 'closing' | 'opening'): void;
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Whether to close the overlay on outside click or not.
|
|
192
|
-
* Override this method to customize the closing logic.
|
|
193
|
-
*/
|
|
194
|
-
protected _shouldCloseOnOutsideClick(event: Event): boolean;
|
|
195
134
|
}
|
|
196
135
|
|
|
197
136
|
declare global {
|
package/src/vaadin-overlay.js
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
* Copyright (c) 2017 - 2023 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
|
-
import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js';
|
|
7
6
|
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
|
|
8
|
-
import { isIOS } from '@vaadin/component-base/src/browser-utils.js';
|
|
9
7
|
import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
|
|
10
8
|
import { processTemplates } from '@vaadin/component-base/src/templates.js';
|
|
11
|
-
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
9
|
+
import { registerStyles, ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
10
|
+
import { OverlayMixin } from './vaadin-overlay-mixin.js';
|
|
11
|
+
import { overlayStyles } from './vaadin-overlay-styles.js';
|
|
12
|
+
|
|
13
|
+
registerStyles('vaadin-overlay', overlayStyles, { moduleId: 'vaadin-overlay-styles' });
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* `<vaadin-overlay>` is a Web Component for creating overlays. The content of the overlay
|
|
@@ -74,77 +74,11 @@ import { OverlayStackMixin } from './vaadin-overlay-stack-mixin.js';
|
|
|
74
74
|
* @extends HTMLElement
|
|
75
75
|
* @mixes ThemableMixin
|
|
76
76
|
* @mixes DirMixin
|
|
77
|
-
* @mixes
|
|
78
|
-
* @mixes OverlayStackMixin
|
|
77
|
+
* @mixes OverlayMixin
|
|
79
78
|
*/
|
|
80
|
-
class Overlay extends
|
|
79
|
+
class Overlay extends OverlayMixin(ThemableMixin(DirMixin(PolymerElement))) {
|
|
81
80
|
static get template() {
|
|
82
81
|
return html`
|
|
83
|
-
<style>
|
|
84
|
-
:host {
|
|
85
|
-
z-index: 200;
|
|
86
|
-
position: fixed;
|
|
87
|
-
|
|
88
|
-
/* Despite of what the names say, <vaadin-overlay> is just a container
|
|
89
|
-
for position/sizing/alignment. The actual overlay is the overlay part. */
|
|
90
|
-
|
|
91
|
-
/* Default position constraints: the entire viewport. Note: themes can
|
|
92
|
-
override this to introduce gaps between the overlay and the viewport. */
|
|
93
|
-
top: 0;
|
|
94
|
-
right: 0;
|
|
95
|
-
bottom: var(--vaadin-overlay-viewport-bottom);
|
|
96
|
-
left: 0;
|
|
97
|
-
|
|
98
|
-
/* Use flexbox alignment for the overlay part. */
|
|
99
|
-
display: flex;
|
|
100
|
-
flex-direction: column; /* makes dropdowns sizing easier */
|
|
101
|
-
/* Align to center by default. */
|
|
102
|
-
align-items: center;
|
|
103
|
-
justify-content: center;
|
|
104
|
-
|
|
105
|
-
/* Allow centering when max-width/max-height applies. */
|
|
106
|
-
margin: auto;
|
|
107
|
-
|
|
108
|
-
/* The host is not clickable, only the overlay part is. */
|
|
109
|
-
pointer-events: none;
|
|
110
|
-
|
|
111
|
-
/* Remove tap highlight on touch devices. */
|
|
112
|
-
-webkit-tap-highlight-color: transparent;
|
|
113
|
-
|
|
114
|
-
/* CSS API for host */
|
|
115
|
-
--vaadin-overlay-viewport-bottom: 0;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
:host([hidden]),
|
|
119
|
-
:host(:not([opened]):not([closing])) {
|
|
120
|
-
display: none !important;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
[part='overlay'] {
|
|
124
|
-
-webkit-overflow-scrolling: touch;
|
|
125
|
-
overflow: auto;
|
|
126
|
-
pointer-events: auto;
|
|
127
|
-
|
|
128
|
-
/* Prevent overflowing the host in MSIE 11 */
|
|
129
|
-
max-width: 100%;
|
|
130
|
-
box-sizing: border-box;
|
|
131
|
-
|
|
132
|
-
-webkit-tap-highlight-color: initial; /* reenable tap highlight inside */
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
[part='backdrop'] {
|
|
136
|
-
z-index: -1;
|
|
137
|
-
content: '';
|
|
138
|
-
background: rgba(0, 0, 0, 0.5);
|
|
139
|
-
position: fixed;
|
|
140
|
-
top: 0;
|
|
141
|
-
left: 0;
|
|
142
|
-
bottom: 0;
|
|
143
|
-
right: 0;
|
|
144
|
-
pointer-events: auto;
|
|
145
|
-
}
|
|
146
|
-
</style>
|
|
147
|
-
|
|
148
82
|
<div id="backdrop" part="backdrop" hidden$="[[!withBackdrop]]"></div>
|
|
149
83
|
<div part="overlay" id="overlay" tabindex="0">
|
|
150
84
|
<div part="content" id="content">
|
|
@@ -158,478 +92,13 @@ class Overlay extends OverlayStackMixin(OverlayFocusMixin(ThemableMixin(DirMixin
|
|
|
158
92
|
return 'vaadin-overlay';
|
|
159
93
|
}
|
|
160
94
|
|
|
161
|
-
static get properties() {
|
|
162
|
-
return {
|
|
163
|
-
/**
|
|
164
|
-
* When true, the overlay is visible and attached to body.
|
|
165
|
-
*/
|
|
166
|
-
opened: {
|
|
167
|
-
type: Boolean,
|
|
168
|
-
notify: true,
|
|
169
|
-
observer: '_openedChanged',
|
|
170
|
-
reflectToAttribute: true,
|
|
171
|
-
},
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Owner element passed with renderer function
|
|
175
|
-
* @type {HTMLElement}
|
|
176
|
-
*/
|
|
177
|
-
owner: Element,
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Custom function for rendering the content of the overlay.
|
|
181
|
-
* Receives three arguments:
|
|
182
|
-
*
|
|
183
|
-
* - `root` The root container DOM element. Append your content to it.
|
|
184
|
-
* - `owner` The host element of the renderer function.
|
|
185
|
-
* - `model` The object with the properties related with rendering.
|
|
186
|
-
* @type {OverlayRenderer | null | undefined}
|
|
187
|
-
*/
|
|
188
|
-
renderer: Function,
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* When true the overlay has backdrop on top of content when opened.
|
|
192
|
-
* @type {boolean}
|
|
193
|
-
*/
|
|
194
|
-
withBackdrop: {
|
|
195
|
-
type: Boolean,
|
|
196
|
-
value: false,
|
|
197
|
-
reflectToAttribute: true,
|
|
198
|
-
},
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Object with properties that is passed to `renderer` function
|
|
202
|
-
*/
|
|
203
|
-
model: Object,
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* When true the overlay won't disable the main content, showing
|
|
207
|
-
* it doesn't change the functionality of the user interface.
|
|
208
|
-
* @type {boolean}
|
|
209
|
-
*/
|
|
210
|
-
modeless: {
|
|
211
|
-
type: Boolean,
|
|
212
|
-
value: false,
|
|
213
|
-
reflectToAttribute: true,
|
|
214
|
-
observer: '_modelessChanged',
|
|
215
|
-
},
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* When set to true, the overlay is hidden. This also closes the overlay
|
|
219
|
-
* immediately in case there is a closing animation in progress.
|
|
220
|
-
* @type {boolean}
|
|
221
|
-
*/
|
|
222
|
-
hidden: {
|
|
223
|
-
type: Boolean,
|
|
224
|
-
reflectToAttribute: true,
|
|
225
|
-
observer: '_hiddenChanged',
|
|
226
|
-
},
|
|
227
|
-
|
|
228
|
-
/** @private */
|
|
229
|
-
_mouseDownInside: {
|
|
230
|
-
type: Boolean,
|
|
231
|
-
},
|
|
232
|
-
|
|
233
|
-
/** @private */
|
|
234
|
-
_mouseUpInside: {
|
|
235
|
-
type: Boolean,
|
|
236
|
-
},
|
|
237
|
-
|
|
238
|
-
/** @private */
|
|
239
|
-
_oldOwner: Element,
|
|
240
|
-
|
|
241
|
-
/** @private */
|
|
242
|
-
_oldModel: Object,
|
|
243
|
-
|
|
244
|
-
/** @private */
|
|
245
|
-
_oldRenderer: Object,
|
|
246
|
-
|
|
247
|
-
/** @private */
|
|
248
|
-
_oldOpened: Boolean,
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
static get observers() {
|
|
253
|
-
return ['_rendererOrDataChanged(renderer, owner, model, opened)'];
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
constructor() {
|
|
257
|
-
super();
|
|
258
|
-
this._boundMouseDownListener = this._mouseDownListener.bind(this);
|
|
259
|
-
this._boundMouseUpListener = this._mouseUpListener.bind(this);
|
|
260
|
-
this._boundOutsideClickListener = this._outsideClickListener.bind(this);
|
|
261
|
-
this._boundKeydownListener = this._keydownListener.bind(this);
|
|
262
|
-
|
|
263
|
-
/* c8 ignore next 3 */
|
|
264
|
-
if (isIOS) {
|
|
265
|
-
this._boundIosResizeListener = () => this._detectIosNavbar();
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
95
|
/** @protected */
|
|
270
96
|
ready() {
|
|
271
97
|
super.ready();
|
|
272
98
|
|
|
273
|
-
// Need to add dummy click listeners to this and the backdrop or else
|
|
274
|
-
// the document click event listener (_outsideClickListener) may never
|
|
275
|
-
// get invoked on iOS Safari (reproducible in <vaadin-dialog>
|
|
276
|
-
// and <vaadin-context-menu>).
|
|
277
|
-
this.addEventListener('click', () => {});
|
|
278
|
-
this.$.backdrop.addEventListener('click', () => {});
|
|
279
|
-
|
|
280
99
|
processTemplates(this);
|
|
281
100
|
}
|
|
282
101
|
|
|
283
|
-
/** @private */
|
|
284
|
-
_detectIosNavbar() {
|
|
285
|
-
/* c8 ignore next 15 */
|
|
286
|
-
if (!this.opened) {
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const innerHeight = window.innerHeight;
|
|
291
|
-
const innerWidth = window.innerWidth;
|
|
292
|
-
|
|
293
|
-
const landscape = innerWidth > innerHeight;
|
|
294
|
-
|
|
295
|
-
const clientHeight = document.documentElement.clientHeight;
|
|
296
|
-
|
|
297
|
-
if (landscape && clientHeight > innerHeight) {
|
|
298
|
-
this.style.setProperty('--vaadin-overlay-viewport-bottom', `${clientHeight - innerHeight}px`);
|
|
299
|
-
} else {
|
|
300
|
-
this.style.setProperty('--vaadin-overlay-viewport-bottom', '0');
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* @param {Event=} sourceEvent
|
|
306
|
-
*/
|
|
307
|
-
close(sourceEvent) {
|
|
308
|
-
const evt = new CustomEvent('vaadin-overlay-close', {
|
|
309
|
-
bubbles: true,
|
|
310
|
-
cancelable: true,
|
|
311
|
-
detail: { sourceEvent },
|
|
312
|
-
});
|
|
313
|
-
this.dispatchEvent(evt);
|
|
314
|
-
if (!evt.defaultPrevented) {
|
|
315
|
-
this.opened = false;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/** @protected */
|
|
320
|
-
connectedCallback() {
|
|
321
|
-
super.connectedCallback();
|
|
322
|
-
|
|
323
|
-
/* c8 ignore next 3 */
|
|
324
|
-
if (this._boundIosResizeListener) {
|
|
325
|
-
this._detectIosNavbar();
|
|
326
|
-
window.addEventListener('resize', this._boundIosResizeListener);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/** @protected */
|
|
331
|
-
disconnectedCallback() {
|
|
332
|
-
super.disconnectedCallback();
|
|
333
|
-
|
|
334
|
-
/* c8 ignore next 3 */
|
|
335
|
-
if (this._boundIosResizeListener) {
|
|
336
|
-
window.removeEventListener('resize', this._boundIosResizeListener);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Requests an update for the content of the overlay.
|
|
342
|
-
* While performing the update, it invokes the renderer passed in the `renderer` property.
|
|
343
|
-
*
|
|
344
|
-
* It is not guaranteed that the update happens immediately (synchronously) after it is requested.
|
|
345
|
-
*/
|
|
346
|
-
requestContentUpdate() {
|
|
347
|
-
if (this.renderer) {
|
|
348
|
-
this.renderer.call(this.owner, this, this.owner, this.model);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/** @private */
|
|
353
|
-
_mouseDownListener(event) {
|
|
354
|
-
this._mouseDownInside = event.composedPath().indexOf(this.$.overlay) >= 0;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/** @private */
|
|
358
|
-
_mouseUpListener(event) {
|
|
359
|
-
this._mouseUpInside = event.composedPath().indexOf(this.$.overlay) >= 0;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* Whether to close the overlay on outside click or not.
|
|
364
|
-
* Override this method to customize the closing logic.
|
|
365
|
-
*
|
|
366
|
-
* @param {Event} _event
|
|
367
|
-
* @return {boolean}
|
|
368
|
-
* @protected
|
|
369
|
-
*/
|
|
370
|
-
_shouldCloseOnOutsideClick(_event) {
|
|
371
|
-
return this._last;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Outside click listener used in capture phase to close the overlay before
|
|
376
|
-
* propagating the event to the listener on the element that triggered it.
|
|
377
|
-
* Otherwise, calling `open()` would result in closing and re-opening.
|
|
378
|
-
*
|
|
379
|
-
* @private
|
|
380
|
-
*/
|
|
381
|
-
_outsideClickListener(event) {
|
|
382
|
-
if (event.composedPath().includes(this.$.overlay) || this._mouseDownInside || this._mouseUpInside) {
|
|
383
|
-
this._mouseDownInside = false;
|
|
384
|
-
this._mouseUpInside = false;
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
if (!this._shouldCloseOnOutsideClick(event)) {
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const evt = new CustomEvent('vaadin-overlay-outside-click', {
|
|
393
|
-
bubbles: true,
|
|
394
|
-
cancelable: true,
|
|
395
|
-
detail: { sourceEvent: event },
|
|
396
|
-
});
|
|
397
|
-
this.dispatchEvent(evt);
|
|
398
|
-
|
|
399
|
-
if (this.opened && !evt.defaultPrevented) {
|
|
400
|
-
this.close(event);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Listener used to close whe overlay on Escape press, if it is the last one.
|
|
406
|
-
* @private
|
|
407
|
-
*/
|
|
408
|
-
_keydownListener(event) {
|
|
409
|
-
if (!this._last) {
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Only close modeless overlay on Esc press when it contains focus
|
|
414
|
-
if (this.modeless && !event.composedPath().includes(this.$.overlay)) {
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
if (event.key === 'Escape') {
|
|
419
|
-
const evt = new CustomEvent('vaadin-overlay-escape-press', {
|
|
420
|
-
bubbles: true,
|
|
421
|
-
cancelable: true,
|
|
422
|
-
detail: { sourceEvent: event },
|
|
423
|
-
});
|
|
424
|
-
this.dispatchEvent(evt);
|
|
425
|
-
|
|
426
|
-
if (this.opened && !evt.defaultPrevented) {
|
|
427
|
-
this.close(event);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/** @private */
|
|
433
|
-
_openedChanged(opened, wasOpened) {
|
|
434
|
-
if (opened) {
|
|
435
|
-
this._saveFocus();
|
|
436
|
-
|
|
437
|
-
this._animatedOpening();
|
|
438
|
-
|
|
439
|
-
afterNextRender(this, () => {
|
|
440
|
-
this._trapFocus();
|
|
441
|
-
|
|
442
|
-
const evt = new CustomEvent('vaadin-overlay-open', { bubbles: true });
|
|
443
|
-
this.dispatchEvent(evt);
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
document.addEventListener('keydown', this._boundKeydownListener);
|
|
447
|
-
|
|
448
|
-
if (!this.modeless) {
|
|
449
|
-
this._addGlobalListeners();
|
|
450
|
-
}
|
|
451
|
-
} else if (wasOpened) {
|
|
452
|
-
this._resetFocus();
|
|
453
|
-
|
|
454
|
-
this._animatedClosing();
|
|
455
|
-
|
|
456
|
-
document.removeEventListener('keydown', this._boundKeydownListener);
|
|
457
|
-
|
|
458
|
-
if (!this.modeless) {
|
|
459
|
-
this._removeGlobalListeners();
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/** @private */
|
|
465
|
-
_hiddenChanged(hidden) {
|
|
466
|
-
if (hidden && this.hasAttribute('closing')) {
|
|
467
|
-
this._flushAnimation('closing');
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* @return {boolean}
|
|
473
|
-
* @private
|
|
474
|
-
*/
|
|
475
|
-
_shouldAnimate() {
|
|
476
|
-
const style = getComputedStyle(this);
|
|
477
|
-
const name = style.getPropertyValue('animation-name');
|
|
478
|
-
const hidden = style.getPropertyValue('display') === 'none';
|
|
479
|
-
return !hidden && name && name !== 'none';
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* @param {string} type
|
|
484
|
-
* @param {Function} callback
|
|
485
|
-
* @private
|
|
486
|
-
*/
|
|
487
|
-
_enqueueAnimation(type, callback) {
|
|
488
|
-
const handler = `__${type}Handler`;
|
|
489
|
-
const listener = (event) => {
|
|
490
|
-
if (event && event.target !== this) {
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
493
|
-
callback();
|
|
494
|
-
this.removeEventListener('animationend', listener);
|
|
495
|
-
delete this[handler];
|
|
496
|
-
};
|
|
497
|
-
this[handler] = listener;
|
|
498
|
-
this.addEventListener('animationend', listener);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
/**
|
|
502
|
-
* @param {string} type
|
|
503
|
-
* @protected
|
|
504
|
-
*/
|
|
505
|
-
_flushAnimation(type) {
|
|
506
|
-
const handler = `__${type}Handler`;
|
|
507
|
-
if (typeof this[handler] === 'function') {
|
|
508
|
-
this[handler]();
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
/** @private */
|
|
513
|
-
_animatedOpening() {
|
|
514
|
-
if (this.parentNode === document.body && this.hasAttribute('closing')) {
|
|
515
|
-
this._flushAnimation('closing');
|
|
516
|
-
}
|
|
517
|
-
this._attachOverlay();
|
|
518
|
-
if (!this.modeless) {
|
|
519
|
-
this._enterModalState();
|
|
520
|
-
}
|
|
521
|
-
this.setAttribute('opening', '');
|
|
522
|
-
|
|
523
|
-
if (this._shouldAnimate()) {
|
|
524
|
-
this._enqueueAnimation('opening', () => {
|
|
525
|
-
this._finishOpening();
|
|
526
|
-
});
|
|
527
|
-
} else {
|
|
528
|
-
this._finishOpening();
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
/** @private */
|
|
533
|
-
_attachOverlay() {
|
|
534
|
-
this._placeholder = document.createComment('vaadin-overlay-placeholder');
|
|
535
|
-
this.parentNode.insertBefore(this._placeholder, this);
|
|
536
|
-
document.body.appendChild(this);
|
|
537
|
-
this.bringToFront();
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
/** @private */
|
|
541
|
-
_finishOpening() {
|
|
542
|
-
this.removeAttribute('opening');
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
/** @private */
|
|
546
|
-
_finishClosing() {
|
|
547
|
-
this._detachOverlay();
|
|
548
|
-
this.$.overlay.style.removeProperty('pointer-events');
|
|
549
|
-
this.removeAttribute('closing');
|
|
550
|
-
this.dispatchEvent(new CustomEvent('vaadin-overlay-closed'));
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
/** @private */
|
|
554
|
-
_animatedClosing() {
|
|
555
|
-
if (this.hasAttribute('opening')) {
|
|
556
|
-
this._flushAnimation('opening');
|
|
557
|
-
}
|
|
558
|
-
if (this._placeholder) {
|
|
559
|
-
this._exitModalState();
|
|
560
|
-
this.setAttribute('closing', '');
|
|
561
|
-
this.dispatchEvent(new CustomEvent('vaadin-overlay-closing'));
|
|
562
|
-
|
|
563
|
-
if (this._shouldAnimate()) {
|
|
564
|
-
this._enqueueAnimation('closing', () => {
|
|
565
|
-
this._finishClosing();
|
|
566
|
-
});
|
|
567
|
-
} else {
|
|
568
|
-
this._finishClosing();
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
/** @private */
|
|
574
|
-
_detachOverlay() {
|
|
575
|
-
this._placeholder.parentNode.insertBefore(this, this._placeholder);
|
|
576
|
-
this._placeholder.parentNode.removeChild(this._placeholder);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
/** @private */
|
|
580
|
-
_modelessChanged(modeless) {
|
|
581
|
-
if (!modeless) {
|
|
582
|
-
if (this.opened) {
|
|
583
|
-
this._addGlobalListeners();
|
|
584
|
-
this._enterModalState();
|
|
585
|
-
}
|
|
586
|
-
} else {
|
|
587
|
-
this._removeGlobalListeners();
|
|
588
|
-
this._exitModalState();
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/** @private */
|
|
593
|
-
_addGlobalListeners() {
|
|
594
|
-
document.addEventListener('mousedown', this._boundMouseDownListener);
|
|
595
|
-
document.addEventListener('mouseup', this._boundMouseUpListener);
|
|
596
|
-
// Firefox leaks click to document on contextmenu even if prevented
|
|
597
|
-
// https://bugzilla.mozilla.org/show_bug.cgi?id=990614
|
|
598
|
-
document.documentElement.addEventListener('click', this._boundOutsideClickListener, true);
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
/** @private */
|
|
602
|
-
_removeGlobalListeners() {
|
|
603
|
-
document.removeEventListener('mousedown', this._boundMouseDownListener);
|
|
604
|
-
document.removeEventListener('mouseup', this._boundMouseUpListener);
|
|
605
|
-
document.documentElement.removeEventListener('click', this._boundOutsideClickListener, true);
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
/** @private */
|
|
609
|
-
_rendererOrDataChanged(renderer, owner, model, opened) {
|
|
610
|
-
const ownerOrModelChanged = this._oldOwner !== owner || this._oldModel !== model;
|
|
611
|
-
this._oldModel = model;
|
|
612
|
-
this._oldOwner = owner;
|
|
613
|
-
|
|
614
|
-
const rendererChanged = this._oldRenderer !== renderer;
|
|
615
|
-
this._oldRenderer = renderer;
|
|
616
|
-
|
|
617
|
-
const openedChanged = this._oldOpened !== opened;
|
|
618
|
-
this._oldOpened = opened;
|
|
619
|
-
|
|
620
|
-
if (rendererChanged) {
|
|
621
|
-
this.innerHTML = '';
|
|
622
|
-
// Whenever a Lit-based renderer is used, it assigns a Lit part to the node it was rendered into.
|
|
623
|
-
// When clearing the rendered content, this part needs to be manually disposed of.
|
|
624
|
-
// Otherwise, using a Lit-based renderer on the same node will throw an exception or render nothing afterward.
|
|
625
|
-
delete this._$litPart$;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
if (opened && renderer && (rendererChanged || openedChanged || ownerOrModelChanged)) {
|
|
629
|
-
this.requestContentUpdate();
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
102
|
/**
|
|
634
103
|
* @event vaadin-overlay-open
|
|
635
104
|
* Fired after the overlay is opened.
|