propsOptions,
)
}
- root = cloneVNode(root, fallthroughAttrs)
+ root = cloneVNode(root, fallthroughAttrs, false, true)
} else if (__DEV__ && !accessedAttrs && root.type !== Comment) {
const allAttrs = Object.keys(attrs)
const eventAttrs: string[] = []
getComponentName(instance.type),
)
}
- root = cloneVNode(root, {
- class: cls,
- style: style,
- })
+ root = cloneVNode(
+ root,
+ {
+ class: cls,
+ style: style,
+ },
+ false,
+ true,
+ )
}
}
)
}
// clone before mutating since the root may be a hoisted vnode
- root = cloneVNode(root)
+ root = cloneVNode(root, null, false, true)
root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs
}
// inherit transition data
vnode: VNode<T, U>,
extraProps?: (Data & VNodeProps) | null,
mergeRef = false,
+ cloneTransition = false,
): VNode<T, U> {
// This is intentionally NOT using spread or extend to avoid the runtime
// key enumeration cost.
- const { props, ref, patchFlag, children } = vnode
+ const { props, ref, patchFlag, children, transition } = vnode
const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
const cloned: VNode<T, U> = {
__v_isVNode: true,
dynamicChildren: vnode.dynamicChildren,
appContext: vnode.appContext,
dirs: vnode.dirs,
- transition: vnode.transition,
+ transition,
// These should technically only be non-null on mounted VNodes. However,
// they *should* be copied for kept-alive vnodes. So we just always copy
ctx: vnode.ctx,
ce: vnode.ce,
}
+
+ // if the vnode will be replaced by the cloned one, it is necessary
+ // to clone the transition to ensure that the vnode referenced within
+ // the transition hooks is fresh.
+ if (transition && cloneTransition) {
+ cloned.transition = transition.clone(cloned as VNode)
+ }
+
if (__COMPAT__) {
defineLegacyVNodeProperties(cloned as VNode)
}
+
return cloned
}
E2E_TIMEOUT,
)
+ // #3716
+ test(
+ 'wrapping transition + fallthrough attrs',
+ async () => {
+ await page().goto(baseUrl)
+ await page().waitForSelector('#app')
+ await page().evaluate(() => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ components: {
+ 'my-transition': {
+ template: `
+ <transition foo="1" name="test">
+ <slot></slot>
+ </transition>
+ `,
+ },
+ },
+ template: `
+ <div id="container">
+ <my-transition>
+ <div v-if="toggle">content</div>
+ </my-transition>
+ </div>
+ <button id="toggleBtn" @click="click">button</button>
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click }
+ },
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe('<div foo="1">content</div>')
+
+ await click('#toggleBtn')
+ // toggle again before leave finishes
+ await nextTick()
+ await click('#toggleBtn')
+
+ await transitionFinish()
+ expect(await html('#container')).toBe(
+ '<div foo="1" class="">content</div>',
+ )
+ },
+ E2E_TIMEOUT,
+ )
+
test(
'w/ KeepAlive + unmount innerChild',
async () => {