mirror of https://github.com/vuejs/core.git
				
				
				
			fix(suspense): fix anchor for suspense with transition out-in (#9999)
close #9996
This commit is contained in:
		
							parent
							
								
									7220c58d99
								
							
						
					
					
						commit
						a3fbf2132b
					
				|  | @ -400,7 +400,6 @@ export interface SuspenseBoundary { | ||||||
|   namespace: ElementNamespace |   namespace: ElementNamespace | ||||||
|   container: RendererElement |   container: RendererElement | ||||||
|   hiddenContainer: RendererElement |   hiddenContainer: RendererElement | ||||||
|   anchor: RendererNode | null |  | ||||||
|   activeBranch: VNode | null |   activeBranch: VNode | null | ||||||
|   pendingBranch: VNode | null |   pendingBranch: VNode | null | ||||||
|   deps: number |   deps: number | ||||||
|  | @ -473,6 +472,7 @@ function createSuspenseBoundary( | ||||||
|     assertNumber(timeout, `Suspense timeout`) |     assertNumber(timeout, `Suspense timeout`) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   const initialAnchor = anchor | ||||||
|   const suspense: SuspenseBoundary = { |   const suspense: SuspenseBoundary = { | ||||||
|     vnode, |     vnode, | ||||||
|     parent: parentSuspense, |     parent: parentSuspense, | ||||||
|  | @ -480,7 +480,6 @@ function createSuspenseBoundary( | ||||||
|     namespace, |     namespace, | ||||||
|     container, |     container, | ||||||
|     hiddenContainer, |     hiddenContainer, | ||||||
|     anchor, |  | ||||||
|     deps: 0, |     deps: 0, | ||||||
|     pendingId: suspenseId++, |     pendingId: suspenseId++, | ||||||
|     timeout: typeof timeout === 'number' ? timeout : -1, |     timeout: typeof timeout === 'number' ? timeout : -1, | ||||||
|  | @ -529,20 +528,28 @@ function createSuspenseBoundary( | ||||||
|               move( |               move( | ||||||
|                 pendingBranch!, |                 pendingBranch!, | ||||||
|                 container, |                 container, | ||||||
|                 next(activeBranch!), |                 anchor === initialAnchor ? next(activeBranch!) : anchor, | ||||||
|                 MoveType.ENTER, |                 MoveType.ENTER, | ||||||
|               ) |               ) | ||||||
|               queuePostFlushCb(effects) |               queuePostFlushCb(effects) | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|         // this is initial anchor on mount
 |  | ||||||
|         let { anchor } = suspense |  | ||||||
|         // unmount current active tree
 |         // unmount current active tree
 | ||||||
|         if (activeBranch) { |         if (activeBranch) { | ||||||
|           // if the fallback tree was mounted, it may have been moved
 |           // if the fallback tree was mounted, it may have been moved
 | ||||||
|           // as part of a parent suspense. get the latest anchor for insertion
 |           // as part of a parent suspense. get the latest anchor for insertion
 | ||||||
|  |           // #8105 if `delayEnter` is true, it means that the mounting of
 | ||||||
|  |           // `activeBranch` will be delayed. if the branch switches before
 | ||||||
|  |           // transition completes, both `activeBranch` and `pendingBranch` may
 | ||||||
|  |           // coexist in the `hiddenContainer`. This could result in
 | ||||||
|  |           // `next(activeBranch!)` obtaining an incorrect anchor
 | ||||||
|  |           // (got `pendingBranch.el`).
 | ||||||
|  |           // Therefore, after the mounting of activeBranch is completed,
 | ||||||
|  |           // it is necessary to get the latest anchor.
 | ||||||
|  |           if (parentNode(activeBranch.el!) !== suspense.hiddenContainer) { | ||||||
|             anchor = next(activeBranch) |             anchor = next(activeBranch) | ||||||
|  |           } | ||||||
|           unmount(activeBranch, parentComponent, suspense, true) |           unmount(activeBranch, parentComponent, suspense, true) | ||||||
|         } |         } | ||||||
|         if (!delayEnter) { |         if (!delayEnter) { | ||||||
|  |  | ||||||
|  | @ -1652,6 +1652,77 @@ describe('e2e: Transition', () => { | ||||||
|       }, |       }, | ||||||
|       E2E_TIMEOUT, |       E2E_TIMEOUT, | ||||||
|     ) |     ) | ||||||
|  | 
 | ||||||
|  |     // #9996
 | ||||||
|  |     test( | ||||||
|  |       'trigger again when transition is not finished & correctly anchor', | ||||||
|  |       async () => { | ||||||
|  |         await page().evaluate(duration => { | ||||||
|  |           const { createApp, shallowRef, h } = (window as any).Vue | ||||||
|  |           const One = { | ||||||
|  |             async setup() { | ||||||
|  |               return () => h('div', { class: 'test' }, 'one') | ||||||
|  |             }, | ||||||
|  |           } | ||||||
|  |           const Two = { | ||||||
|  |             async setup() { | ||||||
|  |               return () => h('div', { class: 'test' }, 'two') | ||||||
|  |             }, | ||||||
|  |           } | ||||||
|  |           createApp({ | ||||||
|  |             template: ` | ||||||
|  |             <div id="container"> | ||||||
|  |               <div>Top</div> | ||||||
|  |               <transition name="test" mode="out-in" :duration="${duration}"> | ||||||
|  |                 <Suspense> | ||||||
|  |                   <component :is="view"/> | ||||||
|  |                 </Suspense> | ||||||
|  |               </transition> | ||||||
|  |               <div>Bottom</div> | ||||||
|  |             </div> | ||||||
|  |             <button id="toggleBtn" @click="click">button</button> | ||||||
|  |           `,
 | ||||||
|  |             setup: () => { | ||||||
|  |               const view = shallowRef(One) | ||||||
|  |               const click = () => { | ||||||
|  |                 view.value = view.value === One ? Two : One | ||||||
|  |               } | ||||||
|  |               return { view, click } | ||||||
|  |             }, | ||||||
|  |           }).mount('#app') | ||||||
|  |         }, duration) | ||||||
|  | 
 | ||||||
|  |         await nextFrame() | ||||||
|  |         expect(await html('#container')).toBe( | ||||||
|  |           '<div>Top</div><div class="test test-enter-active test-enter-to">one</div><div>Bottom</div>', | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         await transitionFinish() | ||||||
|  |         expect(await html('#container')).toBe( | ||||||
|  |           '<div>Top</div><div class="test">one</div><div>Bottom</div>', | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         // trigger twice
 | ||||||
|  |         classWhenTransitionStart() | ||||||
|  |         await nextFrame() | ||||||
|  |         expect(await html('#container')).toBe( | ||||||
|  |           '<div>Top</div><div class="test test-leave-active test-leave-to">one</div><div>Bottom</div>', | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         await transitionFinish() | ||||||
|  |         await nextFrame() | ||||||
|  |         expect(await html('#container')).toBe( | ||||||
|  |           '<div>Top</div><div class="test test-enter-active test-enter-to">two</div><div>Bottom</div>', | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         await transitionFinish() | ||||||
|  |         await nextFrame() | ||||||
|  |         expect(await html('#container')).toBe( | ||||||
|  |           '<div>Top</div><div class="test">two</div><div>Bottom</div>', | ||||||
|  |         ) | ||||||
|  |       }, | ||||||
|  |       E2E_TIMEOUT, | ||||||
|  |     ) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe('transition with v-show', () => { |   describe('transition with v-show', () => { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue