Skip to content

Commit 9623e8f

Browse files
authored
Merge branch 'main' into fix/12294
2 parents 6f20660 + 35aeae7 commit 9623e8f

File tree

4 files changed

+93
-6
lines changed

4 files changed

+93
-6
lines changed

packages/runtime-core/__tests__/components/Teleport.spec.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
render,
1717
serialize,
1818
serializeInner,
19+
useModel,
1920
withDirectives,
2021
} from '@vue/runtime-test'
2122
import {
@@ -144,6 +145,62 @@ describe('renderer: teleport', () => {
144145
`"<!--teleport start--><!--teleport end--><div>Footer</div><div id="targetId"><div>bar</div></div>"`,
145146
)
146147
})
148+
149+
// #13349
150+
test('handle deferred teleport updates before and after mount', async () => {
151+
const root = document.createElement('div')
152+
document.body.appendChild(root)
153+
154+
const show = ref(false)
155+
const data2 = ref('2')
156+
const data3 = ref('3')
157+
158+
const Comp = {
159+
props: {
160+
modelValue: {},
161+
modelModifiers: {},
162+
},
163+
emits: ['update:modelValue'],
164+
setup(props: any) {
165+
const data2 = useModel(props, 'modelValue')
166+
data2.value = '2+'
167+
return () => h('span')
168+
},
169+
}
170+
171+
createDOMApp({
172+
setup() {
173+
setTimeout(() => (show.value = true), 5)
174+
setTimeout(() => (data3.value = '3+'), 10)
175+
},
176+
render() {
177+
return h(Fragment, null, [
178+
h('span', { id: 'targetId001' }),
179+
show.value
180+
? h(Fragment, null, [
181+
h(Teleport, { to: '#targetId001', defer: true }, [
182+
createTextVNode(String(data3.value)),
183+
]),
184+
h(Comp, {
185+
modelValue: data2.value,
186+
'onUpdate:modelValue': (event: any) =>
187+
(data2.value = event),
188+
}),
189+
])
190+
: createCommentVNode('v-if'),
191+
])
192+
},
193+
}).mount(root)
194+
195+
expect(root.innerHTML).toMatchInlineSnapshot(
196+
`"<span id="targetId001"></span><!--v-if-->"`,
197+
)
198+
199+
await new Promise(r => setTimeout(r, 10))
200+
expect(root.innerHTML).toMatchInlineSnapshot(
201+
`"<span id="targetId001">3+</span><!--teleport start--><!--teleport end--><span></span>"`,
202+
)
203+
})
147204
})
148205

149206
function runSharedTests(deferMode: boolean) {

packages/runtime-core/__tests__/hydration.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1654,6 +1654,29 @@ describe('SSR hydration', () => {
16541654
expect(`mismatch`).not.toHaveBeenWarned()
16551655
})
16561656

1657+
test('transition appear work with pre-existing class', () => {
1658+
const { vnode, container } = mountWithHydration(
1659+
`<template><div class="foo">foo</div></template>`,
1660+
() =>
1661+
h(
1662+
Transition,
1663+
{ appear: true },
1664+
{
1665+
default: () => h('div', { class: 'foo' }, 'foo'),
1666+
},
1667+
),
1668+
)
1669+
expect(container.firstChild).toMatchInlineSnapshot(`
1670+
<div
1671+
class="foo v-enter-from v-enter-active"
1672+
>
1673+
foo
1674+
</div>
1675+
`)
1676+
expect(vnode.el).toBe(container.firstChild)
1677+
expect(`mismatch`).not.toHaveBeenWarned()
1678+
})
1679+
16571680
test('transition appear with v-if', () => {
16581681
const show = false
16591682
const { vnode, container } = mountWithHydration(

packages/runtime-core/src/components/Teleport.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,15 +164,16 @@ export const TeleportImpl = {
164164
}
165165

166166
if (isTeleportDeferred(n2.props)) {
167+
n2.el!.__isMounted = false
167168
queuePostRenderEffect(() => {
168169
mountToTarget()
169-
n2.el!.__isMounted = true
170+
delete n2.el!.__isMounted
170171
}, parentSuspense)
171172
} else {
172173
mountToTarget()
173174
}
174175
} else {
175-
if (isTeleportDeferred(n2.props) && !n1.el!.__isMounted) {
176+
if (isTeleportDeferred(n2.props) && n1.el!.__isMounted === false) {
176177
queuePostRenderEffect(() => {
177178
TeleportImpl.process(
178179
n1,
@@ -186,7 +187,6 @@ export const TeleportImpl = {
186187
optimized,
187188
internals,
188189
)
189-
delete n1.el!.__isMounted
190190
}, parentSuspense)
191191
return
192192
}

packages/runtime-core/src/hydration.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -398,9 +398,11 @@ export function createHydrationFunctions(
398398
parentComponent.vnode.props.appear
399399

400400
const content = (el as HTMLTemplateElement).content
401-
.firstChild as Element
401+
.firstChild as Element & { $cls?: string }
402402

403403
if (needCallTransitionHooks) {
404+
const cls = content.getAttribute('class')
405+
if (cls) content.$cls = cls
404406
transition!.beforeEnter(content)
405407
}
406408

@@ -786,7 +788,7 @@ export function createHydrationFunctions(
786788
* Dev only
787789
*/
788790
function propHasMismatch(
789-
el: Element,
791+
el: Element & { $cls?: string },
790792
key: string,
791793
clientValue: any,
792794
vnode: VNode,
@@ -799,7 +801,12 @@ function propHasMismatch(
799801
if (key === 'class') {
800802
// classes might be in different order, but that doesn't affect cascade
801803
// so we just need to check if the class lists contain the same classes.
802-
actual = el.getAttribute('class')
804+
if (el.$cls) {
805+
actual = el.$cls
806+
delete el.$cls
807+
} else {
808+
actual = el.getAttribute('class')
809+
}
803810
expected = normalizeClass(clientValue)
804811
if (!isSetEqual(toClassSet(actual || ''), toClassSet(expected))) {
805812
mismatchType = MismatchTypes.CLASS

0 commit comments

Comments
 (0)