Skip to content

fix(useCssVar): avoid untarget style pollution #4841

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions packages/core/useCssVar/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,153 @@ describe('useCssVar', () => {
expect(vm.variable).toBe('red')
expect(vm.el?.style.getPropertyValue('--color')).toBe('red')
})

it('should handle ref target changing from element to undefined', async () => {
const vm = mount(defineComponent({
setup() {
const el = useTemplateRef<HTMLDivElement>('el')
const target = shallowRef<HTMLDivElement | null | undefined>(undefined)

const color = '--test-color'
const variable = useCssVar(color, target, { initialValue: 'green' })

function setTarget() {
target.value = el.value
}

function clearTarget() {
target.value = undefined
}

return {
el,
target,
variable,
setTarget,
clearTarget,
}
},
render() {
return h('div', { ref: 'el' })
},
}))

await nextTick()

// Initially target is undefined, should use documentElement
expect(vm.variable).toBe('green')
expect(document.documentElement.style.getPropertyValue('--test-color')).toBe('green')

// Set target to actual element
vm.setTarget()
await nextTick()

// Should now use the element and remove from documentElement
expect(vm.el?.style.getPropertyValue('--test-color')).toBe('green')
expect(document.documentElement.style.getPropertyValue('--test-color')).toBe('')

// Clear target back to undefined
vm.clearTarget()
await nextTick()

// Should NOT go back to documentElement since target had a value before
expect(vm.el?.style.getPropertyValue('--test-color')).toBe('')
expect(document.documentElement.style.getPropertyValue('--test-color')).toBe('')

// Clean up
document.documentElement.style.removeProperty('--test-color')
})

it('should not use documentElement when target is initially null', async () => {
const vm = mount(defineComponent({
setup() {
const target = shallowRef<HTMLDivElement | null>(null)
const color = '--test-null-color'
const variable = useCssVar(color, target, { initialValue: 'purple' })

function setVariable(value: string) {
variable.value = value
}

return {
target,
variable,
setVariable,
}
},
render() {
return h('div')
},
}))

await nextTick()

// When target is initially null, should NOT use documentElement
expect(vm.variable).toBe('purple')
expect(document.documentElement.style.getPropertyValue('--test-null-color')).toBe('')

// Set variable value (should not be applied to any element)
vm.setVariable('orange')
await nextTick()
expect(document.documentElement.style.getPropertyValue('--test-null-color')).toBe('')

// Clean up
document.documentElement.style.removeProperty('--test-null-color')
})

it('should handle ref target changing from element to null', async () => {
const vm = mount(defineComponent({
setup() {
const el = useTemplateRef<HTMLDivElement>('el')
const target = shallowRef<HTMLDivElement | null>(null)

const color = '--test-null-transition-color'
const variable = useCssVar(color, target, { initialValue: 'yellow' })

function setTarget() {
target.value = el.value
}

function clearTargetToNull() {
target.value = null
}

return {
el,
target,
variable,
setTarget,
clearTargetToNull,
}
},
render() {
return h('div', { ref: 'el' })
},
}))

await nextTick()

// Initially target is null, should NOT use documentElement
expect(vm.variable).toBe('yellow')
expect(document.documentElement.style.getPropertyValue('--test-null-transition-color')).toBe('')

// Set target to actual element
vm.setTarget()
await nextTick()

// Should now use the element
expect(vm.el?.style.getPropertyValue('--test-null-transition-color')).toBe('yellow')
expect(document.documentElement.style.getPropertyValue('--test-null-transition-color')).toBe('')

// Clear target back to null
vm.clearTargetToNull()
await nextTick()

// Should NOT go back to documentElement since target had a value before
expect(vm.el?.style.getPropertyValue('--test-null-transition-color')).toBe('')
expect(document.documentElement.style.getPropertyValue('--test-null-transition-color')).toBe('')

// Clean up
document.documentElement.style.removeProperty('--test-null-transition-color')
})
})
30 changes: 29 additions & 1 deletion packages/core/useCssVar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,38 @@ export function useCssVar(
) {
const { window = defaultWindow, initialValue, observe = false } = options
const variable = shallowRef(initialValue)
const elRef = computed(() => unrefElement(target) || window?.document?.documentElement)

// Track if target has ever had a truthy value
let targetHadValue = false
// Track the initial target value to distinguish undefined from null
const initialTargetValue = toValue(target)

const elRef = computed(() => {
const element = unrefElement(target)

if (element) {
targetHadValue = true
return element
}

// If target had a value before, don't use fallback
if (targetHadValue) {
return null
}

// If initial target was explicitly null, don't use documentElement
if (initialTargetValue === null) {
return null
}

// If initial target was undefined, use documentElement as fallback
return window?.document?.documentElement
})

function updateCssVar() {
const key = toValue(prop)
const el = toValue(elRef)
// Only update CSS variable if we have a valid element
if (el && window && key) {
const value = window.getComputedStyle(el).getPropertyValue(key)?.trim()
variable.value = value || variable.value || initialValue
Expand Down Expand Up @@ -62,6 +89,7 @@ export function useCssVar(
[variable, elRef],
([val, el]) => {
const raw_prop = toValue(prop)
// Only set CSS property if we have a valid element
if (el?.style && raw_prop) {
if (val == null)
el.style.removeProperty(raw_prop)
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy