All files / src/render/dom/utils css-variables-conversion.ts

86% Statements 43/50
56.67% Branches 17/30
100% Functions 5/5
92.11% Lines 35/38

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105  35x       551x                       35x   35x 7x 7x   7x 7x     35x       4x   2x         2x     2x     2x   2x 2x                           35x   249x     249x 249x       248x 7x       248x 282x 282x   1x 1x         248x 269x 269x   1x 1x     1x         1x     248x    
import { Target, TargetWithKeyframes } from "../../../types"
import { invariant } from "hey-listen"
import { VisualElement } from "../../types"
 
function isCSSVariable(value: any): value is string {
    return typeof value === "string" && value.startsWith("var(--")
}
 
/**
 * Parse Framer's special CSS variable format into a CSS token and a fallback.
 *
 * ```
 * `var(--foo, #fff)` => [`--foo`, '#fff']
 * ```
 *
 * @param current
 */
export const cssVariableRegex =
    /var\((--[a-zA-Z0-9-_]+),? ?([a-zA-Z0-9 ()%#.,-]+)?\)/
export function parseCSSVariable(current: string) {
    const match = cssVariableRegex.exec(current)
    Iif (!match) return [,]
 
    const [, token, fallback] = match
    return [token, fallback]
}
 
const maxDepth = 4
function getVariableValue(
    current: string,
    element: Element,
    Edepth = 1
): string | undefined {
    invariant(
        depth <= maxDepth,
        `Max CSS variable fallback depth detected in property "${current}". This may indicate a circular fallback dependency.`
    )
 
    const [token, fallback] = parseCSSVariable(current)
 
    // No CSS variable detected
    Iif (!token) return
 
    // Attempt to read this CSS variable off the element
    const resolved = window.getComputedStyle(element).getPropertyValue(token)
 
    Eif (resolved) {
        return resolved.trim()
    } else if (isCSSVariable(fallback)) {
        // The fallback might itself be a CSS variable, in which case we attempt to resolve it too.
        return getVariableValue(fallback, element, depth + 1)
    } else {
        return fallback
    }
}
 
/**
 * Resolve CSS variables from
 *
 * @internal
 */
export function resolveCSSVariables(
    visualElement: VisualElement,
    { ...target }: TargetWithKeyframes,
    transitionEnd: Target | undefined
): { target: TargetWithKeyframes; transitionEnd?: Target } {
    const element = visualElement.getInstance()
    if (!(element instanceof Element)) return { target, transitionEnd }
 
    // If `transitionEnd` isn't `undefined`, clone it. We could clone `target` and `transitionEnd`
    // only if they change but I think this reads clearer and this isn't a performance-critical path.
    if (transitionEnd) {
        transitionEnd = { ...transitionEnd }
    }
 
    // Go through existing `MotionValue`s and ensure any existing CSS variables are resolved
    visualElement.forEachValue((value) => {
        const current = value.get()
        if (!isCSSVariable(current)) return
 
        const resolved = getVariableValue(current, element)
        Eif (resolved) value.set(resolved)
    })
 
    // Cycle through every target property and resolve CSS variables. Currently
    // we only read single-var properties like `var(--foo)`, not `calc(var(--foo) + 20px)`
    for (const key in target) {
        const current = target[key]
        if (!isCSSVariable(current)) continue
 
        const resolved = getVariableValue(current, element)
        Iif (!resolved) continue
 
        // Clone target if it hasn't already been
        target[key] = resolved
 
        // If the user hasn't already set this key on `transitionEnd`, set it to the unresolved
        // CSS variable. This will ensure that after the animation the component will reflect
        // changes in the value of the CSS variable.
        Iif (transitionEnd) transitionEnd[key] ??= current
    }
 
    return { target, transitionEnd }
}