@@ -27,6 +27,8 @@ const EVENT_CANCEL = `cancel${EVENT_KEY}`
2727const EVENT_CLICK_DATA_API = `click${ EVENT_KEY } ${ DATA_API_KEY } `
2828
2929const CLASS_NAME_NONMODAL = 'dialog-nonmodal'
30+ const CLASS_NAME_INSTANT = 'dialog-instant'
31+ const CLASS_NAME_SWAP_IN = 'dialog-swap-in'
3032
3133const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dialog"]'
3234
@@ -130,11 +132,37 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (
130132 const shouldSwap = currentDialog && currentDialog !== target
131133
132134 if ( shouldSwap ) {
135+ // Swap strategy (seamless backdrop, no flash):
136+ // 1. Mark the incoming dialog with .dialog-swap-in so its ::backdrop
137+ // skips the @starting -style fade-in and appears fully opaque on
138+ // its very first frame in the top layer.
139+ // 2. Open the incoming dialog (showModal).
140+ // 3. Close the outgoing dialog synchronously — no exit transition, no
141+ // .hiding — so its ::backdrop is removed in the same frame the
142+ // incoming dialog's backdrop appears. Since both backdrops render
143+ // the same color, the user sees one continuous backdrop. Two
144+ // simultaneously-visible backdrops would composite to ~75% darker,
145+ // and a fading-out + fading-in pair would dip to ~75% opacity —
146+ // either would look like a flash.
147+ // 4. Clean up the .dialog-swap-in flag once the incoming dialog
148+ // finishes its entry transition.
133149 const newDialog = Dialog . getOrCreateInstance ( target , config )
150+ target . classList . add ( CLASS_NAME_SWAP_IN )
134151 newDialog . show ( this )
152+ EventHandler . one ( target , `shown${ EVENT_KEY } ` , ( ) => {
153+ target . classList . remove ( CLASS_NAME_SWAP_IN )
154+ } )
135155
136156 const currentInstance = Dialog . getInstance ( currentDialog )
137157 if ( currentInstance ) {
158+ // Force synchronous close: .dialog-instant makes _isAnimated() false,
159+ // which makes _shouldDeferClose() false, so hide() calls close()
160+ // immediately (no deferred .hiding path). The class is removed after
161+ // the (now-synchronous) hidden event fires.
162+ currentDialog . classList . add ( CLASS_NAME_INSTANT )
163+ EventHandler . one ( currentDialog , EVENT_HIDDEN , ( ) => {
164+ currentDialog . classList . remove ( CLASS_NAME_INSTANT )
165+ } )
138166 currentInstance . hide ( )
139167 }
140168
0 commit comments