[READ-ONLY] a fast, modern browser for the npm registry
at main 89 lines 3.2 kB view raw
1import { computed, type ComputedRef, type Ref, unref } from 'vue' 2import { useMutationObserver, useResizeObserver, useSupported } from '@vueuse/core' 3 4type CssVariableSource = HTMLElement | null | undefined | Ref<HTMLElement | null | undefined> 5 6type UseCssVariableOptions = { 7 element?: CssVariableSource 8 watchResize?: boolean 9 watchHtmlAttributes?: boolean 10} 11 12function readCssVariable(element: HTMLElement, variableName: string): string { 13 return getComputedStyle(element).getPropertyValue(variableName).trim() 14} 15 16function toCamelCase(cssVariable: string): string { 17 return cssVariable.replace(/^--/, '').replace(/-([a-z0-9])/gi, (_, c) => c.toUpperCase()) 18} 19 20function resolveElement(element?: CssVariableSource): HTMLElement | null { 21 if (typeof window === 'undefined' || typeof document === 'undefined') return null 22 if (!element) return document.documentElement 23 const resolved = unref(element) 24 return resolved ?? document.documentElement 25} 26 27/** 28 * Read multiple CSS custom properties at once and expose them as a reactive object. 29 * 30 * Each CSS variable name is normalized into a camelCase key: 31 * - Leading `--` is removed 32 * - kebab-case is converted to camelCase 33 * 34 * Example: 35 * ```ts 36 * useCssVariables(['--bg', '--fg-subtle']) 37 * // => colors.value = { bg: '...', fgSubtle: '...' } 38 * ``` 39 * 40 * The returned values are always resolved via `getComputedStyle`, meaning the 41 * effective value is returned (after cascade, theme classes, etc.). 42 * 43 * Reactivity behavior: 44 * - Updates automatically when the observed element changes 45 * - Can react to theme toggles via `watchHtmlAttributes` 46 * - Can react to responsive CSS variables via `watchResize` 47 * 48 * @param variables - List of CSS variable names (must include the leading `--`) 49 * @param options - Configuration options 50 * @param options.element - Element to read variables from (defaults to `:root`) 51 * @param options.watchResize - Re-evaluate values on resize (useful for media-query-driven variables) 52 * @param options.watchHtmlAttributes - Re-evaluate values when `<html>` attributes change 53 * 54 * @returns An object containing a reactive `colors` map, keyed by camelCase names 55 */ 56export function useCssVariables( 57 variables: readonly string[], 58 options: UseCssVariableOptions = {}, 59): { colors: ComputedRef<Record<string, string>> } { 60 const isClientSupported = useSupported( 61 () => typeof window !== 'undefined' && typeof document !== 'undefined', 62 ) 63 64 const elementComputed = computed(() => resolveElement(options.element)) 65 66 const colors = computed<Record<string, string>>(() => { 67 const element = elementComputed.value 68 if (!element) return {} 69 70 const result: Record<string, string> = {} 71 for (const variable of variables) { 72 result[toCamelCase(variable)] = readCssVariable(element, variable) 73 } 74 return result 75 }) 76 77 if (options.watchResize) { 78 useResizeObserver(elementComputed, () => void colors.value) 79 } 80 81 if (options.watchHtmlAttributes && isClientSupported.value) { 82 useMutationObserver(document.documentElement, () => void colors.value, { 83 attributes: true, 84 attributeFilter: ['class', 'style', 'data-theme', 'data-bg-theme'], 85 }) 86 } 87 88 return { colors } 89}