Browse Source

fix(runtime-dom): ensure css vars deps tracking when component has no DOM on mount (#14299)

edison 3 months ago
parent
commit
084389ed39

+ 30 - 0
packages/runtime-dom/__tests__/helpers/useCssVars.spec.ts

@@ -488,4 +488,34 @@ describe('useCssVars', () => {
     expect(style.getPropertyValue('--foo')).toBe('initial')
     expect(style.getPropertyValue('--bar')).toBe('initial')
   })
+
+  test('with v-if initial false then update css vars', async () => {
+    const state = reactive({ color: 'red' })
+    const root = document.createElement('div')
+    const show = ref(false)
+
+    const App = {
+      setup() {
+        useCssVars(() => state)
+        return () => (show.value ? h('div') : null)
+      },
+    }
+
+    render(h(App), root)
+    await nextTick()
+    expect(root.children.length).toBe(0)
+
+    // toggle v-if to true
+    show.value = true
+    await nextTick()
+    expect(root.children.length).toBe(1)
+    let el = root.children[0] as HTMLElement
+    expect(el.style.getPropertyValue(`--color`)).toBe('red')
+
+    // update css vars
+    state.color = 'green'
+    await nextTick()
+    el = root.children[0] as HTMLElement
+    expect(el.style.getPropertyValue(`--color`)).toBe('green')
+  })
 })

+ 15 - 6
packages/runtime-dom/src/helpers/useCssVars.ts

@@ -11,7 +11,7 @@ import {
   warn,
   watch,
 } from '@vue/runtime-core'
-import { NOOP, ShapeFlags, normalizeCssVarValue } from '@vue/shared'
+import { NOOP, ShapeFlags, extend, normalizeCssVarValue } from '@vue/shared'
 
 export const CSS_VAR_TEXT: unique symbol = Symbol(__DEV__ ? 'CSS_VAR_TEXT' : '')
 /**
@@ -99,8 +99,7 @@ export function baseUseCssVars(
     ).forEach(node => setVarsOnNode(node, vars))
   })
 
-  const applyCssCars = () => {
-    const vars = getVars()
+  const applyCssVars = (vars = getVars()) => {
     setVars(vars)
     updateTeleports(vars)
   }
@@ -108,13 +107,23 @@ export function baseUseCssVars(
   // handle cases where child component root is affected
   // and triggers reflow in onMounted
   onBeforeUpdate(() => {
-    queuePostFlushCb(applyCssCars)
+    queuePostFlushCb(applyCssVars)
   })
 
   onMounted(() => {
     // run setVars synchronously here, but run as post-effect on changes
-    watch(applyCssCars, NOOP, { flush: 'post' })
-    const ob = new MutationObserver(applyCssCars)
+    watch(
+      () => {
+        const vars = getVars()
+        // access all properties to ensure dependency tracking
+        // even if there are no DOM elements to receive vars yet
+        extend({}, vars)
+        applyCssVars(vars)
+      },
+      NOOP,
+      { flush: 'post' },
+    )
+    const ob = new MutationObserver(() => applyCssVars())
     ob.observe(getParentNode(), { childList: true })
     onUnmounted(() => ob.disconnect())
   })

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

@@ -297,7 +297,7 @@ describe('useVaporCssVars', () => {
     expect(host.children[0].outerHTML.includes('data-v-owner')).toBe(true)
   })
 
-  test('with teleport and nested component', async () => {
+  test('with teleport and nested fragment', async () => {
     const state = reactive({ color: 'red' })
     const target = document.createElement('div')
     document.body.appendChild(target)
@@ -517,4 +517,36 @@ describe('useVaporCssVars', () => {
 
     expect(root.innerHTML).toBe(`<!--for-->`)
   })
+
+  test('with v-if initial false then update css vars', async () => {
+    const state = reactive({ color: 'red' })
+    const root = document.createElement('div')
+    const toggle = ref(false)
+
+    define({
+      setup() {
+        useVaporCssVars(() => state)
+        return createIf(
+          () => toggle.value,
+          () => template('<div></div>')(),
+        )
+      },
+    }).render({}, root)
+
+    await nextTick()
+    expect(root.children.length).toBe(0)
+
+    // toggle v-if to true
+    toggle.value = true
+    await nextTick()
+    expect(root.children.length).toBe(1)
+    let el = root.children[0] as HTMLElement
+    expect(el.style.getPropertyValue(`--color`)).toBe('red')
+
+    // update css vars
+    state.color = 'green'
+    await nextTick()
+    el = root.children[0] as HTMLElement
+    expect(el.style.getPropertyValue(`--color`)).toBe('green')
+  })
 })