@@ -174,6 +174,7 @@ class TouchSelectionMenuController {
174174 #menuAnchor = null ;
175175 #menuAnimation = null ;
176176 #viewAnimations = [ ] ;
177+ #overflowExpanded = false ;
177178
178179 constructor ( view , options = { } ) {
179180 this . #view = view ;
@@ -680,7 +681,8 @@ class TouchSelectionMenuController {
680681 const menuKey = `${ hasSelection } :${ items
681682 . map ( ( item ) => item . id || this . #getItemLabel( item ) )
682683 . join ( "|" ) } `;
683- if ( menuKey !== this . #renderedMenuKey) {
684+ const menuChanged = menuKey !== this . #renderedMenuKey;
685+ if ( menuChanged ) {
684686 const groups = partitionSelectionMenuItems ( items , { hasSelection } ) ;
685687 this . #renderMenu( groups . primary , groups . overflow ) ;
686688 this . #renderedMenuKey = menuKey ;
@@ -689,6 +691,8 @@ class TouchSelectionMenuController {
689691 if ( ! this . $menu . isConnected ) {
690692 this . #container. append ( this . $menu ) ;
691693 }
694+ this . $menu . style . removeProperty ( "width" ) ;
695+ this . $menu . style . removeProperty ( "height" ) ;
692696 const isOpening = ! this . #menuActive;
693697 this . #positionMenu( anchor ) ;
694698 this . #menuActive = true ;
@@ -697,6 +701,11 @@ class TouchSelectionMenuController {
697701 }
698702
699703 #renderMenu( primaryItems , overflowItems ) {
704+ for ( const animation of this . #viewAnimations) animation . cancel ?. ( ) ;
705+ this . #viewAnimations = [ ] ;
706+ this . #overflowExpanded = false ;
707+ this . $menu . style . removeProperty ( "width" ) ;
708+ this . $menu . style . removeProperty ( "height" ) ;
700709 this . $menu . replaceChildren ( ) ;
701710 this . $menu . setAttribute ( "aria-label" , "Text selection actions" ) ;
702711
@@ -822,68 +831,121 @@ class TouchSelectionMenuController {
822831 }
823832
824833 #setOverflowExpanded( $primary , $overflow , expanded ) {
834+ if ( expanded === this . #overflowExpanded) return ;
835+ const initialRect = this . $menu . getBoundingClientRect ( ) ;
836+ this . #menuAnimation?. cancel ?. ( ) ;
837+ this . #menuAnimation = null ;
825838 for ( const animation of this . #viewAnimations) animation . cancel ?. ( ) ;
826839 this . #viewAnimations = [ ] ;
827- const initialRect = this . $menu . getBoundingClientRect ( ) ;
828- const outgoing = expanded ? $primary : $overflow ;
840+ $primary . hidden = this . #overflowExpanded;
841+ $overflow . hidden = ! this . #overflowExpanded;
842+ const outgoing = this . #overflowExpanded ? $overflow : $primary ;
829843 const incoming = expanded ? $overflow : $primary ;
830844 const direction = expanded ? 1 : - 1 ;
831- this . $menu . style . width = `${ this . $menu . getBoundingClientRect ( ) . width } px` ;
832- incoming . hidden = false ;
833- incoming . style . visibility = "" ;
834- incoming . style . pointerEvents = "auto" ;
845+ for ( const $view of [ $primary , $overflow ] ) {
846+ $view . style . opacity = "" ;
847+ $view . style . transform = "" ;
848+ $view . style . visibility = "" ;
849+ $view . style . position = "" ;
850+ $view . style . inset = "" ;
851+ $view . style . pointerEvents = "" ;
852+ $view . style . transformOrigin = "" ;
853+ }
854+ this . #overflowExpanded = expanded ;
855+ outgoing . style . position = "absolute" ;
856+ outgoing . style . inset = "0 auto auto 0" ;
835857 outgoing . style . pointerEvents = "none" ;
836-
837- const usesGrid = $overflow . classList . contains (
838- "cursor-menu__overflow--grid" ,
839- ) ;
840- const targetHeight =
841- expanded && usesGrid ? $overflow . getBoundingClientRect ( ) . height : 40 ;
842- this . $menu . style . height = `${ targetHeight } px` ;
843- if ( usesGrid && this . #menuAnchor) this . #positionMenu( this . #menuAnchor) ;
858+ incoming . hidden = false ;
859+ this . $menu . style . removeProperty ( "width" ) ;
860+ this . $menu . style . removeProperty ( "height" ) ;
861+ this . $menu . style . transform = "" ;
862+ if ( this . #menuAnchor) this . #positionMenu( this . #menuAnchor) ;
844863 const finalRect = this . $menu . getBoundingClientRect ( ) ;
864+ const menuScaleX = finalRect . width
865+ ? initialRect . width / finalRect . width
866+ : 1 ;
867+ const menuScaleY = finalRect . height
868+ ? initialRect . height / finalRect . height
869+ : 1 ;
870+ const viewScaleX = menuScaleX ? 1 / menuScaleX : 1 ;
871+ const viewScaleY = menuScaleY ? 1 / menuScaleY : 1 ;
872+ this . $menu . style . transformOrigin = "left top" ;
873+ for ( const $view of [ outgoing , incoming ] ) {
874+ $view . style . transformOrigin = "left top" ;
875+ }
845876
877+ const clearMenuGeometry = ( ) => {
878+ this . $menu . style . removeProperty ( "width" ) ;
879+ this . $menu . style . removeProperty ( "height" ) ;
880+ this . $menu . style . transform = "" ;
881+ this . $menu . style . transformOrigin = "" ;
882+ } ;
846883 const finish = ( ) => {
847884 outgoing . hidden = true ;
848- outgoing . style . opacity = "" ;
849- outgoing . style . transform = "" ;
850- outgoing . style . visibility = "" ;
851- incoming . style . opacity = "" ;
852- incoming . style . transform = "" ;
853- this . $menu . style . height = `${ targetHeight } px` ;
854- this . $menu . style . transform = "" ;
885+ for ( const $view of [ $primary , $overflow ] ) {
886+ $view . style . opacity = "" ;
887+ $view . style . transform = "" ;
888+ $view . style . position = "" ;
889+ $view . style . inset = "" ;
890+ $view . style . pointerEvents = "" ;
891+ $view . style . transformOrigin = "" ;
892+ }
893+ clearMenuGeometry ( ) ;
855894 this . #viewAnimations = [ ] ;
856895 } ;
896+
857897 if ( animationsDisabled ( ) ) {
858898 finish ( ) ;
859899 return ;
860900 }
861-
862901 const outgoingAnimation = animate (
863902 outgoing ,
864- { opacity : [ 1 , 0 ] , x : [ 0 , - 6 * direction ] } ,
865- { duration : 0.1 , ease : "easeIn" } ,
903+ {
904+ opacity : [ 1 , 0 ] ,
905+ x : [ 0 , - 6 * direction ] ,
906+ scaleX : [ viewScaleX , 1 ] ,
907+ scaleY : [ viewScaleY , 1 ] ,
908+ } ,
909+ { duration : 0.12 , ease : "easeIn" } ,
866910 ) ;
867911 const incomingAnimation = animate (
868912 incoming ,
869- { opacity : [ 0 , 1 ] , x : [ 6 * direction , 0 ] } ,
870- { duration : 0.14 , ease : "easeOut" } ,
913+ {
914+ opacity : [ 0 , 1 ] ,
915+ x : [ 6 * direction , 0 ] ,
916+ scaleX : [ viewScaleX , 1 ] ,
917+ scaleY : [ viewScaleY , 1 ] ,
918+ } ,
919+ { duration : 0.18 , ease : "easeOut" } ,
871920 ) ;
872921 const animations = [ outgoingAnimation , incomingAnimation ] ;
873- if ( usesGrid && initialRect . height !== finalRect . height ) {
922+ if (
923+ initialRect . width !== finalRect . width ||
924+ initialRect . height !== finalRect . height ||
925+ initialRect . left !== finalRect . left ||
926+ initialRect . top !== finalRect . top
927+ ) {
874928 animations . push (
875929 animate (
876930 this . $menu ,
877931 {
878- height : [ initialRect . height , finalRect . height ] ,
932+ x : [ initialRect . left - finalRect . left , 0 ] ,
879933 y : [ initialRect . top - finalRect . top , 0 ] ,
934+ scaleX : [ menuScaleX , 1 ] ,
935+ scaleY : [ menuScaleY , 1 ] ,
936+ } ,
937+ {
938+ duration : 0.18 ,
939+ ease : [ 0.2 , 0 , 0 , 1 ] ,
940+ onComplete : clearMenuGeometry ,
880941 } ,
881- { duration : 0.16 , ease : "easeOut" } ,
882942 ) ,
883943 ) ;
884944 }
885945 this . #viewAnimations = animations ;
886- Promise . allSettled ( animations ) . then ( ( ) => {
946+ Promise . allSettled (
947+ animations . map ( ( animation ) => animation . finished ?? animation ) ,
948+ ) . then ( ( ) => {
887949 if ( this . #viewAnimations !== animations ) return ;
888950 finish ( ) ;
889951 } ) ;
@@ -1044,22 +1106,30 @@ class TouchSelectionMenuController {
10441106 overflow . style . opacity = "" ;
10451107 overflow . style . transform = "" ;
10461108 overflow . style . pointerEvents = "" ;
1109+ overflow . style . position = "" ;
1110+ overflow . style . inset = "" ;
1111+ overflow . style . transformOrigin = "" ;
10471112 }
10481113 const primary = this . $menu . querySelector ( ".cursor-menu__primary" ) ;
10491114 if ( primary ) {
10501115 primary . hidden = false ;
10511116 primary . style . opacity = "" ;
10521117 primary . style . transform = "" ;
10531118 primary . style . pointerEvents = "" ;
1119+ primary . style . position = "" ;
1120+ primary . style . inset = "" ;
1121+ primary . style . transformOrigin = "" ;
10541122 }
10551123 this . $menu . style . opacity = "" ;
10561124 this . $menu . style . transform = "" ;
1057- this . $menu . style . width = "" ;
1058- this . $menu . style . height = "40px" ;
1125+ this . $menu . style . transformOrigin = "" ;
1126+ this . $menu . style . removeProperty ( "width" ) ;
1127+ this . $menu . style . removeProperty ( "height" ) ;
10591128 this . $menu
10601129 . querySelector ( ".cursor-menu__more" )
10611130 ?. setAttribute ( "aria-expanded" , "false" ) ;
10621131 this . #renderedMenuKey = "" ;
1132+ this . #overflowExpanded = false ;
10631133 this . #menuAnchor = null ;
10641134 this . #menuActive = false ;
10651135 }
0 commit comments