@@ -19,11 +19,13 @@ import {
1919 subscribeToRTLChanges ,
2020 unsubscribeToRTLChanges
2121} from "../../common/utils" ;
22+ import { getDocumentSizes } from "./get-document-sizes" ;
2223import {
2324 ChPopoverAlign ,
2425 ChPopoverResizeElement ,
2526 ChPopoverSizeMatch ,
26- PopoverActionElement
27+ PopoverActionElement ,
28+ PopoverClosedInfo
2729} from "./types" ;
2830import { fromPxToNumber , setResponsiveAlignment } from "./utils" ;
2931
@@ -506,8 +508,26 @@ export class ChPopover {
506508 *
507509 * This event can be prevented (`preventDefault()`), interrupting the
508510 * `ch-popover`'s closing.
511+ *
512+ * The `reason` property of the event provides more information about
513+ * the cause of the closing:
514+ * - `"click-outside"`: The popover is being closed because the user clicked
515+ * outside the popover when using `closeOnClickOutside === true` and
516+ * `mode === "manual"`.
517+ *
518+ * - `"escape-key"`: The popover is being closed because the user pressed the
519+ * "Escape" key when using `closeOnClickOutside === true` and
520+ * `mode === "manual"`.
521+ *
522+ * - `"popover-no-longer-visible"`: The popover is being closed because it
523+ * is no longer visible.
524+ *
525+ * - `"toggle"`: The popover is being closed by the native toggle behavior
526+ * of popover. It can be produced by the user clicking the `actionElement`,
527+ * pressing the "Enter" or "Space" keys on the `actionElement`, pressing
528+ * the "Escape" key or other. Used when `mode === "auto"`.
509529 */
510- @Event ( ) popoverClosed : EventEmitter ;
530+ @Event ( ) popoverClosed : EventEmitter < PopoverClosedInfo > ;
511531
512532 #showPopover = ( ) => {
513533 this . el . showPopover ( ) ;
@@ -520,11 +540,14 @@ export class ChPopover {
520540 } ;
521541
522542 // TODO: Add unit tests for this feature
523- #closePopoverIfNotDefaultPrevented = ( event : Event ) => {
524- const eventInfo = this . popoverClosed . emit ( ) ;
543+ #closePopoverIfNotDefaultPrevented = (
544+ reason : PopoverClosedInfo ,
545+ event ?: Event
546+ ) => {
547+ const eventInfo = this . popoverClosed . emit ( reason ) ;
525548
526549 if ( eventInfo . defaultPrevented ) {
527- event . preventDefault ( ) ;
550+ event ? .preventDefault ( ) ;
528551 return ;
529552 }
530553
@@ -541,13 +564,16 @@ export class ChPopover {
541564 // determine if the popover should be closed
542565 ! composedPath . includes ( this . actionElement )
543566 ) {
544- this . #closePopoverIfNotDefaultPrevented( event ) ;
567+ this . #closePopoverIfNotDefaultPrevented(
568+ { reason : "click-outside" } ,
569+ event
570+ ) ;
545571 }
546572 } ;
547573
548574 #handlePopoverCloseOnEscapeKey = ( event : KeyboardEvent ) => {
549575 if ( event . code === KEY_CODES . ESCAPE ) {
550- this . #closePopoverIfNotDefaultPrevented( event ) ;
576+ this . #closePopoverIfNotDefaultPrevented( { reason : "escape-key" } , event ) ;
551577 }
552578 } ;
553579
@@ -664,6 +690,13 @@ export class ChPopover {
664690 passive : true
665691 } )
666692 ) ;
693+
694+ // We must observe the actual viewport size, because observing the
695+ // document.body does not work when the browser's window is resized
696+ // and the body has scrollbars.
697+ // Also, passive mode is not supported in the "resize" event, because this
698+ // event can not be prevented
699+ window . addEventListener ( "resize" , this . #updatePositionRAF) ;
667700 } ;
668701
669702 #updatePositionRAF = ( ) => {
@@ -672,7 +705,7 @@ export class ChPopover {
672705
673706 #updatePosition = ( ) => {
674707 // - - - - - - - - - - - - - DOM read operations - - - - - - - - - - - - -
675- const documentRect = document . documentElement . getBoundingClientRect ( ) ;
708+ const documentRect = getDocumentSizes ( ) ;
676709 const actionRect = this . actionElement . getBoundingClientRect ( ) ;
677710 const popoverScrollSizes = {
678711 width : this . el . scrollWidth ,
@@ -701,15 +734,15 @@ export class ChPopover {
701734 } ;
702735
703736 #getActionInlineStartPosition = (
704- documentRect : DOMRect ,
737+ documentRect : { width : number ; height : number } ,
705738 actionRect : DOMRect
706739 ) =>
707740 this . #isRTLDirection
708741 ? documentRect . width - ( actionRect . left + actionRect . width )
709742 : actionRect . left ;
710743
711744 #setResponsiveAlignment = (
712- documentRect : DOMRect ,
745+ documentRect : { width : number ; height : number } ,
713746 actionRect : DOMRect ,
714747 actionInlineStart : number ,
715748 popoverScrollSizes : { width : number ; height : number } ,
@@ -743,13 +776,15 @@ export class ChPopover {
743776 // TODO: Add e2e tests for this
744777 try {
745778 if ( maxInlineSizeCustomVarValue . endsWith ( "px" ) ) {
746- actualPopoverWidth = Number (
747- maxInlineSizeCustomVarValue . replace ( "px" , "" ) . trim ( )
779+ actualPopoverWidth = Math . min (
780+ actualPopoverWidth ,
781+ Number ( maxInlineSizeCustomVarValue . replace ( "px" , "" ) . trim ( ) )
748782 ) ;
749783 }
750784 if ( maxBlockSizeCustomVarValue . endsWith ( "px" ) ) {
751- actualPopoverHeight = Number (
752- maxBlockSizeCustomVarValue . replace ( "px" , "" ) . trim ( )
785+ actualPopoverHeight = Math . min (
786+ actualPopoverHeight ,
787+ Number ( maxBlockSizeCustomVarValue . replace ( "px" , "" ) . trim ( ) )
753788 ) ;
754789 }
755790 } catch {
@@ -809,6 +844,17 @@ export class ChPopover {
809844 // Inline size
810845 if ( inlineOverflow < 0 ) {
811846 const newMaxInlineSize = popoverWidth + inlineOverflow ;
847+
848+ // TODO: Add e2e tests for this
849+ // TODO: We must implement a property to configure the behavior of these
850+ // kinds of situations
851+ // Close the popover if it won't be visible.
852+ if ( newMaxInlineSize <= PRECISION_TO_AVOID_FLOATING_POINT_ERRORS ) {
853+ return this . #closePopoverIfNotDefaultPrevented( {
854+ reason : "popover-no-longer-visible"
855+ } ) ;
856+ }
857+
812858 setProperty ( this . el , POPOVER_FORCED_MAX_INLINE_SIZE , newMaxInlineSize ) ;
813859 }
814860 // Check if the forced inline size is no longer needed
@@ -823,6 +869,17 @@ export class ChPopover {
823869 // Block size
824870 if ( blockOverflow < 0 ) {
825871 const newMaxBlockSize = popoverHeight + blockOverflow ;
872+
873+ // TODO: Add e2e tests for this
874+ // TODO: We must implement a property to configure the behavior of these
875+ // kinds of situations
876+ // Close the popover if it won't be visible.
877+ if ( newMaxBlockSize <= PRECISION_TO_AVOID_FLOATING_POINT_ERRORS ) {
878+ return this . #closePopoverIfNotDefaultPrevented( {
879+ reason : "popover-no-longer-visible"
880+ } ) ;
881+ }
882+
826883 setProperty ( this . el , POPOVER_FORCED_MAX_BLOCK_SIZE , newMaxBlockSize ) ;
827884 }
828885 // Check if the forced block size is no longer needed
@@ -890,6 +947,7 @@ export class ChPopover {
890947 capture : true
891948 } )
892949 ) ;
950+ window . removeEventListener ( "resize" , this . #updatePositionRAF) ;
893951
894952 // Delete references for root nodes to any avoid memory leak
895953 this . #rootNodes = undefined ;
@@ -903,7 +961,18 @@ export class ChPopover {
903961 if ( willBeOpen ) {
904962 eventInfo = this . popoverOpened . emit ( ) ;
905963 } else {
906- eventInfo = this . popoverClosed . emit ( ) ;
964+ // If the popover is already closed, don't emit the event again
965+ // This can happen when:
966+ // - The "Escape" key is pressed in mode === "manual"
967+ // - The user clicks outside the popover in mode === "manual"
968+ // - The show property is changed to false externally
969+ // - The user scrolls the window and the popover is no longer visible
970+ // TODO: Add e2e tests for this
971+ if ( ! this . show ) {
972+ return ;
973+ }
974+
975+ eventInfo = this . popoverClosed . emit ( { reason : "toggle" } ) ;
907976 }
908977
909978 // TODO: Add unit tests for this feature
@@ -1215,7 +1284,7 @@ export class ChPopover {
12151284 }
12161285
12171286 if ( this . #adjustAlignment) {
1218- const documentRect = document . documentElement . getBoundingClientRect ( ) ;
1287+ const documentRect = getDocumentSizes ( ) ;
12191288 const actionRect = this . actionElement . getBoundingClientRect ( ) ;
12201289 const popoverScrollSizes = {
12211290 width : this . el . scrollWidth ,
@@ -1285,6 +1354,7 @@ export class ChPopover {
12851354 ? this . #handleMouseDown
12861355 : null
12871356 }
1357+ // TODO: Add beforetoggle listener to properly support preventing the natural toggle
12881358 // TODO: Should we add this event with popover="manual"???
12891359 // TODO: Check if the actionElement is an instance of Button to add this handler
12901360 onToggle = { this . #handlePopoverToggle}
0 commit comments