feat(transition): handle cancel hooks
This commit is contained in:
parent
231b940103
commit
5c691aebfd
@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
ComponentInternalInstance,
|
|
||||||
SetupContext,
|
SetupContext,
|
||||||
ComponentOptions
|
ComponentOptions
|
||||||
} from '../component'
|
} from '../component'
|
||||||
@ -30,17 +29,38 @@ export interface TransitionProps {
|
|||||||
onLeaveCancelled?: (el: any) => void
|
onLeaveCancelled?: (el: any) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TransitionHookCaller = (
|
||||||
|
hook: ((el: any) => void) | undefined,
|
||||||
|
args?: any[]
|
||||||
|
) => void
|
||||||
|
|
||||||
|
interface PendingCallbacks {
|
||||||
|
enter?: (cancelled?: boolean) => void
|
||||||
|
leave?: (cancelled?: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
const TransitionImpl = {
|
const TransitionImpl = {
|
||||||
name: `BaseTransition`,
|
name: `BaseTransition`,
|
||||||
setup(props: TransitionProps, { slots }: SetupContext) {
|
setup(props: TransitionProps, { slots }: SetupContext) {
|
||||||
const instance = getCurrentInstance()!
|
const instance = getCurrentInstance()!
|
||||||
let isLeaving = false
|
let isLeaving = false
|
||||||
let isMounted = false
|
let isMounted = false
|
||||||
|
const pendingCallbacks: PendingCallbacks = {}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
isMounted = true
|
isMounted = true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const callTransitionHook: TransitionHookCaller = (hook, args) => {
|
||||||
|
hook &&
|
||||||
|
callWithAsyncErrorHandling(
|
||||||
|
hook,
|
||||||
|
instance,
|
||||||
|
ErrorCodes.TRANSITION_HOOK,
|
||||||
|
args
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const children = slots.default && slots.default()
|
const children = slots.default && slots.default()
|
||||||
if (!children || !children.length) {
|
if (!children || !children.length) {
|
||||||
@ -72,10 +92,11 @@ const TransitionImpl = {
|
|||||||
|
|
||||||
let delayedLeave: (() => void) | undefined
|
let delayedLeave: (() => void) | undefined
|
||||||
const performDelayedLeave = () => delayedLeave && delayedLeave()
|
const performDelayedLeave = () => delayedLeave && delayedLeave()
|
||||||
const transitionData = (child.transition = resolveTransitionData(
|
const transitionHooks = (child.transition = resolveTransitionHooks(
|
||||||
instance,
|
|
||||||
rawProps,
|
rawProps,
|
||||||
|
callTransitionHook,
|
||||||
isMounted,
|
isMounted,
|
||||||
|
pendingCallbacks,
|
||||||
performDelayedLeave
|
performDelayedLeave
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -92,18 +113,18 @@ const TransitionImpl = {
|
|||||||
) {
|
) {
|
||||||
// update old tree's hooks in case of dynamic transition
|
// update old tree's hooks in case of dynamic transition
|
||||||
// need to do this recursively in case of HOCs
|
// need to do this recursively in case of HOCs
|
||||||
updateHOCTransitionData(oldChild, transitionData)
|
updateHOCTransitionData(oldChild, transitionHooks)
|
||||||
// switching between different views
|
// switching between different views
|
||||||
if (mode === 'out-in') {
|
if (mode === 'out-in') {
|
||||||
isLeaving = true
|
isLeaving = true
|
||||||
// return placeholder node and queue update when leave finishes
|
// return placeholder node and queue update when leave finishes
|
||||||
transitionData.afterLeave = () => {
|
transitionHooks.afterLeave = () => {
|
||||||
isLeaving = false
|
isLeaving = false
|
||||||
instance.update()
|
instance.update()
|
||||||
}
|
}
|
||||||
return placeholder(child)
|
return placeholder(child)
|
||||||
} else if (mode === 'in-out') {
|
} else if (mode === 'in-out') {
|
||||||
transitionData.delayLeave = performLeave => {
|
transitionHooks.delayLeave = performLeave => {
|
||||||
delayedLeave = performLeave
|
delayedLeave = performLeave
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,7 +160,7 @@ export const Transition = (TransitionImpl as any) as {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransitionData {
|
export interface TransitionHooks {
|
||||||
beforeEnter(el: object): void
|
beforeEnter(el: object): void
|
||||||
enter(el: object): void
|
enter(el: object): void
|
||||||
leave(el: object, remove: () => void): void
|
leave(el: object, remove: () => void): void
|
||||||
@ -147,8 +168,7 @@ export interface TransitionData {
|
|||||||
delayLeave?(performLeave: () => void): void
|
delayLeave?(performLeave: () => void): void
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveTransitionData(
|
function resolveTransitionHooks(
|
||||||
instance: ComponentInternalInstance,
|
|
||||||
{
|
{
|
||||||
appear,
|
appear,
|
||||||
onBeforeEnter,
|
onBeforeEnter,
|
||||||
@ -160,66 +180,59 @@ function resolveTransitionData(
|
|||||||
onAfterLeave,
|
onAfterLeave,
|
||||||
onLeaveCancelled
|
onLeaveCancelled
|
||||||
}: TransitionProps,
|
}: TransitionProps,
|
||||||
|
callHook: TransitionHookCaller,
|
||||||
isMounted: boolean,
|
isMounted: boolean,
|
||||||
|
pendingCallbacks: PendingCallbacks,
|
||||||
performDelayedLeave: () => void
|
performDelayedLeave: () => void
|
||||||
): TransitionData {
|
): TransitionHooks {
|
||||||
// TODO handle cancel hooks
|
|
||||||
return {
|
return {
|
||||||
beforeEnter(el) {
|
beforeEnter(el) {
|
||||||
if (!isMounted && !appear) {
|
if (!isMounted && !appear) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
onBeforeEnter &&
|
if (pendingCallbacks.leave) {
|
||||||
callWithAsyncErrorHandling(
|
pendingCallbacks.leave(true /* cancelled */)
|
||||||
onBeforeEnter,
|
}
|
||||||
instance,
|
callHook(onBeforeEnter, [el])
|
||||||
ErrorCodes.TRANSITION_HOOK,
|
|
||||||
[el]
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
enter(el) {
|
enter(el) {
|
||||||
if (!isMounted && !appear) {
|
if (!isMounted && !appear) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const done = () => {
|
const afterEnter = (pendingCallbacks.enter = (cancelled?) => {
|
||||||
onAfterEnter &&
|
if (cancelled) {
|
||||||
callWithAsyncErrorHandling(
|
callHook(onEnterCancelled, [el])
|
||||||
onAfterEnter,
|
} else {
|
||||||
instance,
|
callHook(onAfterEnter, [el])
|
||||||
ErrorCodes.TRANSITION_HOOK,
|
performDelayedLeave()
|
||||||
[el]
|
}
|
||||||
)
|
pendingCallbacks.enter = undefined
|
||||||
performDelayedLeave()
|
})
|
||||||
}
|
|
||||||
if (onEnter) {
|
if (onEnter) {
|
||||||
onEnter(el, done)
|
onEnter(el, afterEnter)
|
||||||
} else {
|
} else {
|
||||||
done()
|
afterEnter()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
leave(el, remove) {
|
leave(el, remove) {
|
||||||
onBeforeLeave &&
|
if (pendingCallbacks.enter) {
|
||||||
callWithAsyncErrorHandling(
|
pendingCallbacks.enter(true /* cancelled */)
|
||||||
onBeforeLeave,
|
}
|
||||||
instance,
|
callHook(onBeforeLeave, [el])
|
||||||
ErrorCodes.TRANSITION_HOOK,
|
const afterLeave = (pendingCallbacks.leave = (cancelled?) => {
|
||||||
[el]
|
|
||||||
)
|
|
||||||
const afterLeave = () =>
|
|
||||||
onAfterLeave &&
|
|
||||||
callWithAsyncErrorHandling(
|
|
||||||
onAfterLeave,
|
|
||||||
instance,
|
|
||||||
ErrorCodes.TRANSITION_HOOK,
|
|
||||||
[el]
|
|
||||||
)
|
|
||||||
if (onLeave) {
|
|
||||||
onLeave(el, () => {
|
|
||||||
remove()
|
|
||||||
afterLeave()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
remove()
|
remove()
|
||||||
|
if (cancelled) {
|
||||||
|
callHook(onLeaveCancelled, [el])
|
||||||
|
} else {
|
||||||
|
callHook(onAfterLeave, [el])
|
||||||
|
}
|
||||||
|
pendingCallbacks.leave = undefined
|
||||||
|
})
|
||||||
|
if (onLeave) {
|
||||||
|
onLeave(el, afterLeave)
|
||||||
|
} else {
|
||||||
afterLeave()
|
afterLeave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -238,7 +251,7 @@ function placeholder(vnode: VNode): VNode | undefined {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateHOCTransitionData(vnode: VNode, data: TransitionData) {
|
function updateHOCTransitionData(vnode: VNode, data: TransitionHooks) {
|
||||||
if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
|
if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
|
||||||
updateHOCTransitionData(vnode.component!.subTree, data)
|
updateHOCTransitionData(vnode.component!.subTree, data)
|
||||||
} else {
|
} else {
|
||||||
|
@ -19,7 +19,7 @@ import { AppContext } from './apiApp'
|
|||||||
import { SuspenseBoundary } from './components/Suspense'
|
import { SuspenseBoundary } from './components/Suspense'
|
||||||
import { DirectiveBinding } from './directives'
|
import { DirectiveBinding } from './directives'
|
||||||
import { SuspenseImpl } from './components/Suspense'
|
import { SuspenseImpl } from './components/Suspense'
|
||||||
import { TransitionData } from './components/Transition'
|
import { TransitionHooks } from './components/Transition'
|
||||||
|
|
||||||
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
|
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
|
||||||
__isFragment: true
|
__isFragment: true
|
||||||
@ -93,7 +93,7 @@ export interface VNode<HostNode = any, HostElement = any> {
|
|||||||
component: ComponentInternalInstance | null
|
component: ComponentInternalInstance | null
|
||||||
suspense: SuspenseBoundary<HostNode, HostElement> | null
|
suspense: SuspenseBoundary<HostNode, HostElement> | null
|
||||||
dirs: DirectiveBinding[] | null
|
dirs: DirectiveBinding[] | null
|
||||||
transition: TransitionData | null
|
transition: TransitionHooks | null
|
||||||
|
|
||||||
// DOM
|
// DOM
|
||||||
el: HostNode | null
|
el: HostNode | null
|
||||||
|
Loading…
Reference in New Issue
Block a user