css-variables-conversion.mjs 1.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142
  1. import { invariant } from 'motion-utils';
  2. import { isNumericalString } from '../../../utils/is-numerical-string.mjs';
  3. import { isCSSVariableToken } from './is-css-variable.mjs';
  4. /**
  5. * Parse Framer's special CSS variable format into a CSS token and a fallback.
  6. *
  7. * ```
  8. * `var(--foo, #fff)` => [`--foo`, '#fff']
  9. * ```
  10. *
  11. * @param current
  12. */
  13. const splitCSSVariableRegex =
  14. // eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive, as it can match a lot of words
  15. /^var\(--(?:([\w-]+)|([\w-]+), ?([a-zA-Z\d ()%#.,-]+))\)/u;
  16. function parseCSSVariable(current) {
  17. const match = splitCSSVariableRegex.exec(current);
  18. if (!match)
  19. return [,];
  20. const [, token1, token2, fallback] = match;
  21. return [`--${token1 ?? token2}`, fallback];
  22. }
  23. const maxDepth = 4;
  24. function getVariableValue(current, element, depth = 1) {
  25. invariant(depth <= maxDepth, `Max CSS variable fallback depth detected in property "${current}". This may indicate a circular fallback dependency.`);
  26. const [token, fallback] = parseCSSVariable(current);
  27. // No CSS variable detected
  28. if (!token)
  29. return;
  30. // Attempt to read this CSS variable off the element
  31. const resolved = window.getComputedStyle(element).getPropertyValue(token);
  32. if (resolved) {
  33. const trimmed = resolved.trim();
  34. return isNumericalString(trimmed) ? parseFloat(trimmed) : trimmed;
  35. }
  36. return isCSSVariableToken(fallback)
  37. ? getVariableValue(fallback, element, depth + 1)
  38. : fallback;
  39. }
  40. export { getVariableValue, parseCSSVariable };