useCssVars.ts 2.4 KB

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