@vaadin/overlay 24.1.5 → 24.2.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.
@@ -3,13 +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';
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' });
13
14
 
14
15
  /**
15
16
  * `<vaadin-overlay>` is a Web Component for creating overlays. The content of the overlay
@@ -73,76 +74,11 @@ import { OverlayFocusMixin } from './vaadin-overlay-focus-mixin.js';
73
74
  * @extends HTMLElement
74
75
  * @mixes ThemableMixin
75
76
  * @mixes DirMixin
76
- * @mixes OverlayFocusMixin
77
+ * @mixes OverlayMixin
77
78
  */
78
- class Overlay extends OverlayFocusMixin(ThemableMixin(DirMixin(PolymerElement))) {
79
+ class Overlay extends OverlayMixin(ThemableMixin(DirMixin(PolymerElement))) {
79
80
  static get template() {
80
81
  return html`
81
- <style>
82
- :host {
83
- z-index: 200;
84
- position: fixed;
85
-
86
- /* Despite of what the names say, <vaadin-overlay> is just a container
87
- for position/sizing/alignment. The actual overlay is the overlay part. */
88
-
89
- /* Default position constraints: the entire viewport. Note: themes can
90
- override this to introduce gaps between the overlay and the viewport. */
91
- top: 0;
92
- right: 0;
93
- bottom: var(--vaadin-overlay-viewport-bottom);
94
- left: 0;
95
-
96
- /* Use flexbox alignment for the overlay part. */
97
- display: flex;
98
- flex-direction: column; /* makes dropdowns sizing easier */
99
- /* Align to center by default. */
100
- align-items: center;
101
- justify-content: center;
102
-
103
- /* Allow centering when max-width/max-height applies. */
104
- margin: auto;
105
-
106
- /* The host is not clickable, only the overlay part is. */
107
- pointer-events: none;
108
-
109
- /* Remove tap highlight on touch devices. */
110
- -webkit-tap-highlight-color: transparent;
111
-
112
- /* CSS API for host */
113
- --vaadin-overlay-viewport-bottom: 0;
114
- }
115
-
116
- :host([hidden]),
117
- :host(:not([opened]):not([closing])) {
118
- display: none !important;
119
- }
120
-
121
- [part='overlay'] {
122
- -webkit-overflow-scrolling: touch;
123
- overflow: auto;
124
- pointer-events: auto;
125
-
126
- /* Prevent overflowing the host in MSIE 11 */
127
- max-width: 100%;
128
- box-sizing: border-box;
129
-
130
- -webkit-tap-highlight-color: initial; /* reenable tap highlight inside */
131
- }
132
-
133
- [part='backdrop'] {
134
- z-index: -1;
135
- content: '';
136
- background: rgba(0, 0, 0, 0.5);
137
- position: fixed;
138
- top: 0;
139
- left: 0;
140
- bottom: 0;
141
- right: 0;
142
- pointer-events: auto;
143
- }
144
- </style>
145
-
146
82
  <div id="backdrop" part="backdrop" hidden$="[[!withBackdrop]]"></div>
147
83
  <div part="overlay" id="overlay" tabindex="0">
148
84
  <div part="content" id="content">
@@ -156,553 +92,13 @@ class Overlay extends OverlayFocusMixin(ThemableMixin(DirMixin(PolymerElement)))
156
92
  return 'vaadin-overlay';
157
93
  }
158
94
 
159
- static get properties() {
160
- return {
161
- /**
162
- * When true, the overlay is visible and attached to body.
163
- */
164
- opened: {
165
- type: Boolean,
166
- notify: true,
167
- observer: '_openedChanged',
168
- reflectToAttribute: true,
169
- },
170
-
171
- /**
172
- * Owner element passed with renderer function
173
- * @type {HTMLElement}
174
- */
175
- owner: Element,
176
-
177
- /**
178
- * Custom function for rendering the content of the overlay.
179
- * Receives three arguments:
180
- *
181
- * - `root` The root container DOM element. Append your content to it.
182
- * - `owner` The host element of the renderer function.
183
- * - `model` The object with the properties related with rendering.
184
- * @type {OverlayRenderer | null | undefined}
185
- */
186
- renderer: Function,
187
-
188
- /**
189
- * When true the overlay has backdrop on top of content when opened.
190
- * @type {boolean}
191
- */
192
- withBackdrop: {
193
- type: Boolean,
194
- value: false,
195
- reflectToAttribute: true,
196
- },
197
-
198
- /**
199
- * Object with properties that is passed to `renderer` function
200
- */
201
- model: Object,
202
-
203
- /**
204
- * When true the overlay won't disable the main content, showing
205
- * it doesn't change the functionality of the user interface.
206
- * @type {boolean}
207
- */
208
- modeless: {
209
- type: Boolean,
210
- value: false,
211
- reflectToAttribute: true,
212
- observer: '_modelessChanged',
213
- },
214
-
215
- /**
216
- * When set to true, the overlay is hidden. This also closes the overlay
217
- * immediately in case there is a closing animation in progress.
218
- * @type {boolean}
219
- */
220
- hidden: {
221
- type: Boolean,
222
- reflectToAttribute: true,
223
- observer: '_hiddenChanged',
224
- },
225
-
226
- /** @private */
227
- _mouseDownInside: {
228
- type: Boolean,
229
- },
230
-
231
- /** @private */
232
- _mouseUpInside: {
233
- type: Boolean,
234
- },
235
-
236
- /** @private */
237
- _oldOwner: Element,
238
-
239
- /** @private */
240
- _oldModel: Object,
241
-
242
- /** @private */
243
- _oldRenderer: Object,
244
-
245
- /** @private */
246
- _oldOpened: Boolean,
247
- };
248
- }
249
-
250
- static get observers() {
251
- return ['_rendererOrDataChanged(renderer, owner, model, opened)'];
252
- }
253
-
254
- /**
255
- * Returns all attached overlays in visual stacking order.
256
- * @private
257
- */
258
- static get __attachedInstances() {
259
- return Array.from(document.body.children)
260
- .filter((el) => el instanceof Overlay && !el.hasAttribute('closing'))
261
- .sort((a, b) => a.__zIndex - b.__zIndex || 0);
262
- }
263
-
264
- constructor() {
265
- super();
266
- this._boundMouseDownListener = this._mouseDownListener.bind(this);
267
- this._boundMouseUpListener = this._mouseUpListener.bind(this);
268
- this._boundOutsideClickListener = this._outsideClickListener.bind(this);
269
- this._boundKeydownListener = this._keydownListener.bind(this);
270
-
271
- /* c8 ignore next 3 */
272
- if (isIOS) {
273
- this._boundIosResizeListener = () => this._detectIosNavbar();
274
- }
275
- }
276
-
277
- /**
278
- * Returns true if this is the last one in the opened overlays stack
279
- * @return {boolean}
280
- * @protected
281
- */
282
- get _last() {
283
- return this === Overlay.__attachedInstances.pop();
284
- }
285
-
286
95
  /** @protected */
287
96
  ready() {
288
97
  super.ready();
289
98
 
290
- // Need to add dummy click listeners to this and the backdrop or else
291
- // the document click event listener (_outsideClickListener) may never
292
- // get invoked on iOS Safari (reproducible in <vaadin-dialog>
293
- // and <vaadin-context-menu>).
294
- this.addEventListener('click', () => {});
295
- this.$.backdrop.addEventListener('click', () => {});
296
-
297
99
  processTemplates(this);
298
100
  }
299
101
 
300
- /** @private */
301
- _detectIosNavbar() {
302
- /* c8 ignore next 15 */
303
- if (!this.opened) {
304
- return;
305
- }
306
-
307
- const innerHeight = window.innerHeight;
308
- const innerWidth = window.innerWidth;
309
-
310
- const landscape = innerWidth > innerHeight;
311
-
312
- const clientHeight = document.documentElement.clientHeight;
313
-
314
- if (landscape && clientHeight > innerHeight) {
315
- this.style.setProperty('--vaadin-overlay-viewport-bottom', `${clientHeight - innerHeight}px`);
316
- } else {
317
- this.style.setProperty('--vaadin-overlay-viewport-bottom', '0');
318
- }
319
- }
320
-
321
- /**
322
- * @param {Event=} sourceEvent
323
- */
324
- close(sourceEvent) {
325
- const evt = new CustomEvent('vaadin-overlay-close', {
326
- bubbles: true,
327
- cancelable: true,
328
- detail: { sourceEvent },
329
- });
330
- this.dispatchEvent(evt);
331
- if (!evt.defaultPrevented) {
332
- this.opened = false;
333
- }
334
- }
335
-
336
- /** @protected */
337
- connectedCallback() {
338
- super.connectedCallback();
339
-
340
- /* c8 ignore next 3 */
341
- if (this._boundIosResizeListener) {
342
- this._detectIosNavbar();
343
- window.addEventListener('resize', this._boundIosResizeListener);
344
- }
345
- }
346
-
347
- /** @protected */
348
- disconnectedCallback() {
349
- super.disconnectedCallback();
350
-
351
- /* c8 ignore next 3 */
352
- if (this._boundIosResizeListener) {
353
- window.removeEventListener('resize', this._boundIosResizeListener);
354
- }
355
- }
356
-
357
- /**
358
- * Requests an update for the content of the overlay.
359
- * While performing the update, it invokes the renderer passed in the `renderer` property.
360
- *
361
- * It is not guaranteed that the update happens immediately (synchronously) after it is requested.
362
- */
363
- requestContentUpdate() {
364
- if (this.renderer) {
365
- this.renderer.call(this.owner, this, this.owner, this.model);
366
- }
367
- }
368
-
369
- /** @private */
370
- _mouseDownListener(event) {
371
- this._mouseDownInside = event.composedPath().indexOf(this.$.overlay) >= 0;
372
- }
373
-
374
- /** @private */
375
- _mouseUpListener(event) {
376
- this._mouseUpInside = event.composedPath().indexOf(this.$.overlay) >= 0;
377
- }
378
-
379
- /**
380
- * Whether to close the overlay on outside click or not.
381
- * Override this method to customize the closing logic.
382
- *
383
- * @param {Event} _event
384
- * @return {boolean}
385
- * @protected
386
- */
387
- _shouldCloseOnOutsideClick(_event) {
388
- return this._last;
389
- }
390
-
391
- /**
392
- * Outside click listener used in capture phase to close the overlay before
393
- * propagating the event to the listener on the element that triggered it.
394
- * Otherwise, calling `open()` would result in closing and re-opening.
395
- *
396
- * @private
397
- */
398
- _outsideClickListener(event) {
399
- if (event.composedPath().includes(this.$.overlay) || this._mouseDownInside || this._mouseUpInside) {
400
- this._mouseDownInside = false;
401
- this._mouseUpInside = false;
402
- return;
403
- }
404
-
405
- if (!this._shouldCloseOnOutsideClick(event)) {
406
- return;
407
- }
408
-
409
- const evt = new CustomEvent('vaadin-overlay-outside-click', {
410
- bubbles: true,
411
- cancelable: true,
412
- detail: { sourceEvent: event },
413
- });
414
- this.dispatchEvent(evt);
415
-
416
- if (this.opened && !evt.defaultPrevented) {
417
- this.close(event);
418
- }
419
- }
420
-
421
- /**
422
- * Listener used to close whe overlay on Escape press, if it is the last one.
423
- * @private
424
- */
425
- _keydownListener(event) {
426
- if (!this._last) {
427
- return;
428
- }
429
-
430
- // Only close modeless overlay on Esc press when it contains focus
431
- if (this.modeless && !event.composedPath().includes(this.$.overlay)) {
432
- return;
433
- }
434
-
435
- if (event.key === 'Escape') {
436
- const evt = new CustomEvent('vaadin-overlay-escape-press', {
437
- bubbles: true,
438
- cancelable: true,
439
- detail: { sourceEvent: event },
440
- });
441
- this.dispatchEvent(evt);
442
-
443
- if (this.opened && !evt.defaultPrevented) {
444
- this.close(event);
445
- }
446
- }
447
- }
448
-
449
- /** @private */
450
- _openedChanged(opened, wasOpened) {
451
- if (opened) {
452
- this._saveFocus();
453
-
454
- this._animatedOpening();
455
-
456
- afterNextRender(this, () => {
457
- this._trapFocus();
458
-
459
- const evt = new CustomEvent('vaadin-overlay-open', { bubbles: true });
460
- this.dispatchEvent(evt);
461
- });
462
-
463
- document.addEventListener('keydown', this._boundKeydownListener);
464
-
465
- if (!this.modeless) {
466
- this._addGlobalListeners();
467
- }
468
- } else if (wasOpened) {
469
- this._resetFocus();
470
-
471
- this._animatedClosing();
472
-
473
- document.removeEventListener('keydown', this._boundKeydownListener);
474
-
475
- if (!this.modeless) {
476
- this._removeGlobalListeners();
477
- }
478
- }
479
- }
480
-
481
- /** @private */
482
- _hiddenChanged(hidden) {
483
- if (hidden && this.hasAttribute('closing')) {
484
- this._flushAnimation('closing');
485
- }
486
- }
487
-
488
- /**
489
- * @return {boolean}
490
- * @private
491
- */
492
- _shouldAnimate() {
493
- const style = getComputedStyle(this);
494
- const name = style.getPropertyValue('animation-name');
495
- const hidden = style.getPropertyValue('display') === 'none';
496
- return !hidden && name && name !== 'none';
497
- }
498
-
499
- /**
500
- * @param {string} type
501
- * @param {Function} callback
502
- * @private
503
- */
504
- _enqueueAnimation(type, callback) {
505
- const handler = `__${type}Handler`;
506
- const listener = (event) => {
507
- if (event && event.target !== this) {
508
- return;
509
- }
510
- callback();
511
- this.removeEventListener('animationend', listener);
512
- delete this[handler];
513
- };
514
- this[handler] = listener;
515
- this.addEventListener('animationend', listener);
516
- }
517
-
518
- /**
519
- * @param {string} type
520
- * @protected
521
- */
522
- _flushAnimation(type) {
523
- const handler = `__${type}Handler`;
524
- if (typeof this[handler] === 'function') {
525
- this[handler]();
526
- }
527
- }
528
-
529
- /** @private */
530
- _animatedOpening() {
531
- if (this.parentNode === document.body && this.hasAttribute('closing')) {
532
- this._flushAnimation('closing');
533
- }
534
- this._attachOverlay();
535
- if (!this.modeless) {
536
- this._enterModalState();
537
- }
538
- this.setAttribute('opening', '');
539
-
540
- if (this._shouldAnimate()) {
541
- this._enqueueAnimation('opening', () => {
542
- this._finishOpening();
543
- });
544
- } else {
545
- this._finishOpening();
546
- }
547
- }
548
-
549
- /** @private */
550
- _attachOverlay() {
551
- this._placeholder = document.createComment('vaadin-overlay-placeholder');
552
- this.parentNode.insertBefore(this._placeholder, this);
553
- document.body.appendChild(this);
554
- this.bringToFront();
555
- }
556
-
557
- /** @private */
558
- _finishOpening() {
559
- this.removeAttribute('opening');
560
- }
561
-
562
- /** @private */
563
- _finishClosing() {
564
- this._detachOverlay();
565
- this.$.overlay.style.removeProperty('pointer-events');
566
- this.removeAttribute('closing');
567
- this.dispatchEvent(new CustomEvent('vaadin-overlay-closed'));
568
- }
569
-
570
- /** @private */
571
- _animatedClosing() {
572
- if (this.hasAttribute('opening')) {
573
- this._flushAnimation('opening');
574
- }
575
- if (this._placeholder) {
576
- this._exitModalState();
577
- this.setAttribute('closing', '');
578
- this.dispatchEvent(new CustomEvent('vaadin-overlay-closing'));
579
-
580
- if (this._shouldAnimate()) {
581
- this._enqueueAnimation('closing', () => {
582
- this._finishClosing();
583
- });
584
- } else {
585
- this._finishClosing();
586
- }
587
- }
588
- }
589
-
590
- /** @private */
591
- _detachOverlay() {
592
- this._placeholder.parentNode.insertBefore(this, this._placeholder);
593
- this._placeholder.parentNode.removeChild(this._placeholder);
594
- }
595
-
596
- /** @private */
597
- _modelessChanged(modeless) {
598
- if (!modeless) {
599
- if (this.opened) {
600
- this._addGlobalListeners();
601
- this._enterModalState();
602
- }
603
- } else {
604
- this._removeGlobalListeners();
605
- this._exitModalState();
606
- }
607
- }
608
-
609
- /** @private */
610
- _addGlobalListeners() {
611
- document.addEventListener('mousedown', this._boundMouseDownListener);
612
- document.addEventListener('mouseup', this._boundMouseUpListener);
613
- // Firefox leaks click to document on contextmenu even if prevented
614
- // https://bugzilla.mozilla.org/show_bug.cgi?id=990614
615
- document.documentElement.addEventListener('click', this._boundOutsideClickListener, true);
616
- }
617
-
618
- /** @private */
619
- _enterModalState() {
620
- if (document.body.style.pointerEvents !== 'none') {
621
- // Set body pointer-events to 'none' to disable mouse interactions with
622
- // other document nodes.
623
- this._previousDocumentPointerEvents = document.body.style.pointerEvents;
624
- document.body.style.pointerEvents = 'none';
625
- }
626
-
627
- // Disable pointer events in other attached overlays
628
- Overlay.__attachedInstances.forEach((el) => {
629
- if (el !== this) {
630
- el.shadowRoot.querySelector('[part="overlay"]').style.pointerEvents = 'none';
631
- }
632
- });
633
- }
634
-
635
- /** @private */
636
- _removeGlobalListeners() {
637
- document.removeEventListener('mousedown', this._boundMouseDownListener);
638
- document.removeEventListener('mouseup', this._boundMouseUpListener);
639
- document.documentElement.removeEventListener('click', this._boundOutsideClickListener, true);
640
- }
641
-
642
- /** @private */
643
- _exitModalState() {
644
- if (this._previousDocumentPointerEvents !== undefined) {
645
- // Restore body pointer-events
646
- document.body.style.pointerEvents = this._previousDocumentPointerEvents;
647
- delete this._previousDocumentPointerEvents;
648
- }
649
-
650
- // Restore pointer events in the previous overlay(s)
651
- const instances = Overlay.__attachedInstances;
652
- let el;
653
- // Use instances.pop() to ensure the reverse order
654
- while ((el = instances.pop())) {
655
- if (el === this) {
656
- // Skip the current instance
657
- continue;
658
- }
659
- el.shadowRoot.querySelector('[part="overlay"]').style.removeProperty('pointer-events');
660
- if (!el.modeless) {
661
- // Stop after the last modal
662
- break;
663
- }
664
- }
665
- }
666
-
667
- /** @private */
668
- _rendererOrDataChanged(renderer, owner, model, opened) {
669
- const ownerOrModelChanged = this._oldOwner !== owner || this._oldModel !== model;
670
- this._oldModel = model;
671
- this._oldOwner = owner;
672
-
673
- const rendererChanged = this._oldRenderer !== renderer;
674
- this._oldRenderer = renderer;
675
-
676
- const openedChanged = this._oldOpened !== opened;
677
- this._oldOpened = opened;
678
-
679
- if (rendererChanged) {
680
- this.innerHTML = '';
681
- // Whenever a Lit-based renderer is used, it assigns a Lit part to the node it was rendered into.
682
- // When clearing the rendered content, this part needs to be manually disposed of.
683
- // Otherwise, using a Lit-based renderer on the same node will throw an exception or render nothing afterward.
684
- delete this._$litPart$;
685
- }
686
-
687
- if (opened && renderer && (rendererChanged || openedChanged || ownerOrModelChanged)) {
688
- this.requestContentUpdate();
689
- }
690
- }
691
-
692
- /**
693
- * Brings the overlay as visually the frontmost one
694
- */
695
- bringToFront() {
696
- let zIndex = '';
697
- const frontmost = Overlay.__attachedInstances.filter((o) => o !== this).pop();
698
- if (frontmost) {
699
- const frontmostZIndex = frontmost.__zIndex;
700
- zIndex = frontmostZIndex + 1;
701
- }
702
- this.style.zIndex = zIndex;
703
- this.__zIndex = zIndex || parseFloat(getComputedStyle(this).zIndex);
704
- }
705
-
706
102
  /**
707
103
  * @event vaadin-overlay-open
708
104
  * Fired after the overlay is opened.