@vaadin/popover 25.0.0-alpha8 → 25.0.0-beta1

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);
@@ -469,7 +493,6 @@ class Popover extends PopoverPositionMixin(
469
493
  return html`
470
494
  <vaadin-popover-overlay
471
495
  id="overlay"
472
- popover="manual"
473
496
  .renderer="${this.renderer}"
474
497
  .owner="${this}"
475
498
  theme="${ifDefined(this._theme)}"
@@ -507,6 +530,8 @@ class Popover extends PopoverPositionMixin(
507
530
  * While performing the update, it invokes the renderer passed in the `renderer` property.
508
531
  *
509
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
510
535
  */
511
536
  requestContentUpdate() {
512
537
  if (!this.renderer || !this._overlayElement) {
@@ -521,14 +546,38 @@ class Popover extends PopoverPositionMixin(
521
546
  super.ready();
522
547
 
523
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
+ }
524
572
  }
525
573
 
526
574
  /** @protected */
527
575
  updated(props) {
528
576
  super.updated(props);
529
577
 
530
- if (props.has('overlayRole')) {
531
- 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));
532
581
  }
533
582
 
534
583
  if (props.has('accessibleName')) {
@@ -546,6 +595,14 @@ class Popover extends PopoverPositionMixin(
546
595
  this.removeAttribute('aria-labelledby');
547
596
  }
548
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
+ }
549
606
  }
550
607
 
551
608
  /** @protected */
@@ -556,16 +613,12 @@ class Popover extends PopoverPositionMixin(
556
613
  if (!this.id) {
557
614
  this.id = this.__generatedId;
558
615
  }
559
-
560
- document.documentElement.addEventListener('click', this.__onGlobalClick, true);
561
616
  }
562
617
 
563
618
  /** @protected */
564
619
  disconnectedCallback() {
565
620
  super.disconnectedCallback();
566
621
 
567
- document.documentElement.removeEventListener('click', this.__onGlobalClick, true);
568
-
569
622
  // Automatically close popover when it is removed from DOM
570
623
  // Avoid closing if the popover is just moved in the DOM
571
624
  queueMicrotask(() => {
@@ -611,7 +664,7 @@ class Popover extends PopoverPositionMixin(
611
664
  }
612
665
 
613
666
  /** @private */
614
- __updateAriaAttributes(opened, overlayRole, target) {
667
+ __updateAriaAttributes(opened, role, target) {
615
668
  if (this.__oldTarget) {
616
669
  const oldEffectiveTarget = this.__oldTarget.ariaTarget || this.__oldTarget;
617
670
  oldEffectiveTarget.removeAttribute('aria-haspopup');
@@ -622,7 +675,7 @@ class Popover extends PopoverPositionMixin(
622
675
  if (target) {
623
676
  const effectiveTarget = target.ariaTarget || target;
624
677
 
625
- const isDialog = overlayRole === 'dialog' || overlayRole === 'alertdialog';
678
+ const isDialog = role === 'dialog' || role === 'alertdialog';
626
679
  effectiveTarget.setAttribute('aria-haspopup', isDialog ? 'dialog' : 'true');
627
680
 
628
681
  effectiveTarget.setAttribute('aria-expanded', opened ? 'true' : 'false');
@@ -637,23 +690,6 @@ class Popover extends PopoverPositionMixin(
637
690
  }
638
691
  }
639
692
 
640
- /**
641
- * Overlay's global outside click listener doesn't work when
642
- * the overlay is modeless, so we use a separate listener.
643
- * @private
644
- */
645
- __onGlobalClick(event) {
646
- if (
647
- this.opened &&
648
- !this.modal &&
649
- !event.composedPath().some((el) => el === this._overlayElement || el === this.target) &&
650
- !this.noCloseOnOutsideClick &&
651
- isLastOverlay(this._overlayElement)
652
- ) {
653
- this._openedStateController.close(true);
654
- }
655
- }
656
-
657
693
  /** @private */
658
694
  __onTargetClick() {
659
695
  if (this.__hasTrigger('click')) {
@@ -674,17 +710,11 @@ class Popover extends PopoverPositionMixin(
674
710
  * @private
675
711
  */
676
712
  __onGlobalKeyDown(event) {
677
- // Modal popover uses overlay logic for Esc key and focus trap.
713
+ // Modal popover uses overlay logic focus trap.
678
714
  if (this.modal) {
679
715
  return;
680
716
  }
681
717
 
682
- if (event.key === 'Escape' && !this.noCloseOnEsc && this.opened && isLastOverlay(this._overlayElement)) {
683
- // Prevent closing parent overlay (e.g. dialog)
684
- event.stopPropagation();
685
- this._openedStateController.close(true);
686
- }
687
-
688
718
  // Include popover content in the Tab order after the target.
689
719
  if (event.key === 'Tab') {
690
720
  if (event.shiftKey) {
@@ -697,20 +727,18 @@ class Popover extends PopoverPositionMixin(
697
727
 
698
728
  /** @private */
699
729
  __onGlobalTab(event) {
700
- const overlayPart = this._overlayElement.$.overlay;
701
-
702
- // Move focus to the popover content on target element Tab
730
+ // Move focus to the popover on target element Tab
703
731
  if (this.target && isElementFocused(this.__getTargetFocusable())) {
704
732
  event.preventDefault();
705
- overlayPart.focus();
733
+ this.focus();
706
734
  return;
707
735
  }
708
736
 
709
737
  // Move focus to the next element after target on content Tab
710
- const lastFocusable = this.__getLastFocusable(overlayPart);
738
+ const lastFocusable = this.__getLastFocusable(this);
711
739
  if (lastFocusable && isElementFocused(lastFocusable)) {
712
740
  const focusable = this.__getNextBodyFocusable(this.__getTargetFocusable());
713
- if (focusable && focusable !== overlayPart) {
741
+ if (focusable && focusable !== this) {
714
742
  event.preventDefault();
715
743
  focusable.focus();
716
744
  return;
@@ -720,7 +748,7 @@ class Popover extends PopoverPositionMixin(
720
748
  // Prevent focusing the popover content on previous element Tab
721
749
  const activeElement = getDeepActiveElement();
722
750
  const nextFocusable = this.__getNextBodyFocusable(activeElement);
723
- if (nextFocusable === overlayPart && lastFocusable) {
751
+ if (nextFocusable === this && lastFocusable) {
724
752
  // Move focus to the last overlay focusable and do NOT prevent keydown
725
753
  // to move focus outside the popover content (e.g. to the URL bar).
726
754
  lastFocusable.focus();
@@ -729,16 +757,14 @@ class Popover extends PopoverPositionMixin(
729
757
 
730
758
  /** @private */
731
759
  __onGlobalShiftTab(event) {
732
- const overlayPart = this._overlayElement.$.overlay;
733
-
734
760
  // Prevent restoring focus after target blur on Shift + Tab
735
761
  if (this.target && isElementFocused(this.__getTargetFocusable()) && this.__shouldRestoreFocus) {
736
762
  this.__shouldRestoreFocus = false;
737
763
  return;
738
764
  }
739
765
 
740
- // Move focus back to the target on overlay content Shift + Tab
741
- if (this.target && isElementFocused(overlayPart)) {
766
+ // Move focus back to the target on popover Shift + Tab
767
+ if (this.target && isElementFocused(this)) {
742
768
  event.preventDefault();
743
769
  this.__getTargetFocusable().focus();
744
770
  return;
@@ -747,7 +773,7 @@ class Popover extends PopoverPositionMixin(
747
773
  // Move focus back to the popover on next element Shift + Tab
748
774
  const nextFocusable = this.__getNextBodyFocusable(this.__getTargetFocusable());
749
775
  if (nextFocusable && isElementFocused(nextFocusable)) {
750
- const lastFocusable = this.__getLastFocusable(overlayPart);
776
+ const lastFocusable = this.__getLastFocusable(this);
751
777
  if (lastFocusable) {
752
778
  event.preventDefault();
753
779
  lastFocusable.focus();
@@ -800,10 +826,14 @@ class Popover extends PopoverPositionMixin(
800
826
 
801
827
  /** @private */
802
828
  __onTargetFocusOut(event) {
803
- // 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.
804
830
  // This covers the case when focus moves to the nested popover opened
805
831
  // without focusing parent popover overlay (e.g. using hover trigger).
806
- if (this._overlayElement.opened && !isLastOverlay(this._overlayElement)) {
832
+ if (
833
+ this._overlayElement.opened &&
834
+ !isLastOverlay(this._overlayElement) &&
835
+ hasOnlyNestedOverlays(this._overlayElement)
836
+ ) {
807
837
  return;
808
838
  }
809
839
 
@@ -829,13 +859,16 @@ class Popover extends PopoverPositionMixin(
829
859
 
830
860
  /** @private */
831
861
  __onTargetMouseLeave(event) {
832
- // Do not close the popover on target focusout if the overlay is not the last one.
833
- // This happens e.g. when opening the nested popover that uses non-modal overlay.
834
- if (this._overlayElement.opened && !isLastOverlay(this._overlayElement)) {
862
+ // Do not close if the pointer moves to the overlay
863
+ if (this.contains(event.relatedTarget)) {
835
864
  return;
836
865
  }
837
-
838
- 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
+ ) {
839
872
  return;
840
873
  }
841
874
 
@@ -843,7 +876,7 @@ class Popover extends PopoverPositionMixin(
843
876
  }
844
877
 
845
878
  /** @private */
846
- __onOverlayFocusIn() {
879
+ __onFocusIn() {
847
880
  this.__focusInside = true;
848
881
 
849
882
  // When using Tab to move focus, restoring focus is reset. However, if pressing Tab
@@ -854,19 +887,18 @@ class Popover extends PopoverPositionMixin(
854
887
  }
855
888
 
856
889
  /** @private */
857
- __onOverlayFocusOut(event) {
858
- // 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.
859
892
  // This covers the following cases of nested overlay based components:
860
893
  // 1. Moving focus to the nested overlay (e.g. vaadin-select, vaadin-menu-bar)
861
894
  // 2. Closing not focused nested overlay on outside (e.g. vaadin-combo-box)
862
- if (!isLastOverlay(this._overlayElement)) {
895
+ if (!isLastOverlay(this._overlayElement) && hasOnlyNestedOverlays(this._overlayElement)) {
863
896
  return;
864
897
  }
865
898
 
866
899
  if (
867
900
  (this.__hasTrigger('focus') && this.__mouseDownInside) ||
868
901
  event.relatedTarget === this.target ||
869
- event.relatedTarget === this._overlayElement ||
870
902
  this.contains(event.relatedTarget)
871
903
  ) {
872
904
  return;
@@ -902,14 +934,12 @@ class Popover extends PopoverPositionMixin(
902
934
 
903
935
  /** @private */
904
936
  __onOverlayMouseLeave(event) {
905
- // Do not close the popover on overlay focusout if it's not the last one.
906
- // This happens when opening the nested component that uses "modal" overlay
907
- // setting `pointer-events: none` on the body (combo-box, date-picker etc).
908
- if (!isLastOverlay(this._overlayElement)) {
937
+ // Do not close if the pointer moves to the target
938
+ if (event.relatedTarget === this.target) {
909
939
  return;
910
940
  }
911
-
912
- 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)) {
913
943
  return;
914
944
  }
915
945
 
@@ -955,7 +985,7 @@ class Popover extends PopoverPositionMixin(
955
985
  /** @private */
956
986
  __onOverlayOpened() {
957
987
  if (this.autofocus && !this.modal) {
958
- this._overlayElement.$.overlay.focus();
988
+ this.focus();
959
989
  }
960
990
  }
961
991
 
@@ -970,7 +1000,7 @@ class Popover extends PopoverPositionMixin(
970
1000
  }
971
1001
 
972
1002
  // Restore pointer-events set when opening on hover.
973
- if (this.modal && this.target.style.pointerEvents) {
1003
+ if (this.modal && this.target && this.target.style.pointerEvents) {
974
1004
  this.target.style.pointerEvents = '';
975
1005
  }
976
1006
 
@@ -1002,13 +1032,6 @@ class Popover extends PopoverPositionMixin(
1002
1032
  return Array.isArray(this.trigger) && this.trigger.includes(trigger);
1003
1033
  }
1004
1034
 
1005
- /** @private */
1006
- __sizeChanged(width, height, overlay) {
1007
- if (overlay) {
1008
- requestAnimationFrame(() => overlay.setBounds({ width, height }, false));
1009
- }
1010
- }
1011
-
1012
1035
  /**
1013
1036
  * Fired when the popover is closed.
1014
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';