| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- import { EffectScope, setActiveSub } from '@vue/reactivity'
- import { createComment, createTextNode } from './dom/node'
- import {
- type Block,
- type BlockFn,
- type TransitionOptions,
- type VaporTransitionHooks,
- insert,
- isValidBlock,
- remove,
- } from './block'
- import type { TransitionHooks } from '@vue/runtime-dom'
- import {
- advanceHydrationNode,
- currentHydrationNode,
- isComment,
- isHydrating,
- locateHydrationNode,
- locateVaporFragmentAnchor,
- } from './dom/hydration'
- import {
- applyTransitionHooks,
- applyTransitionLeaveHooks,
- } from './components/Transition'
- import type { VaporComponentInstance } from './component'
- export class VaporFragment<T extends Block = Block>
- implements TransitionOptions
- {
- $key?: any
- $transition?: VaporTransitionHooks | undefined
- nodes: T
- anchor?: Node
- insert?: (
- parent: ParentNode,
- anchor: Node | null,
- transitionHooks?: TransitionHooks,
- ) => void
- remove?: (parent?: ParentNode, transitionHooks?: TransitionHooks) => void
- fallback?: BlockFn
- target?: ParentNode | null
- targetAnchor?: Node | null
- getNodes?: () => Block
- setRef?: (comp: VaporComponentInstance) => void
- constructor(nodes: T) {
- this.nodes = nodes
- }
- }
- export class DynamicFragment extends VaporFragment {
- anchor!: Node
- scope: EffectScope | undefined
- current?: BlockFn
- fallback?: BlockFn
- /**
- * slot only
- * indicates forwarded slot
- */
- forwarded?: boolean
- teardown?: () => void
- anchorLabel?: string
- constructor(anchorLabel?: string) {
- super([])
- if (isHydrating) {
- locateHydrationNode(anchorLabel === 'slot')
- this.anchorLabel = anchorLabel
- } else {
- this.anchor =
- __DEV__ && anchorLabel ? createComment(anchorLabel) : createTextNode()
- }
- }
- update(render?: BlockFn, key: any = render): void {
- if (key === this.current) {
- if (isHydrating) this.hydrate(this.anchorLabel!)
- return
- }
- this.current = key
- const prevSub = setActiveSub()
- const parent = isHydrating ? null : this.anchor.parentNode
- const transition = this.$transition
- const renderBranch = () => {
- if (render) {
- this.scope = new EffectScope()
- this.nodes = this.scope.run(render) || []
- if (transition) {
- this.$transition = applyTransitionHooks(this.nodes, transition)
- }
- if (parent) insert(this.nodes, parent, this.anchor)
- } else {
- this.scope = undefined
- this.nodes = []
- }
- }
- // teardown previous branch
- if (this.scope) {
- this.scope.stop()
- if (parent) this.teardown && this.teardown()
- const mode = transition && transition.mode
- if (mode) {
- applyTransitionLeaveHooks(this.nodes, transition, renderBranch)
- parent && remove(this.nodes, parent)
- if (mode === 'out-in') {
- setActiveSub(prevSub)
- return
- }
- } else {
- parent && remove(this.nodes, parent)
- }
- }
- renderBranch()
- if (this.fallback) {
- // set fallback for nested fragments
- const hasNestedFragment = isFragment(this.nodes)
- if (hasNestedFragment) {
- setFragmentFallback(this.nodes as VaporFragment, this.fallback)
- }
- const invalidFragment = findInvalidFragment(this)
- if (invalidFragment) {
- parent && remove(this.nodes, parent)
- const scope = this.scope || (this.scope = new EffectScope())
- scope.run(() => {
- // for nested fragments, render invalid fragment's fallback
- if (hasNestedFragment) {
- renderFragmentFallback(invalidFragment)
- } else {
- this.nodes = this.fallback!() || []
- }
- })
- parent && insert(this.nodes, parent, this.anchor)
- }
- }
- setActiveSub(prevSub)
- if (isHydrating) this.hydrate(this.anchorLabel!)
- }
- hydrate(label: string): void {
- // for `v-if="false"` the node will be an empty comment, use it as the anchor.
- // otherwise, find next sibling vapor fragment anchor
- if (label === 'if' && isComment(currentHydrationNode!, '')) {
- this.anchor = currentHydrationNode
- } else {
- let anchor = locateVaporFragmentAnchor(currentHydrationNode!, label)!
- if (!anchor && (label === 'slot' || label === 'if')) {
- // fallback to fragment end anchor for ssr vdom slot
- anchor = locateVaporFragmentAnchor(currentHydrationNode!, ']')!
- }
- if (anchor) {
- this.anchor = anchor
- } else if (__DEV__) {
- // this should not happen
- throw new Error(`${label} fragment anchor node was not found.`)
- }
- }
- advanceHydrationNode(this.anchor)
- }
- }
- export class ForFragment extends VaporFragment<Block[]> {
- constructor(nodes: Block[]) {
- super(nodes)
- }
- }
- export function isFragment(val: NonNullable<unknown>): val is VaporFragment {
- return val instanceof VaporFragment
- }
- export function setFragmentFallback(
- fragment: VaporFragment,
- fallback: BlockFn,
- ): void {
- // stop recursion if fragment has its own fallback
- if (fragment.fallback) return
- fragment.fallback = fallback
- if (isFragment(fragment.nodes)) {
- setFragmentFallback(fragment.nodes, fallback)
- }
- }
- function renderFragmentFallback(fragment: VaporFragment): void {
- if (fragment instanceof ForFragment) {
- fragment.nodes[0] = [fragment.fallback!() || []] as Block[]
- } else if (fragment instanceof DynamicFragment) {
- fragment.update(fragment.fallback)
- } else {
- // vdom slots
- }
- }
- function findInvalidFragment(fragment: VaporFragment): VaporFragment | null {
- if (isValidBlock(fragment.nodes)) return null
- return isFragment(fragment.nodes)
- ? findInvalidFragment(fragment.nodes) || fragment
- : fragment
- }
|