@vaadin/popover 24.5.0-alpha1 → 24.5.0-alpha11
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/README.md +1 -1
- package/lit.d.ts +1 -0
- package/lit.js +1 -0
- package/package.json +14 -11
- package/src/lit/renderer-directives.d.ts +58 -0
- package/src/lit/renderer-directives.js +60 -0
- package/src/vaadin-popover-overlay-mixin.js +6 -0
- package/src/vaadin-popover-overlay.js +14 -4
- package/src/vaadin-popover-target-mixin.js +18 -0
- package/src/vaadin-popover.d.ts +72 -0
- package/src/vaadin-popover.js +272 -18
- package/theme/lumo/vaadin-popover-styles.d.ts +1 -0
- package/theme/lumo/vaadin-popover-styles.js +95 -4
- package/theme/material/vaadin-popover-styles.d.ts +1 -1
- package/theme/material/vaadin-popover-styles.js +95 -4
- package/web-types.json +56 -8
- package/web-types.lit.json +26 -5
package/src/vaadin-popover.js
CHANGED
|
@@ -4,18 +4,30 @@
|
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
import './vaadin-popover-overlay.js';
|
|
7
|
-
import { html, LitElement } from 'lit';
|
|
7
|
+
import { css, html, LitElement } from 'lit';
|
|
8
8
|
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
getDeepActiveElement,
|
|
11
|
+
getFocusableElements,
|
|
12
|
+
isElementFocused,
|
|
13
|
+
isKeyboardActive,
|
|
14
|
+
} from '@vaadin/a11y-base/src/focus-utils.js';
|
|
10
15
|
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
|
|
11
16
|
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
|
|
12
17
|
import { OverlayClassMixin } from '@vaadin/component-base/src/overlay-class-mixin.js';
|
|
13
18
|
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
|
|
14
19
|
import { generateUniqueId } from '@vaadin/component-base/src/unique-id-utils.js';
|
|
20
|
+
import { isLastOverlay } from '@vaadin/overlay/src/vaadin-overlay-stack-mixin.js';
|
|
15
21
|
import { ThemePropertyMixin } from '@vaadin/vaadin-themable-mixin/vaadin-theme-property-mixin.js';
|
|
16
22
|
import { PopoverPositionMixin } from './vaadin-popover-position-mixin.js';
|
|
17
23
|
import { PopoverTargetMixin } from './vaadin-popover-target-mixin.js';
|
|
18
24
|
|
|
25
|
+
const DEFAULT_DELAY = 500;
|
|
26
|
+
|
|
27
|
+
let defaultFocusDelay = DEFAULT_DELAY;
|
|
28
|
+
let defaultHoverDelay = DEFAULT_DELAY;
|
|
29
|
+
let defaultHideDelay = DEFAULT_DELAY;
|
|
30
|
+
|
|
19
31
|
/**
|
|
20
32
|
* Controller for handling popover opened state.
|
|
21
33
|
*/
|
|
@@ -34,17 +46,20 @@ class PopoverOpenedStateController {
|
|
|
34
46
|
|
|
35
47
|
/** @private */
|
|
36
48
|
get __focusDelay() {
|
|
37
|
-
|
|
49
|
+
const popover = this.host;
|
|
50
|
+
return popover.focusDelay != null && popover.focusDelay >= 0 ? popover.focusDelay : defaultFocusDelay;
|
|
38
51
|
}
|
|
39
52
|
|
|
40
53
|
/** @private */
|
|
41
54
|
get __hoverDelay() {
|
|
42
|
-
|
|
55
|
+
const popover = this.host;
|
|
56
|
+
return popover.hoverDelay != null && popover.hoverDelay >= 0 ? popover.hoverDelay : defaultHoverDelay;
|
|
43
57
|
}
|
|
44
58
|
|
|
45
59
|
/** @private */
|
|
46
60
|
get __hideDelay() {
|
|
47
|
-
|
|
61
|
+
const popover = this.host;
|
|
62
|
+
return popover.hideDelay != null && popover.hideDelay >= 0 ? popover.hideDelay : defaultHideDelay;
|
|
48
63
|
}
|
|
49
64
|
|
|
50
65
|
/**
|
|
@@ -130,12 +145,49 @@ class PopoverOpenedStateController {
|
|
|
130
145
|
* Unlike `<vaadin-tooltip>`, the popover supports rich content
|
|
131
146
|
* that can be provided by using `renderer` function.
|
|
132
147
|
*
|
|
148
|
+
* ### Styling
|
|
149
|
+
*
|
|
150
|
+
* `<vaadin-popover>` uses `<vaadin-popover-overlay>` internal
|
|
151
|
+
* themable component as the actual visible overlay.
|
|
152
|
+
*
|
|
153
|
+
* See [`<vaadin-overlay>`](#/elements/vaadin-overlay) documentation
|
|
154
|
+
* for `<vaadin-popover-overlay>` parts.
|
|
155
|
+
*
|
|
156
|
+
* In addition to `<vaadin-overlay>` parts, the following parts are available for styling:
|
|
157
|
+
*
|
|
158
|
+
* Part name | Description
|
|
159
|
+
* -----------------|-------------------------------------------
|
|
160
|
+
* `arrow` | Optional arrow pointing to the target when using `theme="arrow"`
|
|
161
|
+
*
|
|
162
|
+
* The following state attributes are available for styling:
|
|
163
|
+
*
|
|
164
|
+
* Attribute | Description
|
|
165
|
+
* -----------------|----------------------------------------
|
|
166
|
+
* `position` | Reflects the `position` property value.
|
|
167
|
+
*
|
|
168
|
+
* Note: the `theme` attribute value set on `<vaadin-popover>` is
|
|
169
|
+
* propagated to the internal `<vaadin-popover-overlay>` component.
|
|
170
|
+
*
|
|
171
|
+
* ### Custom CSS Properties
|
|
172
|
+
*
|
|
173
|
+
* The following custom CSS properties are available on the `<vaadin-popover>` element:
|
|
174
|
+
*
|
|
175
|
+
* Custom CSS property | Description
|
|
176
|
+
* ---------------------------------|-------------
|
|
177
|
+
* `--vaadin-popover-offset-top` | Used as an offset when the popover is aligned vertically below the target
|
|
178
|
+
* `--vaadin-popover-offset-bottom` | Used as an offset when the popover is aligned vertically above the target
|
|
179
|
+
* `--vaadin-popover-offset-start` | Used as an offset when the popover is aligned horizontally after the target
|
|
180
|
+
* `--vaadin-popover-offset-end` | Used as an offset when the popover is aligned horizontally before the target
|
|
181
|
+
*
|
|
182
|
+
* See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
|
|
183
|
+
*
|
|
133
184
|
* @fires {CustomEvent} opened-changed - Fired when the `opened` property changes.
|
|
185
|
+
* @fires {CustomEvent} closed - Fired when the popover is closed.
|
|
134
186
|
*
|
|
135
187
|
* @customElement
|
|
136
188
|
* @extends HTMLElement
|
|
137
189
|
* @mixes ElementMixin
|
|
138
|
-
* @mixes
|
|
190
|
+
* @mixes OverlayClassMixin
|
|
139
191
|
* @mixes PopoverPositionMixin
|
|
140
192
|
* @mixes PopoverTargetMixin
|
|
141
193
|
* @mixes ThemePropertyMixin
|
|
@@ -147,6 +199,14 @@ class Popover extends PopoverPositionMixin(
|
|
|
147
199
|
return 'vaadin-popover';
|
|
148
200
|
}
|
|
149
201
|
|
|
202
|
+
static get styles() {
|
|
203
|
+
return css`
|
|
204
|
+
:host {
|
|
205
|
+
display: none !important;
|
|
206
|
+
}
|
|
207
|
+
`;
|
|
208
|
+
}
|
|
209
|
+
|
|
150
210
|
static get properties() {
|
|
151
211
|
return {
|
|
152
212
|
/**
|
|
@@ -167,6 +227,14 @@ class Popover extends PopoverPositionMixin(
|
|
|
167
227
|
type: String,
|
|
168
228
|
},
|
|
169
229
|
|
|
230
|
+
/**
|
|
231
|
+
* When true, the popover content automatically receives focus after
|
|
232
|
+
* it is opened. Modal popovers use this behavior by default.
|
|
233
|
+
*/
|
|
234
|
+
autofocus: {
|
|
235
|
+
type: Boolean,
|
|
236
|
+
},
|
|
237
|
+
|
|
170
238
|
/**
|
|
171
239
|
* Height to be set on the overlay content.
|
|
172
240
|
*
|
|
@@ -188,6 +256,9 @@ class Popover extends PopoverPositionMixin(
|
|
|
188
256
|
/**
|
|
189
257
|
* The delay in milliseconds before the popover is opened
|
|
190
258
|
* on focus when the corresponding trigger is used.
|
|
259
|
+
*
|
|
260
|
+
* When not specified, the global default (500ms) is used.
|
|
261
|
+
*
|
|
191
262
|
* @attr {number} focus-delay
|
|
192
263
|
*/
|
|
193
264
|
focusDelay: {
|
|
@@ -198,6 +269,9 @@ class Popover extends PopoverPositionMixin(
|
|
|
198
269
|
* The delay in milliseconds before the popover is closed
|
|
199
270
|
* on losing hover, when the corresponding trigger is used.
|
|
200
271
|
* On blur, the popover is closed immediately.
|
|
272
|
+
*
|
|
273
|
+
* When not specified, the global default (500ms) is used.
|
|
274
|
+
*
|
|
201
275
|
* @attr {number} hide-delay
|
|
202
276
|
*/
|
|
203
277
|
hideDelay: {
|
|
@@ -207,6 +281,9 @@ class Popover extends PopoverPositionMixin(
|
|
|
207
281
|
/**
|
|
208
282
|
* The delay in milliseconds before the popover is opened
|
|
209
283
|
* on hover when the corresponding trigger is used.
|
|
284
|
+
*
|
|
285
|
+
* When not specified, the global default (500ms) is used.
|
|
286
|
+
*
|
|
210
287
|
* @attr {number} hover-delay
|
|
211
288
|
*/
|
|
212
289
|
hoverDelay: {
|
|
@@ -334,6 +411,36 @@ class Popover extends PopoverPositionMixin(
|
|
|
334
411
|
];
|
|
335
412
|
}
|
|
336
413
|
|
|
414
|
+
/**
|
|
415
|
+
* Sets the default focus delay to be used by all popover instances,
|
|
416
|
+
* except for those that have focus delay configured using property.
|
|
417
|
+
*
|
|
418
|
+
* @param {number} delay
|
|
419
|
+
*/
|
|
420
|
+
static setDefaultFocusDelay(focusDelay) {
|
|
421
|
+
defaultFocusDelay = focusDelay != null && focusDelay >= 0 ? focusDelay : DEFAULT_DELAY;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Sets the default hide delay to be used by all popover instances,
|
|
426
|
+
* except for those that have hide delay configured using property.
|
|
427
|
+
*
|
|
428
|
+
* @param {number} hideDelay
|
|
429
|
+
*/
|
|
430
|
+
static setDefaultHideDelay(hideDelay) {
|
|
431
|
+
defaultHideDelay = hideDelay != null && hideDelay >= 0 ? hideDelay : DEFAULT_DELAY;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Sets the default hover delay to be used by all popover instances,
|
|
436
|
+
* except for those that have hover delay configured using property.
|
|
437
|
+
*
|
|
438
|
+
* @param {number} delay
|
|
439
|
+
*/
|
|
440
|
+
static setDefaultHoverDelay(hoverDelay) {
|
|
441
|
+
defaultHoverDelay = hoverDelay != null && hoverDelay >= 0 ? hoverDelay : DEFAULT_DELAY;
|
|
442
|
+
}
|
|
443
|
+
|
|
337
444
|
constructor() {
|
|
338
445
|
super();
|
|
339
446
|
|
|
@@ -342,7 +449,6 @@ class Popover extends PopoverPositionMixin(
|
|
|
342
449
|
this.__onGlobalClick = this.__onGlobalClick.bind(this);
|
|
343
450
|
this.__onGlobalKeyDown = this.__onGlobalKeyDown.bind(this);
|
|
344
451
|
this.__onTargetClick = this.__onTargetClick.bind(this);
|
|
345
|
-
this.__onTargetKeydown = this.__onTargetKeydown.bind(this);
|
|
346
452
|
this.__onTargetFocusIn = this.__onTargetFocusIn.bind(this);
|
|
347
453
|
this.__onTargetFocusOut = this.__onTargetFocusOut.bind(this);
|
|
348
454
|
this.__onTargetMouseEnter = this.__onTargetMouseEnter.bind(this);
|
|
@@ -374,6 +480,7 @@ class Popover extends PopoverPositionMixin(
|
|
|
374
480
|
?no-vertical-overlap="${this.__computeNoVerticalOverlap(effectivePosition)}"
|
|
375
481
|
.horizontalAlign="${this.__computeHorizontalAlign(effectivePosition)}"
|
|
376
482
|
.verticalAlign="${this.__computeVerticalAlign(effectivePosition)}"
|
|
483
|
+
@mousedown="${this.__onOverlayMouseDown}"
|
|
377
484
|
@mouseenter="${this.__onOverlayMouseEnter}"
|
|
378
485
|
@mouseleave="${this.__onOverlayMouseLeave}"
|
|
379
486
|
@focusin="${this.__onOverlayFocusIn}"
|
|
@@ -383,6 +490,7 @@ class Popover extends PopoverPositionMixin(
|
|
|
383
490
|
.restoreFocusNode="${this.target}"
|
|
384
491
|
@vaadin-overlay-escape-press="${this.__onEscapePress}"
|
|
385
492
|
@vaadin-overlay-outside-click="${this.__onOutsideClick}"
|
|
493
|
+
@vaadin-overlay-open="${this.__onOverlayOpened}"
|
|
386
494
|
@vaadin-overlay-closed="${this.__onOverlayClosed}"
|
|
387
495
|
></vaadin-popover-overlay>
|
|
388
496
|
`;
|
|
@@ -413,14 +521,14 @@ class Popover extends PopoverPositionMixin(
|
|
|
413
521
|
connectedCallback() {
|
|
414
522
|
super.connectedCallback();
|
|
415
523
|
|
|
416
|
-
document.addEventListener('click', this.__onGlobalClick, true);
|
|
524
|
+
document.documentElement.addEventListener('click', this.__onGlobalClick, true);
|
|
417
525
|
}
|
|
418
526
|
|
|
419
527
|
/** @protected */
|
|
420
528
|
disconnectedCallback() {
|
|
421
529
|
super.disconnectedCallback();
|
|
422
530
|
|
|
423
|
-
document.removeEventListener('click', this.__onGlobalClick, true);
|
|
531
|
+
document.documentElement.removeEventListener('click', this.__onGlobalClick, true);
|
|
424
532
|
|
|
425
533
|
this._openedStateController.close(true);
|
|
426
534
|
}
|
|
@@ -432,7 +540,6 @@ class Popover extends PopoverPositionMixin(
|
|
|
432
540
|
*/
|
|
433
541
|
_addTargetListeners(target) {
|
|
434
542
|
target.addEventListener('click', this.__onTargetClick);
|
|
435
|
-
target.addEventListener('keydown', this.__onTargetKeydown);
|
|
436
543
|
target.addEventListener('mouseenter', this.__onTargetMouseEnter);
|
|
437
544
|
target.addEventListener('mouseleave', this.__onTargetMouseLeave);
|
|
438
545
|
target.addEventListener('focusin', this.__onTargetFocusIn);
|
|
@@ -446,7 +553,6 @@ class Popover extends PopoverPositionMixin(
|
|
|
446
553
|
*/
|
|
447
554
|
_removeTargetListeners(target) {
|
|
448
555
|
target.removeEventListener('click', this.__onTargetClick);
|
|
449
|
-
target.removeEventListener('keydown', this.__onTargetKeydown);
|
|
450
556
|
target.removeEventListener('mouseenter', this.__onTargetMouseEnter);
|
|
451
557
|
target.removeEventListener('mouseleave', this.__onTargetMouseLeave);
|
|
452
558
|
target.removeEventListener('focusin', this.__onTargetFocusIn);
|
|
@@ -500,7 +606,8 @@ class Popover extends PopoverPositionMixin(
|
|
|
500
606
|
!this.__isManual &&
|
|
501
607
|
!this.modal &&
|
|
502
608
|
!event.composedPath().some((el) => el === this._overlayElement || el === this.target) &&
|
|
503
|
-
!this.noCloseOnOutsideClick
|
|
609
|
+
!this.noCloseOnOutsideClick &&
|
|
610
|
+
isLastOverlay(this._overlayElement)
|
|
504
611
|
) {
|
|
505
612
|
this._openedStateController.close(true);
|
|
506
613
|
}
|
|
@@ -526,19 +633,104 @@ class Popover extends PopoverPositionMixin(
|
|
|
526
633
|
* @private
|
|
527
634
|
*/
|
|
528
635
|
__onGlobalKeyDown(event) {
|
|
529
|
-
|
|
636
|
+
// Modal popover uses overlay logic for Esc key and focus trap.
|
|
637
|
+
if (this.modal) {
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (
|
|
642
|
+
event.key === 'Escape' &&
|
|
643
|
+
!this.noCloseOnEsc &&
|
|
644
|
+
this.opened &&
|
|
645
|
+
!this.__isManual &&
|
|
646
|
+
isLastOverlay(this._overlayElement)
|
|
647
|
+
) {
|
|
530
648
|
// Prevent closing parent overlay (e.g. dialog)
|
|
531
649
|
event.stopPropagation();
|
|
532
650
|
this._openedStateController.close(true);
|
|
533
651
|
}
|
|
652
|
+
|
|
653
|
+
// Include popover content in the Tab order after the target.
|
|
654
|
+
if (event.key === 'Tab') {
|
|
655
|
+
if (event.shiftKey) {
|
|
656
|
+
this.__onGlobalShiftTab(event);
|
|
657
|
+
} else {
|
|
658
|
+
this.__onGlobalTab(event);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
534
661
|
}
|
|
535
662
|
|
|
536
663
|
/** @private */
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
664
|
+
__onGlobalTab(event) {
|
|
665
|
+
const overlayPart = this._overlayElement.$.overlay;
|
|
666
|
+
|
|
667
|
+
// Move focus to the popover content on target element Tab
|
|
668
|
+
if (this.target && isElementFocused(this.target)) {
|
|
669
|
+
event.preventDefault();
|
|
670
|
+
overlayPart.focus();
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Move focus to the next element after target on content Tab
|
|
675
|
+
const lastFocusable = this.__getLastFocusable(overlayPart);
|
|
676
|
+
if (lastFocusable && isElementFocused(lastFocusable)) {
|
|
677
|
+
const focusable = this.__getNextBodyFocusable(this.target);
|
|
678
|
+
if (focusable && focusable !== overlayPart) {
|
|
679
|
+
event.preventDefault();
|
|
680
|
+
focusable.focus();
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Prevent focusing the popover content on previous element Tab
|
|
686
|
+
const activeElement = getDeepActiveElement();
|
|
687
|
+
const nextFocusable = this.__getNextBodyFocusable(activeElement);
|
|
688
|
+
if (nextFocusable === overlayPart && lastFocusable) {
|
|
689
|
+
// Move focus to the last overlay focusable and do NOT prevent keydown
|
|
690
|
+
// to move focus outside the popover content (e.g. to the URL bar).
|
|
691
|
+
lastFocusable.focus();
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/** @private */
|
|
696
|
+
__onGlobalShiftTab(event) {
|
|
697
|
+
const overlayPart = this._overlayElement.$.overlay;
|
|
698
|
+
|
|
699
|
+
// Prevent restoring focus after target blur on Shift + Tab
|
|
700
|
+
if (this.target && isElementFocused(this.target) && this.__shouldRestoreFocus) {
|
|
540
701
|
this.__shouldRestoreFocus = false;
|
|
702
|
+
return;
|
|
541
703
|
}
|
|
704
|
+
|
|
705
|
+
// Move focus back to the target on overlay content Shift + Tab
|
|
706
|
+
if (this.target && isElementFocused(overlayPart)) {
|
|
707
|
+
event.preventDefault();
|
|
708
|
+
this.target.focus();
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Move focus back to the popover on next element Shift + Tab
|
|
713
|
+
const nextFocusable = this.__getNextBodyFocusable(this.target);
|
|
714
|
+
if (nextFocusable && isElementFocused(nextFocusable)) {
|
|
715
|
+
const lastFocusable = this.__getLastFocusable(overlayPart);
|
|
716
|
+
if (lastFocusable) {
|
|
717
|
+
event.preventDefault();
|
|
718
|
+
lastFocusable.focus();
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/** @private */
|
|
724
|
+
__getNextBodyFocusable(target) {
|
|
725
|
+
const focusables = getFocusableElements(document.body);
|
|
726
|
+
const idx = focusables.findIndex((el) => el === target);
|
|
727
|
+
return focusables[idx + 1];
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/** @private */
|
|
731
|
+
__getLastFocusable(container) {
|
|
732
|
+
const focusables = getFocusableElements(container);
|
|
733
|
+
return focusables.pop();
|
|
542
734
|
}
|
|
543
735
|
|
|
544
736
|
/** @private */
|
|
@@ -563,7 +755,14 @@ class Popover extends PopoverPositionMixin(
|
|
|
563
755
|
|
|
564
756
|
/** @private */
|
|
565
757
|
__onTargetFocusOut(event) {
|
|
566
|
-
if
|
|
758
|
+
// Do not close the popover on overlay focusout if it's not the last one.
|
|
759
|
+
// This covers the case when focus moves to the nested popover opened
|
|
760
|
+
// without focusing parent popover overlay (e.g. using hover trigger).
|
|
761
|
+
if (!isLastOverlay(this._overlayElement)) {
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
if ((this.__hasTrigger('focus') && this.__mouseDownInside) || this._overlayElement.contains(event.relatedTarget)) {
|
|
567
766
|
return;
|
|
568
767
|
}
|
|
569
768
|
|
|
@@ -585,6 +784,12 @@ class Popover extends PopoverPositionMixin(
|
|
|
585
784
|
|
|
586
785
|
/** @private */
|
|
587
786
|
__onTargetMouseLeave(event) {
|
|
787
|
+
// Do not close the popover on target focusout if the overlay is not the last one.
|
|
788
|
+
// This happens e.g. when opening the nested popover that uses non-modal overlay.
|
|
789
|
+
if (!isLastOverlay(this._overlayElement)) {
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
|
|
588
793
|
if (this._overlayElement.contains(event.relatedTarget)) {
|
|
589
794
|
return;
|
|
590
795
|
}
|
|
@@ -605,13 +810,40 @@ class Popover extends PopoverPositionMixin(
|
|
|
605
810
|
|
|
606
811
|
/** @private */
|
|
607
812
|
__onOverlayFocusOut(event) {
|
|
608
|
-
if
|
|
813
|
+
// Do not close the popover on overlay focusout if it's not the last one.
|
|
814
|
+
// This covers the following cases of nested overlay based components:
|
|
815
|
+
// 1. Moving focus to the nested overlay (e.g. vaadin-select, vaadin-menu-bar)
|
|
816
|
+
// 2. Closing not focused nested overlay on outside (e.g. vaadin-combo-box)
|
|
817
|
+
if (!isLastOverlay(this._overlayElement)) {
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
if (
|
|
822
|
+
(this.__hasTrigger('focus') && this.__mouseDownInside) ||
|
|
823
|
+
event.relatedTarget === this.target ||
|
|
824
|
+
this._overlayElement.contains(event.relatedTarget)
|
|
825
|
+
) {
|
|
609
826
|
return;
|
|
610
827
|
}
|
|
611
828
|
|
|
612
829
|
this.__handleFocusout();
|
|
613
830
|
}
|
|
614
831
|
|
|
832
|
+
/** @private */
|
|
833
|
+
__onOverlayMouseDown() {
|
|
834
|
+
if (this.__hasTrigger('focus')) {
|
|
835
|
+
this.__mouseDownInside = true;
|
|
836
|
+
|
|
837
|
+
document.addEventListener(
|
|
838
|
+
'mouseup',
|
|
839
|
+
() => {
|
|
840
|
+
this.__mouseDownInside = false;
|
|
841
|
+
},
|
|
842
|
+
{ once: true },
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
615
847
|
/** @private */
|
|
616
848
|
__onOverlayMouseEnter() {
|
|
617
849
|
this.__hoverInside = true;
|
|
@@ -624,6 +856,13 @@ class Popover extends PopoverPositionMixin(
|
|
|
624
856
|
|
|
625
857
|
/** @private */
|
|
626
858
|
__onOverlayMouseLeave(event) {
|
|
859
|
+
// Do not close the popover on overlay focusout if it's not the last one.
|
|
860
|
+
// This happens when opening the nested component that uses "modal" overlay
|
|
861
|
+
// setting `pointer-events: none` on the body (combo-box, date-picker etc).
|
|
862
|
+
if (!isLastOverlay(this._overlayElement)) {
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
|
|
627
866
|
if (event.relatedTarget === this.target) {
|
|
628
867
|
return;
|
|
629
868
|
}
|
|
@@ -662,6 +901,13 @@ class Popover extends PopoverPositionMixin(
|
|
|
662
901
|
this.opened = event.detail.value;
|
|
663
902
|
}
|
|
664
903
|
|
|
904
|
+
/** @private */
|
|
905
|
+
__onOverlayOpened() {
|
|
906
|
+
if (this.autofocus && !this.modal) {
|
|
907
|
+
this._overlayElement.$.overlay.focus();
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
665
911
|
/** @private */
|
|
666
912
|
__onOverlayClosed() {
|
|
667
913
|
// Reset restoring focus state after a timeout to make sure focus was restored
|
|
@@ -676,6 +922,8 @@ class Popover extends PopoverPositionMixin(
|
|
|
676
922
|
if (this.modal && this.target.style.pointerEvents) {
|
|
677
923
|
this.target.style.pointerEvents = '';
|
|
678
924
|
}
|
|
925
|
+
|
|
926
|
+
this.dispatchEvent(new CustomEvent('closed'));
|
|
679
927
|
}
|
|
680
928
|
|
|
681
929
|
/**
|
|
@@ -732,6 +980,12 @@ class Popover extends PopoverPositionMixin(
|
|
|
732
980
|
this.__updateDimension(overlay, 'width', width);
|
|
733
981
|
}
|
|
734
982
|
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Fired when the popover is closed.
|
|
986
|
+
*
|
|
987
|
+
* @event closed
|
|
988
|
+
*/
|
|
735
989
|
}
|
|
736
990
|
|
|
737
991
|
defineCustomElement(Popover);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import '@vaadin/vaadin-lumo-styles/color.js';
|
|
2
|
+
import '@vaadin/vaadin-lumo-styles/spacing.js';
|
|
2
3
|
import '@vaadin/vaadin-lumo-styles/style.js';
|
|
3
4
|
import '@vaadin/vaadin-lumo-styles/typography.js';
|
|
4
5
|
import { overlay } from '@vaadin/vaadin-lumo-styles/mixins/overlay.js';
|
|
@@ -6,15 +7,105 @@ import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themab
|
|
|
6
7
|
|
|
7
8
|
const popoverOverlay = css`
|
|
8
9
|
:host {
|
|
9
|
-
--vaadin-popover-offset-top: var(--
|
|
10
|
-
--vaadin-popover-offset-bottom: var(--
|
|
11
|
-
--vaadin-popover-offset-start: var(--
|
|
12
|
-
--vaadin-popover-offset-end: var(--
|
|
10
|
+
--vaadin-popover-offset-top: var(--_vaadin-popover-default-offset);
|
|
11
|
+
--vaadin-popover-offset-bottom: var(--_vaadin-popover-default-offset);
|
|
12
|
+
--vaadin-popover-offset-start: var(--_vaadin-popover-default-offset);
|
|
13
|
+
--vaadin-popover-offset-end: var(--_vaadin-popover-default-offset);
|
|
14
|
+
--vaadin-popover-arrow-size: 0.5rem;
|
|
15
|
+
--_vaadin-popover-default-offset: var(--lumo-space-xs);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
[part='overlay'] {
|
|
19
|
+
outline: none;
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
[part='content'] {
|
|
16
23
|
padding: var(--lumo-space-xs) var(--lumo-space-s);
|
|
17
24
|
}
|
|
25
|
+
|
|
26
|
+
:host([theme~='no-padding']) [part='content'] {
|
|
27
|
+
padding: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
:host([theme~='arrow']) {
|
|
31
|
+
--_vaadin-popover-default-offset: calc(var(--lumo-space-s) + var(--vaadin-popover-arrow-size) / 2);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* top / bottom position */
|
|
35
|
+
:host([theme~='arrow'][position^='top']) [part='arrow'],
|
|
36
|
+
:host([theme~='arrow'][position^='bottom']) [part='arrow'] {
|
|
37
|
+
border-left: var(--vaadin-popover-arrow-size) solid transparent;
|
|
38
|
+
border-right: var(--vaadin-popover-arrow-size) solid transparent;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
:host([theme~='arrow'][position^='bottom'][bottom-aligned]) [part='arrow'],
|
|
42
|
+
:host([theme~='arrow'][position^='top'][bottom-aligned]) [part='arrow'] {
|
|
43
|
+
bottom: calc(var(--vaadin-popover-arrow-size) * -1);
|
|
44
|
+
border-top: var(--vaadin-popover-arrow-size) solid var(--lumo-base-color);
|
|
45
|
+
filter: drop-shadow(0 2px 1px var(--lumo-shade-10pct));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
:host([theme~='arrow'][position^='bottom'][top-aligned]) [part='arrow'],
|
|
49
|
+
:host([theme~='arrow'][position^='top'][top-aligned]) [part='arrow'] {
|
|
50
|
+
top: calc(var(--vaadin-popover-arrow-size) * -1);
|
|
51
|
+
border-bottom: var(--vaadin-popover-arrow-size) solid var(--lumo-base-color);
|
|
52
|
+
filter: drop-shadow(0 -2px 1px var(--lumo-shade-10pct));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
:host([theme~='arrow'][position^='bottom'][start-aligned]) [part='arrow'],
|
|
56
|
+
:host([theme~='arrow'][position^='top'][start-aligned]) [part='arrow'] {
|
|
57
|
+
transform: translateX(-50%);
|
|
58
|
+
inset-inline-start: 1.5rem;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
:host([theme~='arrow'][position^='bottom'][end-aligned]) [part='arrow'],
|
|
62
|
+
:host([theme~='arrow'][position^='top'][end-aligned]) [part='arrow'] {
|
|
63
|
+
transform: translateX(50%);
|
|
64
|
+
inset-inline-end: 1.5rem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
:host([theme~='arrow'][position^='bottom'][arrow-centered]) [part='arrow'],
|
|
68
|
+
:host([theme~='arrow'][position^='top'][arrow-centered]) [part='arrow'] {
|
|
69
|
+
transform: translateX(-50%);
|
|
70
|
+
inset-inline-start: 50%;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* start / end position */
|
|
74
|
+
:host([theme~='arrow'][position^='start']) [part='arrow'],
|
|
75
|
+
:host([theme~='arrow'][position^='end']) [part='arrow'] {
|
|
76
|
+
border-top: var(--vaadin-popover-arrow-size) solid transparent;
|
|
77
|
+
border-bottom: var(--vaadin-popover-arrow-size) solid transparent;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
:host([theme~='arrow'][position^='start'][start-aligned]) [part='arrow'],
|
|
81
|
+
:host([theme~='arrow'][position^='end'][start-aligned]) [part='arrow'] {
|
|
82
|
+
inset-inline-start: calc(var(--vaadin-popover-arrow-size) * -1);
|
|
83
|
+
border-right: var(--vaadin-popover-arrow-size) solid var(--lumo-base-color);
|
|
84
|
+
filter: drop-shadow(-2px 0 1px var(--lumo-shade-10pct));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
:host([theme~='arrow'][position^='start'][end-aligned]) [part='arrow'],
|
|
88
|
+
:host([theme~='arrow'][position^='end'][end-aligned]) [part='arrow'] {
|
|
89
|
+
inset-inline-end: calc(var(--vaadin-popover-arrow-size) * -1);
|
|
90
|
+
border-left: var(--vaadin-popover-arrow-size) solid var(--lumo-base-color);
|
|
91
|
+
filter: drop-shadow(2px 0 1px var(--lumo-shade-10pct));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
:host([theme~='arrow'][position^='start'][top-aligned]) [part='arrow'],
|
|
95
|
+
:host([theme~='arrow'][position^='end'][top-aligned]) [part='arrow'] {
|
|
96
|
+
top: 0.5rem;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
:host([theme~='arrow'][position='start'][top-aligned]) [part='arrow'],
|
|
100
|
+
:host([theme~='arrow'][position='end'][top-aligned]) [part='arrow'] {
|
|
101
|
+
top: 50%;
|
|
102
|
+
transform: translateY(-50%);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
:host([theme~='arrow'][position^='start'][bottom-aligned]) [part='arrow'],
|
|
106
|
+
:host([theme~='arrow'][position^='end'][bottom-aligned]) [part='arrow'] {
|
|
107
|
+
bottom: 0.5rem;
|
|
108
|
+
}
|
|
18
109
|
`;
|
|
19
110
|
|
|
20
111
|
registerStyles('vaadin-popover-overlay', [overlay, popoverOverlay], { moduleId: 'lumo-popover-overlay' });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import '@vaadin/vaadin-material-styles/color.js';
|