All files / runtime-dom/src/helpers useCssVars.ts

100% Statements 70/70
92.85% Branches 26/28
100% Functions 6/6
100% Lines 70/70

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 1032x                   2x 2x   2x         2x 18x   18x                 18x 31x 31x 31x 31x   18x 18x 18x   18x 26x 26x 1x 26x 25x 25x 26x 26x   18x 18x 18x   18x 18x 18x 18x 18x 18x   47x 47x 3x 3x 3x 1x 1x 1x 1x 3x     47x 10x 10x   47x 30x 47x 13x 17x 1x 1x 2x 2x 1x 1x 1x 47x   41x 41x 41x 41x 41x 41x 41x 41x 41x 41x 41x  
import {
  Fragment,
  Static,
  type VNode,
  getCurrentInstance,
  onBeforeMount,
  onMounted,
  onUnmounted,
  warn,
  watchPostEffect,
} from '@vue/runtime-core'
import { ShapeFlags } from '@vue/shared'
 
export const CSS_VAR_TEXT: unique symbol = Symbol(__DEV__ ? 'CSS_VAR_TEXT' : '')
/**
 * Runtime helper for SFC's CSS variable injection feature.
 * @private
 */
export function useCssVars(getter: (ctx: any) => Record<string, string>): void {
  if (!__BROWSER__ && !__TEST__) return
 
  const instance = getCurrentInstance()
  /* v8 ignore start */
  if (!instance) {
    __DEV__ &&
      warn(`useCssVars is called without current active component instance.`)
    return
  }
  /* v8 ignore stop */
 
  const updateTeleports = (instance.ut = (vars = getter(instance.proxy)) => {
    Array.from(
      document.querySelectorAll(`[data-v-owner="${instance.uid}"]`),
    ).forEach(node => setVarsOnNode(node, vars))
  })
 
  if (__DEV__) {
    instance.getCssVars = () => getter(instance.proxy)
  }
 
  const setVars = () => {
    const vars = getter(instance.proxy)
    if (instance.ce) {
      setVarsOnNode(instance.ce as any, vars)
    } else {
      setVarsOnVNode(instance.subTree, vars)
    }
    updateTeleports(vars)
  }
 
  onBeforeMount(() => {
    watchPostEffect(setVars)
  })
 
  onMounted(() => {
    const ob = new MutationObserver(setVars)
    ob.observe(instance.subTree.el!.parentNode, { childList: true })
    onUnmounted(() => ob.disconnect())
  })
}
 
function setVarsOnVNode(vnode: VNode, vars: Record<string, string>) {
  if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
    const suspense = vnode.suspense!
    vnode = suspense.activeBranch!
    if (suspense.pendingBranch && !suspense.isHydrating) {
      suspense.effects.push(() => {
        setVarsOnVNode(suspense.activeBranch!, vars)
      })
    }
  }
 
  // drill down HOCs until it's a non-component vnode
  while (vnode.component) {
    vnode = vnode.component.subTree
  }
 
  if (vnode.shapeFlag & ShapeFlags.ELEMENT && vnode.el) {
    setVarsOnNode(vnode.el as Node, vars)
  } else if (vnode.type === Fragment) {
    ;(vnode.children as VNode[]).forEach(c => setVarsOnVNode(c, vars))
  } else if (vnode.type === Static) {
    let { el, anchor } = vnode
    while (el) {
      setVarsOnNode(el as Node, vars)
      if (el === anchor) break
      el = el.nextSibling
    }
  }
}
 
function setVarsOnNode(el: Node, vars: Record<string, string>) {
  if (el.nodeType === 1) {
    const style = (el as HTMLElement).style
    let cssText = ''
    for (const key in vars) {
      style.setProperty(`--${key}`, vars[key])
      cssText += `--${key}: ${vars[key]};`
    }
    ;(style as any)[CSS_VAR_TEXT] = cssText
  }
}