mirror of https://github.com/vuejs/core.git
feat: hydrate VaporTransition with appear (#13863)
This commit is contained in:
parent
8978ef759f
commit
1ee1777232
|
@ -805,16 +805,16 @@ export function createHydrationFunctions(
|
|||
}
|
||||
}
|
||||
|
||||
const isTemplateNode = (node: Node): node is HTMLTemplateElement => {
|
||||
return (
|
||||
node.nodeType === DOMNodeTypes.ELEMENT &&
|
||||
(node as Element).tagName === 'TEMPLATE'
|
||||
)
|
||||
}
|
||||
|
||||
return [hydrate, hydrateNode]
|
||||
}
|
||||
|
||||
export const isTemplateNode = (node: Node): node is HTMLTemplateElement => {
|
||||
return (
|
||||
node.nodeType === DOMNodeTypes.ELEMENT &&
|
||||
(node as Element).tagName === 'TEMPLATE'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dev only
|
||||
*/
|
||||
|
|
|
@ -601,3 +601,7 @@ export { createInternalObject } from './internalObject'
|
|||
* @internal
|
||||
*/
|
||||
export { createCanSetSetupRefChecker } from './rendererTemplateRef'
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export { isTemplateNode } from './hydration'
|
||||
|
|
|
@ -1568,8 +1568,7 @@ describe('Vapor Mode hydration', () => {
|
|||
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
|
||||
`
|
||||
"
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><!--]-->
|
||||
"
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><!--for-->"
|
||||
`,
|
||||
)
|
||||
|
||||
|
@ -1578,8 +1577,7 @@ describe('Vapor Mode hydration', () => {
|
|||
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
|
||||
`
|
||||
"
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><span>d</span><!--]-->
|
||||
"
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><span>d</span><!--for-->"
|
||||
`,
|
||||
)
|
||||
})
|
||||
|
@ -1601,8 +1599,7 @@ describe('Vapor Mode hydration', () => {
|
|||
`
|
||||
"
|
||||
<!--[--><div>
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><!--]-->
|
||||
</div><div>3</div><!--]-->
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><!--for--></div><div>3</div><!--]-->
|
||||
"
|
||||
`,
|
||||
)
|
||||
|
@ -1613,8 +1610,7 @@ describe('Vapor Mode hydration', () => {
|
|||
`
|
||||
"
|
||||
<!--[--><div>
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><span>d</span><!--]-->
|
||||
</div><div>4</div><!--]-->
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><span>d</span><!--for--></div><div>4</div><!--]-->
|
||||
"
|
||||
`,
|
||||
)
|
||||
|
@ -1635,8 +1631,7 @@ describe('Vapor Mode hydration', () => {
|
|||
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
|
||||
`
|
||||
"<div><span></span>
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><!--]-->
|
||||
<span></span></div>"
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><!--for--><span></span></div>"
|
||||
`,
|
||||
)
|
||||
|
||||
|
@ -1645,8 +1640,7 @@ describe('Vapor Mode hydration', () => {
|
|||
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
|
||||
`
|
||||
"<div><span></span>
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><span>d</span><!--]-->
|
||||
<span></span></div>"
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><span>d</span><!--for--><span></span></div>"
|
||||
`,
|
||||
)
|
||||
|
||||
|
@ -1655,8 +1649,7 @@ describe('Vapor Mode hydration', () => {
|
|||
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
|
||||
`
|
||||
"<div><span></span>
|
||||
<!--[--><span>b</span><span>c</span><span>d</span><!--]-->
|
||||
<span></span></div>"
|
||||
<!--[--><span>b</span><span>c</span><span>d</span><!--for--><span></span></div>"
|
||||
`,
|
||||
)
|
||||
})
|
||||
|
@ -1677,9 +1670,8 @@ describe('Vapor Mode hydration', () => {
|
|||
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
|
||||
`
|
||||
"<div><span></span>
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><!--]-->
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><!--]-->
|
||||
<span></span></div>"
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><!--for-->
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><!--for--><span></span></div>"
|
||||
`,
|
||||
)
|
||||
|
||||
|
@ -1688,9 +1680,8 @@ describe('Vapor Mode hydration', () => {
|
|||
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
|
||||
`
|
||||
"<div><span></span>
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><span>d</span><!--]-->
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><span>d</span><!--]-->
|
||||
<span></span></div>"
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><span>d</span><!--for-->
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><span>d</span><!--for--><span></span></div>"
|
||||
`,
|
||||
)
|
||||
|
||||
|
@ -1699,9 +1690,8 @@ describe('Vapor Mode hydration', () => {
|
|||
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
|
||||
`
|
||||
"<div><span></span>
|
||||
<!--[--><span>c</span><span>d</span><!--]-->
|
||||
<!--[--><span>c</span><span>d</span><!--]-->
|
||||
<span></span></div>"
|
||||
<!--[--><span>c</span><span>d</span><!--for-->
|
||||
<!--[--><span>c</span><span>d</span><!--for--><span></span></div>"
|
||||
`,
|
||||
)
|
||||
})
|
||||
|
@ -1722,8 +1712,7 @@ describe('Vapor Mode hydration', () => {
|
|||
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
|
||||
`
|
||||
"<div>
|
||||
<!--[--><div>comp</div><div>comp</div><div>comp</div><!--]-->
|
||||
</div>"
|
||||
<!--[--><div>comp</div><div>comp</div><div>comp</div><!--for--></div>"
|
||||
`,
|
||||
)
|
||||
|
||||
|
@ -1732,8 +1721,7 @@ describe('Vapor Mode hydration', () => {
|
|||
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
|
||||
`
|
||||
"<div>
|
||||
<!--[--><div>comp</div><div>comp</div><div>comp</div><div>comp</div><!--]-->
|
||||
</div>"
|
||||
<!--[--><div>comp</div><div>comp</div><div>comp</div><div>comp</div><!--for--></div>"
|
||||
`,
|
||||
)
|
||||
})
|
||||
|
@ -1758,8 +1746,7 @@ describe('Vapor Mode hydration', () => {
|
|||
<!--[-->
|
||||
<!--[--><span>a</span><!--]-->
|
||||
<!--[--><span>b</span><!--]-->
|
||||
<!--[--><span>c</span><!--]-->
|
||||
<!--]-->
|
||||
<!--[--><span>c</span><!--for--><!--]-->
|
||||
</div>"
|
||||
`,
|
||||
)
|
||||
|
@ -1772,8 +1759,7 @@ describe('Vapor Mode hydration', () => {
|
|||
<!--[-->
|
||||
<!--[--><span>a</span><!--]-->
|
||||
<!--[--><span>b</span><!--]-->
|
||||
<!--[--><span>c</span><span>d</span><!--slot--><!--]-->
|
||||
<!--]-->
|
||||
<!--[--><span>c</span><span>d</span><!--slot--><!--for--><!--]-->
|
||||
</div>"
|
||||
`,
|
||||
)
|
||||
|
@ -1797,8 +1783,7 @@ describe('Vapor Mode hydration', () => {
|
|||
<!--[-->
|
||||
<!--[--><div>foo</div>-bar-<!--]-->
|
||||
<!--[--><div>foo</div>-bar-<!--]-->
|
||||
<!--[--><div>foo</div>-bar-<!--]-->
|
||||
<!--]-->
|
||||
<!--[--><div>foo</div>-bar-<!--for--><!--]-->
|
||||
</div>"
|
||||
`,
|
||||
)
|
||||
|
@ -1811,8 +1796,7 @@ describe('Vapor Mode hydration', () => {
|
|||
<!--[-->
|
||||
<!--[--><div>foo</div>-bar-<!--]-->
|
||||
<!--[--><div>foo</div>-bar-<!--]-->
|
||||
<!--[--><div>foo</div>-bar-<div>foo</div>-bar-<!--]-->
|
||||
<!--]-->
|
||||
<!--[--><div>foo</div>-bar-<div>foo</div>-bar-<!--for--><!--]-->
|
||||
</div>"
|
||||
`,
|
||||
)
|
||||
|
@ -1950,8 +1934,7 @@ describe('Vapor Mode hydration', () => {
|
|||
`
|
||||
"
|
||||
<!--[-->
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><!--]-->
|
||||
<!--]-->
|
||||
<!--[--><span>a</span><span>b</span><span>c</span><!--for--><!--]-->
|
||||
"
|
||||
`,
|
||||
)
|
||||
|
@ -2383,10 +2366,9 @@ describe('Vapor Mode hydration', () => {
|
|||
`
|
||||
"
|
||||
<!--[-->
|
||||
<!--[--><div>a</div><div>b</div><div>c</div><!--]-->
|
||||
<!--[--><div>a</div><div>b</div><div>c</div><!--for-->
|
||||
<!--[--><span>foo</span><!--]-->
|
||||
<!--[--><div>a</div><div>b</div><div>c</div><!--]-->
|
||||
<!--]-->
|
||||
<!--[--><div>a</div><div>b</div><div>c</div><!--for--><!--]-->
|
||||
"
|
||||
`,
|
||||
)
|
||||
|
@ -2397,10 +2379,9 @@ describe('Vapor Mode hydration', () => {
|
|||
`
|
||||
"
|
||||
<!--[-->
|
||||
<!--[--><div>a</div><div>b</div><div>c</div><div>d</div><!--]-->
|
||||
<!--[--><div>a</div><div>b</div><div>c</div><div>d</div><!--for-->
|
||||
<!--[--><span>foo</span><!--]-->
|
||||
<!--[--><div>a</div><div>b</div><div>c</div><div>d</div><!--]-->
|
||||
<!--]-->
|
||||
<!--[--><div>a</div><div>b</div><div>c</div><div>d</div><!--for--><!--]-->
|
||||
"
|
||||
`,
|
||||
)
|
||||
|
@ -2725,14 +2706,115 @@ describe('Vapor Mode hydration', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe.todo('transition', async () => {
|
||||
test('transition appear', async () => {})
|
||||
describe('transition', async () => {
|
||||
test('transition appear', async () => {
|
||||
const { container } = await testHydration(
|
||||
`<template>
|
||||
<transition appear>
|
||||
<div>foo</div>
|
||||
</transition>
|
||||
</template>`,
|
||||
)
|
||||
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
|
||||
`"<div style="" class="v-enter-from v-enter-active">foo</div>"`,
|
||||
)
|
||||
expect(`mismatch`).not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('transition appear with v-if', async () => {})
|
||||
test('transition appear work with pre-existing class', async () => {
|
||||
const { container } = await testHydration(
|
||||
`<template>
|
||||
<transition appear>
|
||||
<div class="foo">foo</div>
|
||||
</transition>
|
||||
</template>`,
|
||||
)
|
||||
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
|
||||
`"<div class="foo v-enter-from v-enter-active" style="">foo</div>"`,
|
||||
)
|
||||
expect(`mismatch`).not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('transition appear with v-show', async () => {})
|
||||
test('transition appear work with empty content', async () => {
|
||||
const data = ref(true)
|
||||
const { container } = await testHydration(
|
||||
`<template>
|
||||
<transition appear>
|
||||
<slot v-if="data"></slot>
|
||||
<span v-else>foo</span>
|
||||
</transition>
|
||||
</template>`,
|
||||
undefined,
|
||||
data,
|
||||
)
|
||||
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
|
||||
`"<!--slot--><!--if-->"`,
|
||||
)
|
||||
expect(`mismatch`).not.toHaveBeenWarned()
|
||||
|
||||
test('transition appear w/ event listener', async () => {})
|
||||
data.value = false
|
||||
await nextTick()
|
||||
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
|
||||
`"<span class="v-enter-from v-enter-active">foo</span><!--if-->"`,
|
||||
)
|
||||
})
|
||||
|
||||
test('transition appear with v-if', async () => {
|
||||
const data = ref(false)
|
||||
const { container } = await testHydration(
|
||||
`<template>
|
||||
<transition appear>
|
||||
<div v-if="data">foo</div>
|
||||
</transition>
|
||||
</template>`,
|
||||
undefined,
|
||||
data,
|
||||
)
|
||||
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
|
||||
`"<!--if-->"`,
|
||||
)
|
||||
expect(`mismatch`).not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('transition appear with v-show', async () => {
|
||||
const data = ref(false)
|
||||
const { container } = await testHydration(
|
||||
`<template>
|
||||
<transition appear>
|
||||
<div v-show="data">foo</div>
|
||||
</transition>
|
||||
</template>`,
|
||||
undefined,
|
||||
data,
|
||||
)
|
||||
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
|
||||
`"<div style="display:none;" class="v-enter-from v-enter-active v-leave-from v-leave-active">foo</div>"`,
|
||||
)
|
||||
expect(`mismatch`).not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('transition appear w/ event listener', async () => {
|
||||
const { container } = await testHydration(
|
||||
`<script setup>
|
||||
import { ref } from 'vue'
|
||||
const count = ref(0)
|
||||
</script>
|
||||
<template>
|
||||
<transition appear>
|
||||
<button @click="count++">{{ count }}</button>
|
||||
</transition>
|
||||
</template>`,
|
||||
)
|
||||
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
|
||||
`"<button style="" class="v-enter-from v-enter-active">0</button>"`,
|
||||
)
|
||||
|
||||
triggerEvent('click', container.querySelector('button')!)
|
||||
await nextTick()
|
||||
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
|
||||
`"<button style="" class="v-enter-from v-enter-active">1</button>"`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe.todo('async component', async () => {
|
||||
|
|
|
@ -135,9 +135,11 @@ export const createFor = (
|
|||
|
||||
if (isHydrating) {
|
||||
parentAnchor = locateFragmentEndAnchor()!
|
||||
// TODO: special handling vFor not render as a fragment. (inside Transition/TransitionGroup)
|
||||
if (__DEV__ && !parentAnchor) {
|
||||
throw new Error(`v-for fragment anchor node was not found.`)
|
||||
if (__DEV__) {
|
||||
if (!parentAnchor) {
|
||||
throw new Error(`v-for fragment anchor node was not found.`)
|
||||
}
|
||||
;(parentAnchor as Comment).data = 'for'
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
type BaseTransitionProps,
|
||||
type GenericComponentInstance,
|
||||
type TransitionElement,
|
||||
type TransitionHooks,
|
||||
|
@ -9,7 +10,9 @@ import {
|
|||
baseResolveTransitionHooks,
|
||||
checkTransitionMode,
|
||||
currentInstance,
|
||||
isTemplateNode,
|
||||
leaveCbKey,
|
||||
queuePostFlushCb,
|
||||
resolveTransitionProps,
|
||||
useTransitionState,
|
||||
warn,
|
||||
|
@ -24,6 +27,11 @@ import {
|
|||
import { extend, isArray } from '@vue/shared'
|
||||
import { renderEffect } from '../renderEffect'
|
||||
import { isFragment } from '../fragment'
|
||||
import {
|
||||
currentHydrationNode,
|
||||
isHydrating,
|
||||
setCurrentHydrationNode,
|
||||
} from '../dom/hydration'
|
||||
|
||||
const decorate = (t: typeof VaporTransition) => {
|
||||
t.displayName = 'VaporTransition'
|
||||
|
@ -34,6 +42,33 @@ const decorate = (t: typeof VaporTransition) => {
|
|||
|
||||
export const VaporTransition: FunctionalVaporComponent = /*@__PURE__*/ decorate(
|
||||
(props, { slots, attrs }) => {
|
||||
// wrapped <transition appear>
|
||||
let resetDisplay: Function | undefined
|
||||
if (
|
||||
isHydrating &&
|
||||
currentHydrationNode &&
|
||||
isTemplateNode(currentHydrationNode)
|
||||
) {
|
||||
// replace <template> node with inner child
|
||||
const {
|
||||
content: { firstChild },
|
||||
parentNode,
|
||||
} = currentHydrationNode
|
||||
if (firstChild) {
|
||||
if (
|
||||
firstChild instanceof HTMLElement ||
|
||||
firstChild instanceof SVGElement
|
||||
) {
|
||||
const originalDisplay = firstChild.style.display
|
||||
firstChild.style.display = 'none'
|
||||
resetDisplay = () => (firstChild.style.display = originalDisplay)
|
||||
}
|
||||
|
||||
parentNode!.replaceChild(firstChild, currentHydrationNode)
|
||||
setCurrentHydrationNode(firstChild)
|
||||
}
|
||||
}
|
||||
|
||||
const children = (slots.default && slots.default()) as any as Block
|
||||
if (!children) return
|
||||
|
||||
|
@ -41,7 +76,7 @@ export const VaporTransition: FunctionalVaporComponent = /*@__PURE__*/ decorate(
|
|||
const { mode } = props
|
||||
checkTransitionMode(mode)
|
||||
|
||||
let resolvedProps
|
||||
let resolvedProps: BaseTransitionProps<Element>
|
||||
let isMounted = false
|
||||
renderEffect(() => {
|
||||
resolvedProps = resolveTransitionProps(props)
|
||||
|
@ -81,7 +116,7 @@ export const VaporTransition: FunctionalVaporComponent = /*@__PURE__*/ decorate(
|
|||
})
|
||||
}
|
||||
|
||||
applyTransitionHooks(
|
||||
const hooks = applyTransitionHooks(
|
||||
children,
|
||||
{
|
||||
state: useTransitionState(),
|
||||
|
@ -91,6 +126,13 @@ export const VaporTransition: FunctionalVaporComponent = /*@__PURE__*/ decorate(
|
|||
fallthroughAttrs,
|
||||
)
|
||||
|
||||
if (resetDisplay && resolvedProps!.appear) {
|
||||
const child = findTransitionBlock(children)!
|
||||
hooks.beforeEnter(child)
|
||||
resetDisplay()
|
||||
queuePostFlushCb(() => hooks.enter(child))
|
||||
}
|
||||
|
||||
return children
|
||||
},
|
||||
)
|
||||
|
|
|
@ -158,7 +158,7 @@ export const VaporTransitionGroup: ObjectVaporComponent = decorate({
|
|||
return container
|
||||
} else {
|
||||
const frag = __DEV__
|
||||
? new DynamicFragment('transitionGroup')
|
||||
? new DynamicFragment('transition-group')
|
||||
: new DynamicFragment()
|
||||
renderEffect(() => frag.update(() => slottedBlock))
|
||||
return frag
|
||||
|
|
|
@ -12,6 +12,8 @@ import {
|
|||
import type { TransitionHooks } from '@vue/runtime-dom'
|
||||
import {
|
||||
advanceHydrationNode,
|
||||
currentHydrationNode,
|
||||
isComment,
|
||||
isHydrating,
|
||||
locateFragmentEndAnchor,
|
||||
locateHydrationNode,
|
||||
|
@ -152,13 +154,25 @@ export class DynamicFragment extends VaporFragment {
|
|||
if (!this.anchor) {
|
||||
throw new Error('Failed to locate if anchor')
|
||||
} else {
|
||||
;(this.anchor as Comment).data = this.anchorLabel
|
||||
if (__DEV__) {
|
||||
;(this.anchor as Comment).data = this.anchorLabel
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// reuse the vdom fragment end anchor for slots
|
||||
if (this.anchorLabel === 'slot') {
|
||||
// reuse the empty comment node for empty slot
|
||||
// e.g. `<slot v-if="false"></slot>`
|
||||
if (isEmpty && isComment(currentHydrationNode!, '')) {
|
||||
this.anchor = currentHydrationNode!
|
||||
if (__DEV__) {
|
||||
;(this.anchor as Comment).data = this.anchorLabel!
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// reuse the vdom fragment end anchor for slots
|
||||
this.anchor = locateFragmentEndAnchor()!
|
||||
if (!this.anchor) {
|
||||
throw new Error('Failed to locate slot anchor')
|
||||
|
|
Loading…
Reference in New Issue