@vaadin/overlay 24.2.0-alpha2 → 24.2.0-alpha3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/overlay",
3
- "version": "24.2.0-alpha2",
3
+ "version": "24.2.0-alpha3",
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-alpha2",
40
- "@vaadin/component-base": "24.2.0-alpha2",
41
- "@vaadin/vaadin-lumo-styles": "24.2.0-alpha2",
42
- "@vaadin/vaadin-material-styles": "24.2.0-alpha2",
43
- "@vaadin/vaadin-themable-mixin": "24.2.0-alpha2"
39
+ "@vaadin/a11y-base": "24.2.0-alpha3",
40
+ "@vaadin/component-base": "24.2.0-alpha3",
41
+ "@vaadin/vaadin-lumo-styles": "24.2.0-alpha3",
42
+ "@vaadin/vaadin-material-styles": "24.2.0-alpha3",
43
+ "@vaadin/vaadin-themable-mixin": "24.2.0-alpha3"
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": "b8378bd2267728e172012bcb2ea45c3e5a6e590a"
51
+ "gitHead": "e5d1e1ed2d01b56d5922e18a3fcb7cd306351954"
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
+ `;
@@ -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 { OverlayFocusMixin } from './vaadin-overlay-focus-mixin.js';
9
- import { OverlayStackMixin } from './vaadin-overlay-stack-mixin.js';
8
+ import { OverlayMixin } from './vaadin-overlay-mixin.js';
10
9
 
11
- export type OverlayRenderer = (root: HTMLElement, owner: HTMLElement, model?: object) => void;
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 OverlayStackMixin(OverlayFocusMixin(ThemableMixin(DirMixin(HTMLElement)))) {
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 {
@@ -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 { OverlayFocusMixin } from './vaadin-overlay-focus-mixin.js';
13
- import { OverlayStackMixin } from './vaadin-overlay-stack-mixin.js';
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 OverlayFocusMixin
78
- * @mixes OverlayStackMixin
77
+ * @mixes OverlayMixin
79
78
  */
80
- class Overlay extends OverlayStackMixin(OverlayFocusMixin(ThemableMixin(DirMixin(PolymerElement)))) {
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.