Skip to content

Commit c1282ff

Browse files
authored
feat(useCssSupports): add useCssSupports (#5266)
1 parent 7c94afb commit c1282ff

File tree

6 files changed

+143
-0
lines changed

6 files changed

+143
-0
lines changed

packages/core/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export * from './useCloned'
2727
export * from './useColorMode'
2828
export * from './useConfirmDialog'
2929
export * from './useCountdown'
30+
export * from './useCssSupports'
3031
export * from './useCssVar'
3132
export * from './useCurrentElement'
3233
export * from './useCycleList'
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script setup lang="ts">
2+
import { useCssSupports } from '@vueuse/core'
3+
import { shallowRef } from 'vue'
4+
5+
const conditionText = shallowRef('display: flex')
6+
const prop = shallowRef('container-type')
7+
const value = shallowRef('scroll-state')
8+
9+
const { isSupported: conditionTextSupported } = useCssSupports(conditionText)
10+
const { isSupported: propValueSupported } = useCssSupports(prop, value)
11+
</script>
12+
13+
<template>
14+
<div style="display: flex; flex-direction: column;">
15+
<input v-model="conditionText" name="condition" type="text">
16+
<span><code>useCssSupports("{{ conditionText }}")</code>: {{ conditionTextSupported }}</span>
17+
<hr>
18+
<input v-model="prop" name="prop" type="text"><input v-model="value" name="value" type="text">
19+
<span><code>useCssSupports("{{ prop }}", "{{ value }}")</code>: {{ propValueSupported }}</span>
20+
</div>
21+
</template>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { useCssSupports } from '@vueuse/core'
2+
import { describe, expect, it } from 'vitest'
3+
import { shallowRef } from 'vue'
4+
5+
describe('useCssSupports', () => {
6+
it('should be defined', () => {
7+
expect(useCssSupports).toBeDefined()
8+
})
9+
10+
it('should correctly support existing features', () => {
11+
const { isSupported: textDecoration } = useCssSupports('text-decoration-style', 'blink')
12+
const { isSupported: transformOrigin } = useCssSupports('transform-origin', '5%')
13+
const { isSupported: flex } = useCssSupports('display: flex')
14+
const { isSupported: variable } = useCssSupports('(--foo: red)')
15+
const { isSupported: selectorHas } = useCssSupports('selector(:has(a))')
16+
const { isSupported: query } = useCssSupports('(transform-style: preserve) or (-moz-transform-style: preserve) or (-webkit-transform-style: preserve)')
17+
const { isSupported: doesNotExists } = useCssSupports('doesNotExist')
18+
19+
expect(textDecoration.value).toBe(false)
20+
expect(transformOrigin.value).toBe(true)
21+
expect(flex.value).toBe(true)
22+
expect(variable.value).toBe(true)
23+
expect(selectorHas.value).toBe(true)
24+
expect(query.value).toBe(false)
25+
expect(doesNotExists.value).toBe(false)
26+
})
27+
28+
it('should reactively update if condition, prop or value changes', () => {
29+
const condition = shallowRef('display: flex')
30+
const { isSupported } = useCssSupports(condition)
31+
expect(isSupported.value).toBe(true)
32+
condition.value = 'e18e'
33+
expect(isSupported.value).toBe(false)
34+
const prop = shallowRef('transform-origin')
35+
const value = shallowRef('5%')
36+
const { isSupported: variable } = useCssSupports(prop, value)
37+
expect(variable.value).toBe(true)
38+
value.value = 'e18e'
39+
expect(variable.value).toBe(false)
40+
value.value = '5%'
41+
expect(variable.value).toBe(true)
42+
prop.value = 'e18e'
43+
expect(variable.value).toBe(false)
44+
})
45+
46+
it('should not treat conditionText as prop when options is set and value is undefined', () => {
47+
expect(useCssSupports('display: flex').isSupported.value).toBe(true)
48+
expect(useCssSupports('display: flex', {}).isSupported.value).toBe(true)
49+
})
50+
51+
it('should use prop + value instead of condition if value is explicitly undefined', () => {
52+
expect(useCssSupports('display: flex').isSupported.value).toBe(true)
53+
expect(useCssSupports('display: flex', undefined).isSupported.value).toBe(false)
54+
// @ts-expect-error overload catches this issue correctly
55+
expect(useCssSupports('display: flex', shallowRef(undefined)).isSupported.value).toBe(false)
56+
})
57+
})
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
category: Browser
3+
---
4+
5+
# useCssSupports
6+
7+
SSR compatible and reactive [`CSS.supports`](https://developer.mozilla.org/docs/Web/API/CSS/supports_static).
8+
9+
## Usage
10+
11+
```ts
12+
import { useCssSupports } from '@vueuse/core'
13+
14+
const { isSupported } = useCssSupports('container-type', 'scroll-state')
15+
```
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { ComputedRef, MaybeRefOrGetter } from 'vue'
2+
import type { ConfigurableWindow } from '../_configurable'
3+
import { isClient } from '@vueuse/shared'
4+
import { computed, toValue } from 'vue'
5+
import { defaultWindow } from '../_configurable'
6+
import { useMounted } from '../useMounted'
7+
8+
export interface UseCssSupportsOptions extends ConfigurableWindow {
9+
ssrValue?: boolean
10+
}
11+
12+
export interface UseCssSupportsReturn {
13+
isSupported: ComputedRef<boolean>
14+
}
15+
16+
export function useCssSupports(property: MaybeRefOrGetter<string>, value: MaybeRefOrGetter<string>, options?: UseCssSupportsOptions): UseCssSupportsReturn
17+
export function useCssSupports(conditionText: MaybeRefOrGetter<string>, options?: UseCssSupportsOptions): UseCssSupportsReturn
18+
export function useCssSupports(...args: any[]): UseCssSupportsReturn {
19+
let options: UseCssSupportsOptions = {}
20+
21+
if (typeof toValue(args.at(-1)) === 'object') {
22+
options = args.pop()
23+
}
24+
25+
const [prop, value] = args
26+
27+
const { window = defaultWindow, ssrValue = false } = options
28+
29+
const isMounted = useMounted()
30+
31+
return {
32+
isSupported: computed(() => {
33+
// to trigger the ref
34+
// eslint-disable-next-line ts/no-unused-expressions
35+
isMounted.value
36+
37+
if (!isClient) {
38+
return ssrValue
39+
}
40+
41+
return args.length === 2
42+
// @ts-expect-error window type is not correct
43+
? window?.CSS.supports(toValue(prop), toValue(value))
44+
// @ts-expect-error window type is not correct
45+
: window?.CSS.supports(toValue(prop))
46+
}),
47+
}
48+
}

test/exports/core.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@
156156
useConfirmDialog: function
157157
useCountdown: function
158158
useCounter: function
159+
useCssSupports: function
159160
useCssVar: function
160161
useCurrentElement: function
161162
useCycleList: function

0 commit comments

Comments
 (0)