@vaadin/popover 25.0.0-alpha9 → 25.0.0-beta2

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.
@@ -14,10 +14,12 @@ import {
14
14
  } from '@vaadin/a11y-base/src/focus-utils.js';
15
15
  import { defineCustomElement } from '@vaadin/component-base/src/define.js';
16
16
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
17
- import { OverlayClassMixin } from '@vaadin/component-base/src/overlay-class-mixin.js';
18
17
  import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
19
18
  import { generateUniqueId } from '@vaadin/component-base/src/unique-id-utils.js';
20
- import { isLastOverlay as isLastOverlayBase } from '@vaadin/overlay/src/vaadin-overlay-stack-mixin.js';
19
+ import {
20
+ hasOnlyNestedOverlays,
21
+ isLastOverlay as isLastOverlayBase,
22
+ } from '@vaadin/overlay/src/vaadin-overlay-stack-mixin.js';
21
23
  import { ThemePropertyMixin } from '@vaadin/vaadin-themable-mixin/vaadin-theme-property-mixin.js';
22
24
  import { PopoverPositionMixin } from './vaadin-popover-position-mixin.js';
23
25
  import { PopoverTargetMixin } from './vaadin-popover-target-mixin.js';
@@ -203,13 +205,12 @@ const isLastOverlay = (overlay) => {
203
205
  * @customElement
204
206
  * @extends HTMLElement
205
207
  * @mixes ElementMixin
206
- * @mixes OverlayClassMixin
207
208
  * @mixes PopoverPositionMixin
208
209
  * @mixes PopoverTargetMixin
209
210
  * @mixes ThemePropertyMixin
210
211
  */
211
212
  class Popover extends PopoverPositionMixin(
212
- PopoverTargetMixin(OverlayClassMixin(ThemePropertyMixin(ElementMixin(PolylitMixin(LitElement))))),
213
+ PopoverTargetMixin(ThemePropertyMixin(ElementMixin(PolylitMixin(LitElement)))),
213
214
  ) {
214
215
  static get is() {
215
216
  return 'vaadin-popover';
@@ -217,8 +218,21 @@ class Popover extends PopoverPositionMixin(
217
218
 
218
219
  static get styles() {
219
220
  return css`
220
- :host {
221
- display: contents;
221
+ :host([opened]),
222
+ :host([opening]),
223
+ :host([closing]) {
224
+ display: block !important;
225
+ position: absolute;
226
+ outline: none;
227
+ }
228
+
229
+ :host,
230
+ :host([hidden]) {
231
+ display: none !important;
232
+ }
233
+
234
+ :host(:focus-visible) ::part(overlay) {
235
+ outline: var(--vaadin-focus-ring-width) solid var(--vaadin-focus-ring-color);
222
236
  }
223
237
  `;
224
238
  }
@@ -229,6 +243,7 @@ class Popover extends PopoverPositionMixin(
229
243
  * String used to label the popover to screen reader users.
230
244
  *
231
245
  * @attr {string} accessible-name
246
+ * @deprecated Use `aria-label` attribute on the popover instead
232
247
  */
233
248
  accessibleName: {
234
249
  type: String,
@@ -238,6 +253,7 @@ class Popover extends PopoverPositionMixin(
238
253
  * Id of the element used as label of the popover to screen reader users.
239
254
  *
240
255
  * @attr {string} accessible-name-ref
256
+ * @deprecated Use `aria-labelledby` attribute on the popover instead
241
257
  */
242
258
  accessibleNameRef: {
243
259
  type: String,
@@ -252,7 +268,7 @@ class Popover extends PopoverPositionMixin(
252
268
  },
253
269
 
254
270
  /**
255
- * Set the height of the overlay.
271
+ * Set the height of the popover.
256
272
  * If a unitless number is provided, pixels are assumed.
257
273
  */
258
274
  height: {
@@ -260,7 +276,7 @@ class Popover extends PopoverPositionMixin(
260
276
  },
261
277
 
262
278
  /**
263
- * Set the width of the overlay.
279
+ * Set the width of the popover.
264
280
  * If a unitless number is provided, pixels are assumed.
265
281
  */
266
282
  width: {
@@ -305,31 +321,43 @@ class Popover extends PopoverPositionMixin(
305
321
  },
306
322
 
307
323
  /**
308
- * True if the popover overlay is opened, false otherwise.
324
+ * True if the popover is visible and available for interaction.
309
325
  */
310
326
  opened: {
311
327
  type: Boolean,
312
328
  value: false,
313
329
  notify: true,
330
+ reflectToAttribute: true,
314
331
  observer: '__openedChanged',
315
332
  },
316
333
 
317
334
  /**
318
- * The `role` attribute value to be set on the overlay.
335
+ * The `role` attribute value to be set on the popover.
336
+ * When not specified, defaults to 'dialog'.
337
+ */
338
+ role: {
339
+ type: String,
340
+ reflectToAttribute: true,
341
+ },
342
+
343
+ /**
344
+ * The `role` attribute value to be set on the popover.
319
345
  *
320
346
  * @attr {string} overlay-role
347
+ * @deprecated Use standard `role` attribute on the popover instead
321
348
  */
322
349
  overlayRole: {
323
350
  type: String,
324
- value: 'dialog',
325
351
  },
326
352
 
327
353
  /**
328
- * Custom function for rendering the content of the overlay.
354
+ * Custom function for rendering the content of the popover.
329
355
  * Receives two arguments:
330
356
  *
331
357
  * - `root` The root container DOM element. Append your content to it.
332
- * - `popover` The reference to the `vaadin-popover` element (overlay host).
358
+ * - `popover` The reference to the `vaadin-popover` element.
359
+ *
360
+ * @deprecated Use the content in the `vaadin-popover` via default slot
333
361
  */
334
362
  renderer: {
335
363
  type: Object,
@@ -338,7 +366,7 @@ class Popover extends PopoverPositionMixin(
338
366
  /**
339
367
  * When true, the popover prevents interacting with background elements
340
368
  * by setting `pointer-events` style on the document body to `none`.
341
- * This also enables trapping focus inside the overlay.
369
+ * This also enables trapping focus inside the popover.
342
370
  */
343
371
  modal: {
344
372
  type: Boolean,
@@ -346,7 +374,7 @@ class Popover extends PopoverPositionMixin(
346
374
  },
347
375
 
348
376
  /**
349
- * Set to true to disable closing popover overlay on outside click.
377
+ * Set to true to disable closing popover on outside click.
350
378
  *
351
379
  * @attr {boolean} no-close-on-outside-click
352
380
  */
@@ -356,10 +384,7 @@ class Popover extends PopoverPositionMixin(
356
384
  },
357
385
 
358
386
  /**
359
- * Set to true to disable closing popover overlay on Escape press.
360
- * When the popover is modal, pressing Escape anywhere in the
361
- * document closes the overlay. Otherwise, only Escape press
362
- * from the popover itself or its target closes the overlay.
387
+ * Set to true to disable closing popover on Escape press.
363
388
  *
364
389
  * @attr {boolean} no-close-on-esc
365
390
  */
@@ -369,15 +394,15 @@ class Popover extends PopoverPositionMixin(
369
394
  },
370
395
 
371
396
  /**
372
- * Popover trigger mode, used to configure how the overlay is opened or closed.
397
+ * Popover trigger mode, used to configure how the popover is opened or closed.
373
398
  * Could be set to multiple by providing an array, e.g. `trigger = ['hover', 'focus']`.
374
399
  *
375
400
  * Supported values:
376
401
  * - `click` (default) - opens and closes on target click.
377
402
  * - `hover` - opens on target mouseenter, closes on target mouseleave. Moving mouse
378
- * to the popover overlay content keeps the overlay opened.
403
+ * to the popover content keeps the popover opened.
379
404
  * - `focus` - opens on target focus, closes on target blur. Moving focus to the
380
- * popover overlay content keeps the overlay opened.
405
+ * popover content keeps the popover opened.
381
406
  *
382
407
  * In addition to the behavior specified by `trigger`, the popover can be closed by:
383
408
  * - pressing Escape key (unless `noCloseOnEsc` property is true)
@@ -393,7 +418,7 @@ class Popover extends PopoverPositionMixin(
393
418
  },
394
419
 
395
420
  /**
396
- * When true, the overlay has a backdrop (modality curtain) on top of the
421
+ * When true, the popover has a backdrop (modality curtain) on top of the
397
422
  * underlying page content, covering the whole viewport.
398
423
  *
399
424
  * @attr {boolean} with-backdrop
@@ -413,7 +438,7 @@ class Popover extends PopoverPositionMixin(
413
438
  }
414
439
 
415
440
  static get observers() {
416
- return ['__sizeChanged(width, height, _overlayElement)', '__updateAriaAttributes(opened, overlayRole, target)'];
441
+ return ['__updateAriaAttributes(opened, role, target)'];
417
442
  }
418
443
 
419
444
  /**
@@ -451,7 +476,6 @@ class Popover extends PopoverPositionMixin(
451
476
 
452
477
  this.__generatedId = `vaadin-popover-${generateUniqueId()}`;
453
478
 
454
- this.__onGlobalClick = this.__onGlobalClick.bind(this);
455
479
  this.__onGlobalKeyDown = this.__onGlobalKeyDown.bind(this);
456
480
  this.__onTargetClick = this.__onTargetClick.bind(this);
457
481
  this.__onTargetFocusIn = this.__onTargetFocusIn.bind(this);
@@ -506,6 +530,8 @@ class Popover extends PopoverPositionMixin(
506
530
  * While performing the update, it invokes the renderer passed in the `renderer` property.
507
531
  *
508
532
  * It is not guaranteed that the update happens immediately (synchronously) after it is requested.
533
+ *
534
+ * @deprecated Add content elements as children of the popover using default slot
509
535
  */
510
536
  requestContentUpdate() {
511
537
  if (!this.renderer || !this._overlayElement) {
@@ -520,14 +546,38 @@ class Popover extends PopoverPositionMixin(
520
546
  super.ready();
521
547
 
522
548
  this._overlayElement = this.$.overlay;
549
+
550
+ this.setAttribute('tabindex', '0');
551
+
552
+ this.addEventListener('focusin', (e) => {
553
+ this.__onFocusIn(e);
554
+ });
555
+
556
+ this.addEventListener('focusout', (e) => {
557
+ this.__onFocusOut(e);
558
+ });
559
+
560
+ if (!this.hasAttribute('role')) {
561
+ this.role = 'dialog';
562
+ }
563
+ }
564
+
565
+ /** @protected */
566
+ willUpdate(props) {
567
+ super.willUpdate(props);
568
+
569
+ if (props.has('overlayRole')) {
570
+ this.role = this.overlayRole;
571
+ }
523
572
  }
524
573
 
525
574
  /** @protected */
526
575
  updated(props) {
527
576
  super.updated(props);
528
577
 
529
- if (props.has('overlayRole')) {
530
- this.setAttribute('role', this.overlayRole);
578
+ if (props.has('width') || props.has('height')) {
579
+ const { width, height } = this;
580
+ requestAnimationFrame(() => this.$.overlay.setBounds({ width, height }, false));
531
581
  }
532
582
 
533
583
  if (props.has('accessibleName')) {
@@ -545,6 +595,14 @@ class Popover extends PopoverPositionMixin(
545
595
  this.removeAttribute('aria-labelledby');
546
596
  }
547
597
  }
598
+
599
+ if (props.has('modal')) {
600
+ if (this.modal) {
601
+ this.setAttribute('aria-modal', 'true');
602
+ } else {
603
+ this.removeAttribute('aria-modal');
604
+ }
605
+ }
548
606
  }
549
607
 
550
608
  /** @protected */
@@ -555,16 +613,12 @@ class Popover extends PopoverPositionMixin(
555
613
  if (!this.id) {
556
614
  this.id = this.__generatedId;
557
615
  }
558
-
559
- document.documentElement.addEventListener('click', this.__onGlobalClick, true);
560
616
  }
561
617
 
562
618
  /** @protected */
563
619
  disconnectedCallback() {
564
620
  super.disconnectedCallback();
565
621
 
566
- document.documentElement.removeEventListener('click', this.__onGlobalClick, true);
567
-
568
622
  // Automatically close popover when it is removed from DOM
569
623
  // Avoid closing if the popover is just moved in the DOM
570
624
  queueMicrotask(() => {
@@ -610,7 +664,7 @@ class Popover extends PopoverPositionMixin(
610
664
  }
611
665
 
612
666
  /** @private */
613
- __updateAriaAttributes(opened, overlayRole, target) {
667
+ __updateAriaAttributes(opened, role, target) {
614
668
  if (this.__oldTarget) {
615
669
  const oldEffectiveTarget = this.__oldTarget.ariaTarget || this.__oldTarget;
616
670
  oldEffectiveTarget.removeAttribute('aria-haspopup');
@@ -621,7 +675,7 @@ class Popover extends PopoverPositionMixin(
621
675
  if (target) {
622
676
  const effectiveTarget = target.ariaTarget || target;
623
677
 
624
- const isDialog = overlayRole === 'dialog' || overlayRole === 'alertdialog';
678
+ const isDialog = role === 'dialog' || role === 'alertdialog';
625
679
  effectiveTarget.setAttribute('aria-haspopup', isDialog ? 'dialog' : 'true');
626
680
 
627
681
  effectiveTarget.setAttribute('aria-expanded', opened ? 'true' : 'false');
@@ -636,23 +690,6 @@ class Popover extends PopoverPositionMixin(
636
690
  }
637
691
  }
638
692
 
639
- /**
640
- * Overlay's global outside click listener doesn't work when
641
- * the overlay is modeless, so we use a separate listener.
642
- * @private
643
- */
644
- __onGlobalClick(event) {
645
- if (
646
- this.opened &&
647
- !this.modal &&
648
- !event.composedPath().some((el) => el === this._overlayElement || el === this.target) &&
649
- !this.noCloseOnOutsideClick &&
650
- isLastOverlay(this._overlayElement)
651
- ) {
652
- this._openedStateController.close(true);
653
- }
654
- }
655
-
656
693
  /** @private */
657
694
  __onTargetClick() {
658
695
  if (this.__hasTrigger('click')) {
@@ -673,17 +710,11 @@ class Popover extends PopoverPositionMixin(
673
710
  * @private
674
711
  */
675
712
  __onGlobalKeyDown(event) {
676
- // Modal popover uses overlay logic for Esc key and focus trap.
713
+ // Modal popover uses overlay logic focus trap.
677
714
  if (this.modal) {
678
715
  return;
679
716
  }
680
717
 
681
- if (event.key === 'Escape' && !this.noCloseOnEsc && this.opened && isLastOverlay(this._overlayElement)) {
682
- // Prevent closing parent overlay (e.g. dialog)
683
- event.stopPropagation();
684
- this._openedStateController.close(true);
685
- }
686
-
687
718
  // Include popover content in the Tab order after the target.
688
719
  if (event.key === 'Tab') {
689
720
  if (event.shiftKey) {
@@ -696,20 +727,18 @@ class Popover extends PopoverPositionMixin(
696
727
 
697
728
  /** @private */
698
729
  __onGlobalTab(event) {
699
- const overlayPart = this._overlayElement.$.overlay;
700
-
701
- // Move focus to the popover content on target element Tab
730
+ // Move focus to the popover on target element Tab
702
731
  if (this.target && isElementFocused(this.__getTargetFocusable())) {
703
732
  event.preventDefault();
704
- overlayPart.focus();
733
+ this.focus();
705
734
  return;
706
735
  }
707
736
 
708
737
  // Move focus to the next element after target on content Tab
709
- const lastFocusable = this.__getLastFocusable(overlayPart);
738
+ const lastFocusable = this.__getLastFocusable(this);
710
739
  if (lastFocusable && isElementFocused(lastFocusable)) {
711
740
  const focusable = this.__getNextBodyFocusable(this.__getTargetFocusable());
712
- if (focusable && focusable !== overlayPart) {
741
+ if (focusable && focusable !== this) {
713
742
  event.preventDefault();
714
743
  focusable.focus();
715
744
  return;
@@ -719,7 +748,7 @@ class Popover extends PopoverPositionMixin(
719
748
  // Prevent focusing the popover content on previous element Tab
720
749
  const activeElement = getDeepActiveElement();
721
750
  const nextFocusable = this.__getNextBodyFocusable(activeElement);
722
- if (nextFocusable === overlayPart && lastFocusable) {
751
+ if (nextFocusable === this && lastFocusable) {
723
752
  // Move focus to the last overlay focusable and do NOT prevent keydown
724
753
  // to move focus outside the popover content (e.g. to the URL bar).
725
754
  lastFocusable.focus();
@@ -728,16 +757,14 @@ class Popover extends PopoverPositionMixin(
728
757
 
729
758
  /** @private */
730
759
  __onGlobalShiftTab(event) {
731
- const overlayPart = this._overlayElement.$.overlay;
732
-
733
760
  // Prevent restoring focus after target blur on Shift + Tab
734
761
  if (this.target && isElementFocused(this.__getTargetFocusable()) && this.__shouldRestoreFocus) {
735
762
  this.__shouldRestoreFocus = false;
736
763
  return;
737
764
  }
738
765
 
739
- // Move focus back to the target on overlay content Shift + Tab
740
- if (this.target && isElementFocused(overlayPart)) {
766
+ // Move focus back to the target on popover Shift + Tab
767
+ if (this.target && isElementFocused(this)) {
741
768
  event.preventDefault();
742
769
  this.__getTargetFocusable().focus();
743
770
  return;
@@ -746,7 +773,7 @@ class Popover extends PopoverPositionMixin(
746
773
  // Move focus back to the popover on next element Shift + Tab
747
774
  const nextFocusable = this.__getNextBodyFocusable(this.__getTargetFocusable());
748
775
  if (nextFocusable && isElementFocused(nextFocusable)) {
749
- const lastFocusable = this.__getLastFocusable(overlayPart);
776
+ const lastFocusable = this.__getLastFocusable(this);
750
777
  if (lastFocusable) {
751
778
  event.preventDefault();
752
779
  lastFocusable.focus();
@@ -799,10 +826,14 @@ class Popover extends PopoverPositionMixin(
799
826
 
800
827
  /** @private */
801
828
  __onTargetFocusOut(event) {
802
- // Do not close the popover on overlay focusout if it's not the last one.
829
+ // Do not close if there is a nested overlay that should be closed through some method first.
803
830
  // This covers the case when focus moves to the nested popover opened
804
831
  // without focusing parent popover overlay (e.g. using hover trigger).
805
- if (this._overlayElement.opened && !isLastOverlay(this._overlayElement)) {
832
+ if (
833
+ this._overlayElement.opened &&
834
+ !isLastOverlay(this._overlayElement) &&
835
+ hasOnlyNestedOverlays(this._overlayElement)
836
+ ) {
806
837
  return;
807
838
  }
808
839
 
@@ -828,13 +859,16 @@ class Popover extends PopoverPositionMixin(
828
859
 
829
860
  /** @private */
830
861
  __onTargetMouseLeave(event) {
831
- // Do not close the popover on target focusout if the overlay is not the last one.
832
- // This happens e.g. when opening the nested popover that uses non-modal overlay.
833
- if (this._overlayElement.opened && !isLastOverlay(this._overlayElement)) {
862
+ // Do not close if the pointer moves to the overlay
863
+ if (this.contains(event.relatedTarget)) {
834
864
  return;
835
865
  }
836
-
837
- if (this.contains(event.relatedTarget)) {
866
+ // Do not close if there is a nested overlay that should be closed through some method first.
867
+ if (
868
+ this._overlayElement.opened &&
869
+ !isLastOverlay(this._overlayElement) &&
870
+ hasOnlyNestedOverlays(this._overlayElement)
871
+ ) {
838
872
  return;
839
873
  }
840
874
 
@@ -842,7 +876,7 @@ class Popover extends PopoverPositionMixin(
842
876
  }
843
877
 
844
878
  /** @private */
845
- __onOverlayFocusIn() {
879
+ __onFocusIn() {
846
880
  this.__focusInside = true;
847
881
 
848
882
  // When using Tab to move focus, restoring focus is reset. However, if pressing Tab
@@ -853,19 +887,18 @@ class Popover extends PopoverPositionMixin(
853
887
  }
854
888
 
855
889
  /** @private */
856
- __onOverlayFocusOut(event) {
857
- // Do not close the popover on overlay focusout if it's not the last one.
890
+ __onFocusOut(event) {
891
+ // Do not close if there is a nested overlay that should be closed through some method first.
858
892
  // This covers the following cases of nested overlay based components:
859
893
  // 1. Moving focus to the nested overlay (e.g. vaadin-select, vaadin-menu-bar)
860
894
  // 2. Closing not focused nested overlay on outside (e.g. vaadin-combo-box)
861
- if (!isLastOverlay(this._overlayElement)) {
895
+ if (!isLastOverlay(this._overlayElement) && hasOnlyNestedOverlays(this._overlayElement)) {
862
896
  return;
863
897
  }
864
898
 
865
899
  if (
866
900
  (this.__hasTrigger('focus') && this.__mouseDownInside) ||
867
901
  event.relatedTarget === this.target ||
868
- event.relatedTarget === this._overlayElement ||
869
902
  this.contains(event.relatedTarget)
870
903
  ) {
871
904
  return;
@@ -901,14 +934,12 @@ class Popover extends PopoverPositionMixin(
901
934
 
902
935
  /** @private */
903
936
  __onOverlayMouseLeave(event) {
904
- // Do not close the popover on overlay focusout if it's not the last one.
905
- // This happens when opening the nested component that uses "modal" overlay
906
- // setting `pointer-events: none` on the body (combo-box, date-picker etc).
907
- if (!isLastOverlay(this._overlayElement)) {
937
+ // Do not close if the pointer moves to the target
938
+ if (event.relatedTarget === this.target) {
908
939
  return;
909
940
  }
910
-
911
- if (event.relatedTarget === this.target) {
941
+ // Do not close if there is a nested overlay that should be closed through some method first.
942
+ if (!isLastOverlay(this._overlayElement) && hasOnlyNestedOverlays(this._overlayElement)) {
912
943
  return;
913
944
  }
914
945
 
@@ -954,7 +985,7 @@ class Popover extends PopoverPositionMixin(
954
985
  /** @private */
955
986
  __onOverlayOpened() {
956
987
  if (this.autofocus && !this.modal) {
957
- this._overlayElement.$.overlay.focus();
988
+ this.focus();
958
989
  }
959
990
  }
960
991
 
@@ -969,7 +1000,7 @@ class Popover extends PopoverPositionMixin(
969
1000
  }
970
1001
 
971
1002
  // Restore pointer-events set when opening on hover.
972
- if (this.modal && this.target.style.pointerEvents) {
1003
+ if (this.modal && this.target && this.target.style.pointerEvents) {
973
1004
  this.target.style.pointerEvents = '';
974
1005
  }
975
1006
 
@@ -1001,13 +1032,6 @@ class Popover extends PopoverPositionMixin(
1001
1032
  return Array.isArray(this.trigger) && this.trigger.includes(trigger);
1002
1033
  }
1003
1034
 
1004
- /** @private */
1005
- __sizeChanged(width, height, overlay) {
1006
- if (overlay) {
1007
- requestAnimationFrame(() => overlay.setBounds({ width, height }, false));
1008
- }
1009
- }
1010
-
1011
1035
  /**
1012
1036
  * Fired when the popover is closed.
1013
1037
  *
package/vaadin-popover.js CHANGED
@@ -1,2 +1,2 @@
1
- import './theme/lumo/vaadin-popover.js';
1
+ import './src/vaadin-popover.js';
2
2
  export * from './src/vaadin-popover.js';