Bläddra i källkod

fix(runtime-vapor): preserve slot-owner css vars for teleported slot content (#14476)

close #14473
edison 1 månad sedan
förälder
incheckning
24f20e8a3f

+ 60 - 1
packages/runtime-vapor/__tests__/helpers/useCssVars.spec.ts

@@ -10,6 +10,7 @@ import {
   setStyle,
   template,
   useVaporCssVars,
+  withVaporCtx,
 } from '@vue/runtime-vapor'
 import { nextTick, onMounted, reactive, ref } from '@vue/runtime-core'
 import { makeRender } from '../_utils'
@@ -209,7 +210,7 @@ describe('useVaporCssVars', () => {
       setup() {
         useVaporCssVars(() => state)
         return createComponent(Child, null, {
-          default: () =>
+          default: withVaporCtx(() =>
             createComponent(
               VaporTeleport,
               { to: () => target },
@@ -217,6 +218,7 @@ describe('useVaporCssVars', () => {
                 default: () => template('<div></div>', true)(),
               },
             ),
+          ),
         })
       },
     }).render()
@@ -233,6 +235,63 @@ describe('useVaporCssVars', () => {
     }
   })
 
+  test('with teleport in child slot should keep slot owner css vars across branch switches', async () => {
+    const parentState = reactive({ parent: 'red' })
+    const childState = reactive({ child: 'blue' })
+    const target = document.createElement('div')
+    document.body.appendChild(target)
+    const show = ref(true)
+
+    const Child = defineVaporComponent({
+      setup(_, { slots }) {
+        useVaporCssVars(() => childState)
+        return slots.default!()
+      },
+    })
+
+    define({
+      setup() {
+        useVaporCssVars(() => parentState)
+        return createComponent(Child, null, {
+          default: withVaporCtx(() =>
+            createComponent(
+              VaporTeleport,
+              { to: () => target },
+              {
+                default: () =>
+                  createIf(
+                    () => show.value,
+                    () => template('<div></div>', true)(),
+                    () => template('<span></span>', true)(),
+                  ),
+              },
+            ),
+          ),
+        })
+      },
+    }).render()
+
+    await nextTick()
+    let el = target.children[0] as HTMLElement
+    expect(el.tagName).toBe('DIV')
+    expect(el.style.getPropertyValue(`--parent`)).toBe('red')
+    expect(el.style.getPropertyValue(`--child`)).toBe('')
+
+    show.value = false
+    await nextTick()
+    el = target.children[0] as HTMLElement
+    expect(el.tagName).toBe('SPAN')
+    expect(el.style.getPropertyValue(`--parent`)).toBe('red')
+    expect(el.style.getPropertyValue(`--child`)).toBe('')
+
+    show.value = true
+    await nextTick()
+    el = target.children[0] as HTMLElement
+    expect(el.tagName).toBe('DIV')
+    expect(el.style.getPropertyValue(`--parent`)).toBe('red')
+    expect(el.style.getPropertyValue(`--child`)).toBe('')
+  })
+
   test('with teleport(change subTree)', async () => {
     const state = reactive({ color: 'red' })
     const target = document.createElement('div')

+ 2 - 2
packages/runtime-vapor/src/components/Teleport.ts

@@ -4,7 +4,6 @@ import {
   MoveType,
   type TeleportProps,
   type TeleportTargetElement,
-  currentInstance,
   isMismatchAllowed,
   isTeleportDeferred,
   isTeleportDisabled,
@@ -40,6 +39,7 @@ import {
   setCurrentHydrationNode,
 } from '../dom/hydration'
 import type { DefineVaporSetupFnComponent } from '../apiDefineComponent'
+import { getScopeOwner } from '../componentSlots'
 
 const VaporTeleportImpl = {
   name: 'VaporTeleport',
@@ -76,7 +76,7 @@ export class TeleportFragment extends VaporFragment {
     super([])
     this.rawProps = props
     this.rawSlots = slots
-    this.parentComponent = currentInstance
+    this.parentComponent = getScopeOwner()
     this.anchor = isHydrating
       ? undefined
       : __DEV__

+ 5 - 0
packages/runtime-vapor/src/helpers/useCssVars.ts

@@ -7,6 +7,7 @@ import {
 import { type VaporComponentInstance, isVaporComponent } from '../component'
 import { isArray } from '@vue/shared'
 import type { Block } from '../block'
+import { isTeleportFragment } from '../components/Teleport'
 
 export function useVaporCssVars(getter: () => Record<string, string>): void {
   if (!__BROWSER__ && !__TEST__) return
@@ -50,6 +51,10 @@ function setVarsOnBlock(block: Block, vars: Record<string, string>): void {
     block.forEach(child => setVarsOnBlock(child, vars))
   } else if (isVaporComponent(block)) {
     setVarsOnBlock(block.block!, vars)
+  } else if (isTeleportFragment(block)) {
+    // Teleport children are handled via data-v-owner + ut() to preserve
+    // lexical owner semantics for slot content.
+    return
   } else {
     setVarsOnBlock(block.nodes, vars)
   }