]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(Teleport): hydrate disabled Teleport with undefined target (#11235)
authorlinzhe <40790268+linzhe141@users.noreply.github.com>
Tue, 2 Sep 2025 09:08:15 +0000 (17:08 +0800)
committerGitHub <noreply@github.com>
Tue, 2 Sep 2025 09:08:15 +0000 (17:08 +0800)
close #11230

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

index 6828e61ec56f1f063ff3d48b60d313f38ef11a6a..c0f25ab0f649d48e17278ce626932374d99ec4fc 100644 (file)
@@ -2357,6 +2357,30 @@ describe('SSR hydration', () => {
       expect(`Hydration style mismatch`).not.toHaveBeenWarned()
     })
 
+    test('with disabled teleport + undefined target', async () => {
+      const container = document.createElement('div')
+      const isOpen = ref(false)
+      const App = {
+        setup() {
+          return { isOpen }
+        },
+        template: `
+          <Teleport :to="undefined" :disabled="true">
+            <div v-if="isOpen">
+              Menu is open...
+            </div>
+          </Teleport>`,
+      }
+      container.innerHTML = await renderToString(h(App))
+      const app = createSSRApp(App)
+      app.mount(container)
+      isOpen.value = true
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<!--teleport start--><div> Menu is open... </div><!--teleport end-->`,
+      )
+    })
+
     test('escape css var name', () => {
       const container = document.createElement('div')
       container.innerHTML = `<div style="padding: 4px;--foo\\.bar:red;"></div>`
index c37356a7869bfa054eea5fa15e917171fb0266b2..9b32989f0b8137f1a4b07410ef439f22f8e97cf7 100644 (file)
@@ -406,29 +406,43 @@ function hydrateTeleport(
     optimized: boolean,
   ) => Node | null,
 ): Node | null {
+  function hydrateDisabledTeleport(
+    node: Node,
+    vnode: VNode,
+    targetStart: Node | null,
+    targetAnchor: Node | null,
+  ) {
+    vnode.anchor = hydrateChildren(
+      nextSibling(node),
+      vnode,
+      parentNode(node)!,
+      parentComponent,
+      parentSuspense,
+      slotScopeIds,
+      optimized,
+    )
+    vnode.targetStart = targetStart
+    vnode.targetAnchor = targetAnchor
+  }
+
   const target = (vnode.target = resolveTarget<Element>(
     vnode.props,
     querySelector,
   ))
+  const disabled = isTeleportDisabled(vnode.props)
   if (target) {
-    const disabled = isTeleportDisabled(vnode.props)
     // if multiple teleports rendered to the same target element, we need to
     // pick up from where the last teleport finished instead of the first node
     const targetNode =
       (target as TeleportTargetElement)._lpa || target.firstChild
     if (vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
       if (disabled) {
-        vnode.anchor = hydrateChildren(
-          nextSibling(node),
+        hydrateDisabledTeleport(
+          node,
           vnode,
-          parentNode(node)!,
-          parentComponent,
-          parentSuspense,
-          slotScopeIds,
-          optimized,
+          targetNode,
+          targetNode && nextSibling(targetNode),
         )
-        vnode.targetStart = targetNode
-        vnode.targetAnchor = targetNode && nextSibling(targetNode)
       } else {
         vnode.anchor = nextSibling(node)
 
@@ -470,6 +484,10 @@ function hydrateTeleport(
       }
     }
     updateCssVars(vnode, disabled)
+  } else if (disabled) {
+    if (vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
+      hydrateDisabledTeleport(node, vnode, node, nextSibling(node))
+    }
   }
   return vnode.anchor && nextSibling(vnode.anchor as Node)
 }