]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: save
authordaiwei <daiwei521@126.com>
Thu, 20 Mar 2025 14:17:57 +0000 (22:17 +0800)
committerdaiwei <daiwei521@126.com>
Thu, 20 Mar 2025 14:17:57 +0000 (22:17 +0800)
packages/runtime-core/src/components/Teleport.ts
packages/runtime-core/src/index.ts
packages/runtime-vapor/src/block.ts
packages/runtime-vapor/src/component.ts
packages/runtime-vapor/src/components/Teleport.ts [new file with mode: 0644]
packages/runtime-vapor/src/index.ts

index a6445df7b055b2cc868ea634ba6eeeb9837a0515..c365ad0a217b1ebbe3135239ed9aed598a9cdcfc 100644 (file)
@@ -27,10 +27,10 @@ export const TeleportEndKey: unique symbol = Symbol('_vte')
 
 export const isTeleport = (type: any): boolean => type.__isTeleport
 
-const isTeleportDisabled = (props: VNode['props']): boolean =>
+export const isTeleportDisabled = (props: VNode['props']): boolean =>
   props && (props.disabled || props.disabled === '')
 
-const isTeleportDeferred = (props: VNode['props']): boolean =>
+export const isTeleportDeferred = (props: VNode['props']): boolean =>
   props && (props.defer || props.defer === '')
 
 const isTargetSVG = (target: RendererElement): boolean =>
@@ -39,7 +39,7 @@ const isTargetSVG = (target: RendererElement): boolean =>
 const isTargetMathML = (target: RendererElement): boolean =>
   typeof MathMLElement === 'function' && target instanceof MathMLElement
 
-const resolveTarget = <T = RendererElement>(
+export const resolveTarget = <T = RendererElement>(
   props: TeleportProps | null,
   select: RendererOptions['querySelector'],
 ): T | null => {
index c7150e38e808c8cbf4ee1df47d070984d2bfe8bb..2d721b058f0ccf0061b1ba45152b692d421453e7 100644 (file)
@@ -557,3 +557,12 @@ export { startMeasure, endMeasure } from './profiling'
  * @internal
  */
 export { initFeatureFlags } from './featureFlags'
+/**
+ * @internal
+ */
+export {
+  resolveTarget,
+  isTeleportDisabled,
+  isTeleportDeferred,
+  TeleportEndKey,
+} from './components/Teleport'
index b782afd38d35b66c9fed33675d4f4705efbceb3f..6ec62da20d517fbfac9c238922afa996735383d6 100644 (file)
@@ -20,6 +20,8 @@ export type BlockFn = (...args: any[]) => Block
 
 export class VaporFragment {
   nodes: Block
+  target?: ParentNode | null
+  targetAnchor?: Node | null
   anchor?: Node
   insert?: (parent: ParentNode, anchor: Node | null) => void
   remove?: (parent?: ParentNode) => void
@@ -129,7 +131,7 @@ export function insert(
       // TODO handle hydration for vdom interop
       block.insert(parent, anchor)
     } else {
-      insert(block.nodes, parent, anchor)
+      insert(block.nodes, block.target || parent, block.targetAnchor || anchor)
     }
     if (block.anchor) insert(block.anchor, parent, anchor)
   }
index 548babebf8beef2115e31356d50a989e2e1a0112..7989b67a8b6bde20f21e822f316e13e3610a137b 100644 (file)
@@ -60,6 +60,7 @@ import {
 import { hmrReload, hmrRerender } from './hmr'
 import { isHydrating, locateHydrationNode } from './dom/hydration'
 import { insertionAnchor, insertionParent } from './insertionState'
+import type { VaporTeleportImpl } from './components/Teleport'
 
 export { currentInstance } from '@vue/runtime-dom'
 
@@ -92,6 +93,8 @@ export interface ObjectVaporComponent
 
   name?: string
   vapor?: boolean
+
+  __isTeleport?: boolean
 }
 
 interface SharedInternalOptions {
@@ -157,6 +160,21 @@ export function createComponent(
     return frag
   }
 
+  // teleport
+  if (component.__isTeleport) {
+    const frag = (component as typeof VaporTeleportImpl).process(
+      rawProps!,
+      rawSlots!,
+    )
+    if (!isHydrating && _insertionParent) {
+      insert(frag, _insertionParent, _insertionAnchor)
+    } else {
+      frag.hydrate()
+    }
+
+    return frag as any
+  }
+
   if (
     isSingleRoot &&
     component.inheritAttrs !== false &&
diff --git a/packages/runtime-vapor/src/components/Teleport.ts b/packages/runtime-vapor/src/components/Teleport.ts
new file mode 100644 (file)
index 0000000..762d69c
--- /dev/null
@@ -0,0 +1,162 @@
+import {
+  TeleportEndKey,
+  type TeleportProps,
+  isTeleportDeferred,
+  isTeleportDisabled,
+  queuePostFlushCb,
+  resolveTarget,
+  warn,
+} from '@vue/runtime-dom'
+import {
+  type Block,
+  type BlockFn,
+  VaporFragment,
+  insert,
+  remove,
+} from '../block'
+import { createComment, createTextNode, querySelector } from '../dom/node'
+import type { LooseRawProps, LooseRawSlots } from '../component'
+import { rawPropsProxyHandlers } from '../componentProps'
+import { renderEffect } from '../renderEffect'
+
+export const VaporTeleportImpl = {
+  name: 'VaporTeleport',
+  __isTeleport: true,
+  __vapor: true,
+
+  process(props: LooseRawProps, slots: LooseRawSlots): TeleportFragment {
+    const children = slots.default && (slots.default as BlockFn)()
+    const frag = __DEV__
+      ? new TeleportFragment('teleport')
+      : new TeleportFragment()
+
+    const resolvedProps = new Proxy(
+      props,
+      rawPropsProxyHandlers,
+    ) as any as TeleportProps
+
+    renderEffect(() => frag.update(resolvedProps, children))
+
+    frag.remove = parent => {
+      const {
+        nodes,
+        target,
+        cachedTargetAnchor,
+        targetStart,
+        placeholder,
+        mainAnchor,
+      } = frag
+
+      remove(nodes, target || parent)
+
+      // remove anchors
+      if (targetStart) {
+        let parentNode = targetStart.parentNode!
+        remove(targetStart!, parentNode)
+        remove(cachedTargetAnchor!, parentNode)
+      }
+      if (placeholder && placeholder.isConnected) {
+        remove(placeholder!, parent)
+        remove(mainAnchor!, parent)
+      }
+    }
+
+    return frag
+  },
+}
+
+export class TeleportFragment extends VaporFragment {
+  anchor: Node
+  target?: ParentNode | null
+  targetStart?: Node | null
+  targetAnchor?: Node | null
+  cachedTargetAnchor?: Node
+  mainAnchor?: Node
+  placeholder?: Node
+
+  constructor(anchorLabel?: string) {
+    super([])
+    this.anchor =
+      __DEV__ && anchorLabel ? createComment(anchorLabel) : createTextNode()
+  }
+
+  update(props: TeleportProps, children: Block): void {
+    this.nodes = children
+    const parent = this.anchor.parentNode
+
+    if (!this.mainAnchor) {
+      this.mainAnchor = __DEV__
+        ? createComment('teleport end')
+        : createTextNode()
+    }
+    if (!this.placeholder) {
+      this.placeholder = __DEV__
+        ? createComment('teleport start')
+        : createTextNode()
+    }
+    if (parent) {
+      insert(this.placeholder, parent, this.anchor)
+      insert(this.mainAnchor, parent, this.anchor)
+    }
+
+    const disabled = isTeleportDisabled(props)
+    if (disabled) {
+      this.target = this.anchor.parentNode
+      this.targetAnchor = parent ? this.mainAnchor : null
+    } else {
+      const target = (this.target = resolveTarget(
+        props,
+        querySelector,
+      ) as ParentNode)
+      if (target) {
+        if (
+          // initial mount
+          !this.targetStart ||
+          // target changed
+          this.targetStart.parentNode !== target
+        ) {
+          ;[this.targetAnchor, this.targetStart] = prepareAnchor(target)
+          this.cachedTargetAnchor = this.targetAnchor
+        } else {
+          // re-mount or target not changed, use cached target anchor
+          this.targetAnchor = this.cachedTargetAnchor
+        }
+      } else if (__DEV__) {
+        warn('Invalid Teleport target on mount:', target, `(${typeof target})`)
+      }
+    }
+
+    const mountToTarget = () => {
+      insert(this.nodes, this.target!, this.targetAnchor)
+    }
+
+    if (parent) {
+      if (isTeleportDeferred(props)) {
+        queuePostFlushCb(mountToTarget)
+      } else {
+        mountToTarget()
+      }
+    }
+  }
+
+  hydrate(): void {
+    // TODO
+  }
+}
+
+function prepareAnchor(target: ParentNode | null) {
+  const targetStart = createTextNode('targetStart')
+  const targetAnchor = createTextNode('targetAnchor')
+
+  // attach a special property, so we can skip teleported content in
+  // renderer's nextSibling search
+  // @ts-expect-error
+  targetStart[TeleportEndKey] = targetAnchor
+
+  if (target) {
+    insert(targetStart, target)
+    insert(targetAnchor, target)
+  }
+
+  return [targetAnchor, targetStart]
+}
index 682532fa4d80aa01a23a948bbb538b049715a9ff..4b55949a63a5529647b433d38a0202b6e3a3f4d0 100644 (file)
@@ -3,6 +3,7 @@ export { createVaporApp, createVaporSSRApp } from './apiCreateApp'
 export { defineVaporComponent } from './apiDefineComponent'
 export { vaporInteropPlugin } from './vdomInterop'
 export type { VaporDirective } from './directives/custom'
+export { VaporTeleportImpl as VaporTeleport } from './components/Teleport'
 
 // compiler-use only
 export { insert, prepend, remove, isFragment, VaporFragment } from './block'