@@ -95,21 +95,35 @@ let JS = {
9595
9696 exec_focus ( e , eventType , phxEvent , view , sourceEl , el ) {
9797 ARIA . attemptFocus ( el )
98+ // in case the JS.focus command is in a JS.show/hide/toggle chain, for show we need
99+ // to wait for JS.show to have updated the element's display property (see exec_toggle)
100+ // but that run in nested animation frames, therefore we need to use them here as well
101+ window . requestAnimationFrame ( ( ) => {
102+ window . requestAnimationFrame ( ( ) => ARIA . attemptFocus ( el ) )
103+ } )
98104 } ,
99105
100106 exec_focus_first ( e , eventType , phxEvent , view , sourceEl , el ) {
101107 ARIA . focusFirstInteractive ( el ) || ARIA . focusFirst ( el )
108+ // if you wonder about the nested animation frames, see exec_focus
109+ window . requestAnimationFrame ( ( ) => {
110+ window . requestAnimationFrame ( ( ) => ARIA . focusFirstInteractive ( el ) || ARIA . focusFirst ( el ) )
111+ } )
102112 } ,
103113
104114 exec_push_focus ( e , eventType , phxEvent , view , sourceEl , el ) {
105- window . requestAnimationFrame ( ( ) => focusStack . push ( el || sourceEl ) )
115+ focusStack . push ( el || sourceEl )
106116 } ,
107117
108118 exec_pop_focus ( _e , _eventType , _phxEvent , _view , _sourceEl , _el ) {
109- window . requestAnimationFrame ( ( ) => {
110- const el = focusStack . pop ( )
111- if ( el ) { el . focus ( ) }
112- } )
119+ const el = focusStack . pop ( )
120+ if ( el ) {
121+ el . focus ( )
122+ // if you wonder about the nested animation frames, see exec_focus
123+ window . requestAnimationFrame ( ( ) => {
124+ window . requestAnimationFrame ( ( ) => el . focus ( ) )
125+ } )
126+ }
113127 } ,
114128
115129 exec_add_class ( e , eventType , phxEvent , view , sourceEl , el , { names, transition, time, blocking} ) {
@@ -195,11 +209,19 @@ let JS = {
195209 if ( eventType === "remove" ) { return }
196210 let onStart = ( ) => {
197211 this . addOrRemoveClasses ( el , inStartClasses , outClasses . concat ( outStartClasses ) . concat ( outEndClasses ) )
198- let stickyDisplay = display || this . defaultDisplay ( el )
199- DOM . putSticky ( el , "toggle" , currentEl => currentEl . style . display = stickyDisplay )
212+ const stickyDisplay = display || this . defaultDisplay ( el )
200213 window . requestAnimationFrame ( ( ) => {
214+ // first add the starting + active class, THEN make the element visible
215+ // otherwise if we toggled the visibility earlier css animations
216+ // would flicker, as the element becomes visible before the active animation
217+ // class is set (see https://github.com/phoenixframework/phoenix_live_view/issues/3456)
201218 this . addOrRemoveClasses ( el , inClasses , [ ] )
202- window . requestAnimationFrame ( ( ) => this . addOrRemoveClasses ( el , inEndClasses , inStartClasses ) )
219+ // addOrRemoveClasses uses a requestAnimationFrame itself, therefore we need to move the putSticky
220+ // into the next requestAnimationFrame...
221+ window . requestAnimationFrame ( ( ) => {
222+ DOM . putSticky ( el , "toggle" , currentEl => currentEl . style . display = stickyDisplay )
223+ this . addOrRemoveClasses ( el , inEndClasses , inStartClasses )
224+ } )
203225 } )
204226 }
205227 let onEnd = ( ) => {
@@ -216,14 +238,18 @@ let JS = {
216238 }
217239 } else {
218240 if ( this . isVisible ( el ) ) {
219- el . dispatchEvent ( new Event ( "phx:hide-start" ) )
220- DOM . putSticky ( el , "toggle" , currentEl => currentEl . style . display = "none" )
221- el . dispatchEvent ( new Event ( "phx:hide-end" ) )
241+ window . requestAnimationFrame ( ( ) => {
242+ el . dispatchEvent ( new Event ( "phx:hide-start" ) )
243+ DOM . putSticky ( el , "toggle" , currentEl => currentEl . style . display = "none" )
244+ el . dispatchEvent ( new Event ( "phx:hide-end" ) )
245+ } )
222246 } else {
223- el . dispatchEvent ( new Event ( "phx:show-start" ) )
224- let stickyDisplay = display || this . defaultDisplay ( el )
225- DOM . putSticky ( el , "toggle" , currentEl => currentEl . style . display = stickyDisplay )
226- el . dispatchEvent ( new Event ( "phx:show-end" ) )
247+ window . requestAnimationFrame ( ( ) => {
248+ el . dispatchEvent ( new Event ( "phx:show-start" ) )
249+ let stickyDisplay = display || this . defaultDisplay ( el )
250+ DOM . putSticky ( el , "toggle" , currentEl => currentEl . style . display = stickyDisplay )
251+ el . dispatchEvent ( new Event ( "phx:show-end" ) )
252+ } )
227253 }
228254 }
229255 } ,
0 commit comments