Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cssVars): cssVars work with Teleport #4609

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/runtime-core/src/components/Teleport.ts
Expand Up @@ -28,7 +28,7 @@ const isTeleportDisabled = (props: VNode['props']): boolean =>
const isTargetSVG = (target: RendererElement): boolean =>
typeof SVGElement !== 'undefined' && target instanceof SVGElement

const resolveTarget = <T = RendererElement>(
export const resolveTarget = <T = RendererElement>(
props: TeleportProps | null,
select: RendererOptions['querySelector']
): T | null => {
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-core/src/index.ts
Expand Up @@ -86,7 +86,7 @@ export { createVNode, cloneVNode, mergeProps, isVNode } from './vnode'
// VNode types
export { Fragment, Text, Comment, Static } from './vnode'
// Built-in components
export { Teleport, TeleportProps } from './components/Teleport'
export { Teleport, TeleportProps, resolveTarget } from './components/Teleport'
export { Suspense, SuspenseProps } from './components/Suspense'
export { KeepAlive, KeepAliveProps } from './components/KeepAlive'
export {
Expand Down
53 changes: 53 additions & 0 deletions packages/runtime-dom/__tests__/helpers/useCssVars.spec.ts
Expand Up @@ -8,6 +8,7 @@ import {
nextTick,
ComponentOptions,
Suspense,
Teleport,
FunctionalComponent
} from '@vue/runtime-dom'

Expand Down Expand Up @@ -196,4 +197,56 @@ describe('useCssVars', () => {
expect((c as HTMLElement).style.getPropertyValue(`--color`)).toBe('red')
}
})

test('with teleport', async () => {
const state = reactive({ color: 'red' })
const root = document.createElement('div')
const target = document.createElement('div')

const App = {
setup() {
useCssVars(() => state)
return () => [h(Teleport, { to: target }, [h('div')])]
}
}

render(h(App), root)
await nextTick()
for (const c of [].slice.call(target.children as any)) {
expect((c as HTMLElement).style.getPropertyValue(`--color`)).toBe('red')
}
})

test('with teleport(change subTree)', async () => {
const state = reactive({ color: 'red' })
const root = document.createElement('div')
const target = document.body
const toggle = ref(false)

const App = {
setup() {
useCssVars(() => state)
return () => [
h(Teleport, { to: target }, [
h('div'),
toggle.value ? h('div') : null
])
]
}
}

render(h(App), root)
await nextTick()
expect(target.children.length).toBe(1)
for (const c of [].slice.call(target.children as any)) {
expect((c as HTMLElement).style.getPropertyValue(`--color`)).toBe('red')
}

toggle.value = true
await nextTick()
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')
}
})
})
51 changes: 45 additions & 6 deletions packages/runtime-dom/src/helpers/useCssVars.ts
Expand Up @@ -6,9 +6,12 @@ import {
Static,
watchPostEffect,
onMounted,
onUnmounted
onUnmounted,
resolveTarget,
TeleportProps
} from '@vue/runtime-core'
import { ShapeFlags } from '@vue/shared'
import { ShapeFlags, isArray } from '@vue/shared'
import { nodeOps } from '../nodeOps'

/**
* Runtime helper for SFC's CSS variable injection feature.
Expand All @@ -28,11 +31,47 @@ export function useCssVars(getter: (ctx: any) => Record<string, string>) {
const setVars = () =>
setVarsOnVNode(instance.subTree, getter(instance.proxy!))
watchPostEffect(setVars)

onMounted(() => {
const ob = new MutationObserver(setVars)
ob.observe(instance.subTree.el!.parentNode, { childList: true })
onUnmounted(() => ob.disconnect())
const obs = [onSubTreeChange(instance.subTree.el!.parentNode, setVars)]

const observeTeleportTarget = (vnode: VNode) => {
if (vnode.shapeFlag & ShapeFlags.TELEPORT) {
const target = resolveTarget(
vnode.props as TeleportProps,
nodeOps.querySelector
) as Node
if (target) {
obs.push(
onSubTreeChange(target, (node: Node) => {
setVarsOnNode(node, getter(instance.proxy!))
})
)
}
}
if (isArray(vnode.children)) {
vnode.children.forEach(n => observeTeleportTarget(n as VNode))
}
}
observeTeleportTarget(instance.subTree)

onUnmounted(() => {
obs.forEach(ob => ob.disconnect())
})
})
}

function onSubTreeChange(
target: Node,
cb: (...args: Node[]) => void
): MutationObserver {
const ob = new MutationObserver((mutations: MutationRecord[]) => {
mutations.forEach((mutation: MutationRecord) => {
mutation.addedNodes.forEach((node: Node) => cb(node))
})
})
ob.observe(target, { childList: true })
return ob
}

function setVarsOnVNode(vnode: VNode, vars: Record<string, string>) {
Expand All @@ -53,7 +92,7 @@ function setVarsOnVNode(vnode: VNode, vars: Record<string, string>) {

if (vnode.shapeFlag & ShapeFlags.ELEMENT && vnode.el) {
setVarsOnNode(vnode.el as Node, vars)
} else if (vnode.type === Fragment) {
} else if (vnode.type === Fragment || vnode.shapeFlag & ShapeFlags.TELEPORT) {
;(vnode.children as VNode[]).forEach(c => setVarsOnVNode(c, vars))
} else if (vnode.type === Static) {
let { el, anchor } = vnode
Expand Down