]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix: ensure updateCssVars is called when subtree changed
authordaiwei <daiwei521@126.com>
Thu, 13 Nov 2025 02:39:25 +0000 (10:39 +0800)
committerdaiwei <daiwei521@126.com>
Thu, 13 Nov 2025 02:39:25 +0000 (10:39 +0800)
packages/runtime-vapor/__tests__/helpers/useCssVars.spec.ts
packages/runtime-vapor/src/apiCreateFor.ts
packages/runtime-vapor/src/components/Teleport.ts
packages/runtime-vapor/src/fragment.ts
packages/runtime-vapor/src/vdomInterop.ts

index 0aac8ab479d94d2286bef8f14d93eaca383eddbc..1f73df6e0a84fd0b307682f4630138cfb417550b 100644 (file)
@@ -270,8 +270,7 @@ describe('useVaporCssVars', () => {
     expect(target.children.length).toBe(2)
     for (const c of [].slice.call(target.children as any)) {
       expect((c as HTMLElement).style.getPropertyValue(`--color`)).toBe('red')
-      // TODO: problem is slot updateCssVars not called when slot changes
-      // expect((c as HTMLElement).outerHTML.includes('data-v-owner')).toBe(true)
+      expect((c as HTMLElement).outerHTML.includes('data-v-owner')).toBe(true)
     }
   })
 
index 5bc4473997cb95ef5a0c6f5280329d77f2d664f3..927809096f16cfefafa3c2b6529698adc8a7ade9 100644 (file)
@@ -402,6 +402,7 @@ export const createFor = (
     } else {
       oldBlocks = []
     }
+    if (frag.effects) frag.effects.forEach(effect => effect())
     setActiveSub(prevSub)
   }
 
index cb51406c4a3db54bfb1f5ab98d9d93ae802cacea..871da0a02cf1646bdb2bbabeb9c8647a2a5b0454 100644 (file)
@@ -21,7 +21,7 @@ import {
 import { rawPropsProxyHandlers } from '../componentProps'
 import { renderEffect } from '../renderEffect'
 import { extend, isArray } from '@vue/shared'
-import { VaporFragment } from '../fragment'
+import { VaporFragment, isFragment } from '../fragment'
 import {
   advanceHydrationNode,
   currentHydrationNode,
@@ -48,6 +48,7 @@ export class TeleportFragment extends VaporFragment {
   private rawProps?: LooseRawProps
   private resolvedProps?: TeleportProps
   private rawSlots?: LooseRawSlots
+  isDisabled?: boolean
 
   target?: ParentNode | null
   targetAnchor?: Node | null
@@ -78,6 +79,7 @@ export class TeleportFragment extends VaporFragment {
           rawPropsProxyHandlers,
         ) as any as TeleportProps,
       )
+      this.isDisabled = isTeleportDisabled(this.resolvedProps!)
       this.handlePropsUpdate()
     })
 
@@ -97,8 +99,24 @@ export class TeleportFragment extends VaporFragment {
       )
     })
 
+    const nodes = this.nodes
+    // register updateCssVars to sub fragments's effects so that
+    // updateCssVars will be called when subtree changed
+    if (this.parentComponent && this.parentComponent.ut) {
+      if (isFragment(nodes)) {
+        ;(nodes.effects || (nodes.effects = [])).push(() => updateCssVars(this))
+      } else if (isArray(nodes)) {
+        nodes.forEach(node => {
+          if (isFragment(node)) {
+            ;(node.effects || (node.effects = [])).push(() =>
+              updateCssVars(this),
+            )
+          }
+        })
+      }
+    }
+
     if (__DEV__) {
-      const nodes = this.nodes
       if (isVaporComponent(nodes)) {
         nodes.parentTeleport = this
       } else if (isArray(nodes)) {
@@ -162,7 +180,7 @@ export class TeleportFragment extends VaporFragment {
         }
 
         mount(target, this.targetAnchor!)
-        updateCssVars(this, false)
+        updateCssVars(this)
       } else if (__DEV__) {
         warn(
           `Invalid Teleport target on ${this.targetAnchor ? 'update' : 'mount'}:`,
@@ -173,9 +191,9 @@ export class TeleportFragment extends VaporFragment {
     }
 
     // mount into main container
-    if (isTeleportDisabled(this.resolvedProps!)) {
+    if (this.isDisabled) {
       mount(this.parent, this.anchor!)
-      updateCssVars(this, true)
+      updateCssVars(this)
     }
     // mount into target container
     else {
@@ -333,11 +351,11 @@ function locateTeleportEndAnchor(
   return null
 }
 
-function updateCssVars(frag: TeleportFragment, isDisabled: boolean) {
+function updateCssVars(frag: TeleportFragment) {
   const ctx = frag.parentComponent as GenericComponentInstance
   if (ctx && ctx.ut) {
     let node, anchor
-    if (isDisabled) {
+    if (frag.isDisabled) {
       node = frag.placeholder
       anchor = frag.anchor
     } else {
index e30909ea067b564a01d9b79a9732a2bac479ea5e..714a4b0fc8738641ba8f3409a9abe55554cf0bf2 100644 (file)
@@ -56,6 +56,9 @@ export class VaporFragment<T extends Block = Block>
     refKey: string | undefined,
   ) => void
 
+  // effects to run after fragment render
+  effects?: (() => void)[]
+
   constructor(nodes: T) {
     this.nodes = nodes
   }
@@ -185,6 +188,7 @@ export class DynamicFragment extends VaporFragment {
       this.scope = undefined
       this.nodes = []
     }
+    if (this.effects) this.effects.forEach(effect => effect())
   }
 
   hydrate = (isEmpty = false): void => {
index ab232ff8c7878147be37ce2ee8126755e04be2de..86d754160b43e3ab759a43406273aa2f2169b324 100644 (file)
@@ -380,6 +380,7 @@ function createVDOMComponent(
     }
 
     frag.nodes = vnode.el as any
+    if (frag.effects) frag.effects.forEach(effect => effect())
   }
 
   frag.remove = unmount
@@ -445,6 +446,8 @@ function renderVDOMSlot(
         internals.um(oldVNode, parentComponent as any, null)
       }
     }
+
+    if (frag.effects) frag.effects.forEach(effect => effect())
   }
 
   const render = (parentNode?: ParentNode, anchor?: Node | null) => {