fix(core): CSS animation parsing (#10245)

This commit is contained in:
Igor Randjelovic
2023-03-22 22:16:15 +01:00
committed by GitHub
parent 5f96ffe7d9
commit ab436dbfe6
6 changed files with 924 additions and 123 deletions

View File

@ -1,5 +1,7 @@
// @ts-nocheck
global.WeakRef.prototype.get = global.WeakRef.prototype.deref;
global.NativeClass = function () {};
global.NSObject = class NSObject {};
global.NSString = {
stringWithString() {
return {

View File

@ -0,0 +1,523 @@
import { CoreTypes } from '../../core-types';
import type { KeyframeAnimationInfo, KeyframeInfo } from '../animation';
import { CssAnimationParser, keyframeAnimationsFromCSSProperty } from './css-animation-parser';
import { cssTreeParse } from '../../css/css-tree-parser';
describe('css-animation-parser', () => {
describe('shorthand-property-parser', () => {
// helper functions
function testSingleAnimation(css: string): KeyframeAnimationInfo {
const animations: KeyframeAnimationInfo[] = [];
keyframeAnimationsFromCSSProperty(css, animations);
return animations[0];
}
function testMultipleAnimations(css: string): KeyframeAnimationInfo[] {
const animations: KeyframeAnimationInfo[] = [];
keyframeAnimationsFromCSSProperty(css, animations);
return animations;
}
it('empty', () => {
const animation = testSingleAnimation('');
expect(animation).toBeUndefined();
});
// times to test for
const times = {
'0s': 0,
'0ms': 0,
'250ms': 250,
'0.5s': 500,
'1500ms': 1500,
'1s': 1000,
'3s': 3000,
};
const curves = {
ease: CoreTypes.AnimationCurve.ease,
linear: CoreTypes.AnimationCurve.linear,
'ease-in': CoreTypes.AnimationCurve.easeIn,
'ease-out': CoreTypes.AnimationCurve.easeOut,
'ease-in-out': CoreTypes.AnimationCurve.easeInOut,
spring: CoreTypes.AnimationCurve.spring,
'cubic-bezier(0.1, 1.0, 0.5, 0.5)': CoreTypes.AnimationCurve.cubicBezier(0.1, 1.0, 0.5, 0.5),
'cubic-bezier(0.42, 0.0, 1.0, 1.0);': CoreTypes.AnimationCurve.cubicBezier(0.42, 0.0, 1.0, 1.0),
};
it('parses duration', () => {
Object.entries(times).forEach(([timeString, ms]) => {
expect(testSingleAnimation(`${timeString}`).duration).toBe(ms);
});
});
it('parses delay', () => {
Object.entries(times).forEach(([timeString, ms]) => {
const animation = testSingleAnimation(`0s ${timeString}`);
expect(animation.duration).toBe(0);
expect(animation.delay).toBe(ms);
});
});
it('parses duration and delay', () => {
Object.entries(times).forEach(([timeString, ms]) => {
const animation = testSingleAnimation(`${timeString} ${timeString}`);
expect(animation.duration).toBe(ms);
expect(animation.delay).toBe(ms);
});
});
it('parses curve', () => {
Object.entries(curves).forEach(([curveString, curve]) => {
const animation = testSingleAnimation(`${curveString}`);
expect(animation.curve).toEqual(curve);
});
});
it('parses duration, curve and delay', () => {
Object.entries(curves).forEach(([curveString, curve]) => {
const animation1 = testSingleAnimation(`225ms 300ms ${curveString}`);
expect(animation1.duration).toBe(225);
expect(animation1.delay).toBe(300);
expect(animation1.curve).toEqual(curve);
// curve and delay can be swapped
const animation2 = testSingleAnimation(`225ms ${curveString} 300ms`);
expect(animation2.duration).toBe(225);
expect(animation2.delay).toBe(300);
expect(animation2.curve).toEqual(curve);
});
});
it('parses iteration count', () => {
expect(testSingleAnimation(`0s 0s ease 2`).iterations).toBe(2);
expect(testSingleAnimation(`0s 0s ease 2.5`).iterations).toBe(2.5);
expect(testSingleAnimation(`0s 0s ease infinite`).iterations).toBe(Infinity);
expect(testSingleAnimation(`2`).iterations).toBe(2);
expect(testSingleAnimation(`2.5`).iterations).toBe(2.5);
expect(testSingleAnimation(`infinite`).iterations).toBe(Infinity);
expect(testSingleAnimation(`1s 2`).iterations).toBe(2);
expect(testSingleAnimation(`1s 2.5`).iterations).toBe(2.5);
expect(testSingleAnimation(`1s infinite`).iterations).toBe(Infinity);
expect(testSingleAnimation(`ease 2`).iterations).toBe(2);
expect(testSingleAnimation(`ease 2.5`).iterations).toBe(2.5);
expect(testSingleAnimation(`ease infinite`).iterations).toBe(Infinity);
});
it('parses direction', () => {
expect(testSingleAnimation(`1s`).isReverse).toBe(false);
expect(testSingleAnimation(`1s normal`).isReverse).toBe(false);
expect(testSingleAnimation(`1s reverse`).isReverse).toBe(true);
expect(testSingleAnimation(`1s 1s reverse`).isReverse).toBe(true);
expect(testSingleAnimation(`1s 1s ease reverse`).isReverse).toBe(true);
expect(testSingleAnimation(`1s 1s ease 2 reverse`).isReverse).toBe(true);
expect(testSingleAnimation(`1s 1s ease infinite reverse`).isReverse).toBe(true);
expect(testSingleAnimation(`1s ease reverse`).isReverse).toBe(true);
expect(testSingleAnimation(`1s ease 1s reverse`).isReverse).toBe(true);
expect(testSingleAnimation(`1s ease 1s 2 reverse`).isReverse).toBe(true);
// unsupported values should still work
expect(testSingleAnimation(`1s alternate`).isReverse).toBe(false);
expect(testSingleAnimation(`1s alternate-reverse`).isReverse).toBe(false);
});
it('parses fill-mode', () => {
expect(testSingleAnimation(`1s`).isForwards).toBe(false);
expect(testSingleAnimation(`1s none`).isForwards).toBe(false);
expect(testSingleAnimation(`1s backwards`).isForwards).toBe(false);
expect(testSingleAnimation(`1s both`).isForwards).toBe(true);
expect(testSingleAnimation(`1s forwards`).isForwards).toBe(true);
expect(testSingleAnimation(`1s 1s forwards`).isForwards).toBe(true);
expect(testSingleAnimation(`1s 1s ease forwards`).isForwards).toBe(true);
expect(testSingleAnimation(`1s 1s ease 2 forwards`).isForwards).toBe(true);
expect(testSingleAnimation(`1s 1s ease infinite forwards`).isForwards).toBe(true);
expect(testSingleAnimation(`1s ease forwards`).isForwards).toBe(true);
expect(testSingleAnimation(`1s ease 1s forwards`).isForwards).toBe(true);
});
it('parses play-state', () => {
// TODO: implement play-state?
expect(testSingleAnimation(`1s`)).not.toBeUndefined();
expect(testSingleAnimation(`1s running`)).not.toBeUndefined();
expect(testSingleAnimation(`1s paused`)).not.toBeUndefined();
expect(testSingleAnimation(`1s 1s paused`)).not.toBeUndefined();
expect(testSingleAnimation(`1s 1s ease paused`)).not.toBeUndefined();
expect(testSingleAnimation(`1s 1s ease 2 paused`)).not.toBeUndefined();
expect(testSingleAnimation(`1s 1s ease infinite paused`)).not.toBeUndefined();
expect(testSingleAnimation(`1s ease paused`)).not.toBeUndefined();
expect(testSingleAnimation(`1s ease 1s paused`)).not.toBeUndefined();
});
it('parses animation name', () => {
expect(testSingleAnimation(`1s`).name).toBe('');
expect(testSingleAnimation(`1s fade`).name).toBe('fade');
expect(testSingleAnimation(`1s 'fade'`).name).toBe('fade');
expect(testSingleAnimation(`1s "fade"`).name).toBe('fade');
expect(testSingleAnimation(`1s fade-in`).name).toBe('fade-in');
expect(testSingleAnimation(`1s 'fade-in'`).name).toBe('fade-in');
expect(testSingleAnimation(`1s "fade-in"`).name).toBe('fade-in');
expect(testSingleAnimation(`1s fade_in`).name).toBe('fade_in');
expect(testSingleAnimation(`1s 'fade_in'`).name).toBe('fade_in');
expect(testSingleAnimation(`1s "fade_in"`).name).toBe('fade_in');
});
it('parses MDN example: 3s ease-in 1s 2 reverse both paused slidein', () => {
const animation = testSingleAnimation(`3s ease-in 1s 2 reverse both paused slidein`);
expect(animation.duration).toBe(3000);
expect(animation.delay).toBe(1000);
expect(animation.curve).toBe(CoreTypes.AnimationCurve.easeIn);
expect(animation.iterations).toBe(2);
expect(animation.isReverse).toBe(true);
expect(animation.isForwards).toBe(true);
expect(animation.name).toBe('slidein');
});
it('parses MDN example: 3s linear 1s slidein', () => {
const animation = testSingleAnimation(`3s linear 1s slidein`);
expect(animation.duration).toBe(3000);
expect(animation.delay).toBe(1000);
expect(animation.curve).toBe(CoreTypes.AnimationCurve.linear);
expect(animation.name).toBe('slidein');
});
it('parses MDN example: 3s linear slidein, 3s ease-out 5s slideout', () => {
const [animation1, animation2] = testMultipleAnimations(`3s linear slidein, 3s ease-out 5s slideout`);
expect(animation1.duration).toBe(3000);
expect(animation1.curve).toBe(CoreTypes.AnimationCurve.linear);
expect(animation1.name).toBe('slidein');
expect(animation2.duration).toBe(3000);
expect(animation2.delay).toBe(5000);
expect(animation2.curve).toBe(CoreTypes.AnimationCurve.easeOut);
expect(animation2.name).toBe('slideout');
});
it('parses SPEC example: 3s none backwards', () => {
const animation = testSingleAnimation(`3s none backwards`);
expect(animation.duration).toBe(3000);
expect(animation.isForwards).toBe(false);
expect(animation.name).toBe('backwards');
});
it('parses SPEC example: 3s backwards', () => {
const animation = testSingleAnimation(`3s backwards`);
expect(animation.duration).toBe(3000);
expect(animation.isForwards).toBe(false);
expect(animation.name).toBe('');
});
it('does not throw on invalid values', () => {
// prettier-ignore
const invalidValues = [
'asd',
'$#-1401;lk',
'1 1 1 1 1 1 1 1 1 1',
'1s 1s 1s 1s',
',,,,',
'$,1s-_1:s>',
Infinity.toString(),
NaN.toString(),
null,
undefined
];
invalidValues.forEach((value) => {
expect(() => testSingleAnimation(value)).not.toThrow();
});
});
});
describe('keyframe-parser', () => {
// helper function
function testKeyframesArrayFromCSS(css: string, expectedName?: string): KeyframeInfo[] {
const ast = cssTreeParse(css, 'test.css');
const rules = ast.stylesheet.rules;
const firstRule = rules[0];
expect(rules.length).toBe(1);
expect(firstRule.type).toBe('keyframes');
const name = firstRule.name;
const keyframes = firstRule.keyframes;
if (expectedName) {
expect(name).toBe(expectedName);
}
return CssAnimationParser.keyframesArrayFromCSS(keyframes);
}
it('parses "from" keyframes', () => {
const res = testKeyframesArrayFromCSS(
`@keyframes fade {
from { opacity: 0; }
}`,
'fade'
);
expect(res.length).toBe(1);
const [from] = res;
expect(from.duration).toBe(0);
expect(from.declarations.length).toBe(1);
expect(from.declarations[0].property).toBe('opacity');
expect(from.declarations[0].value).toBe(0);
});
it('parses "to" keyframes', () => {
const res = testKeyframesArrayFromCSS(
`@keyframes fade {
to { opacity: 1; }
}`,
'fade'
);
expect(res.length).toBe(1);
const [to] = res;
expect(to.duration).toBe(1);
expect(to.declarations.length).toBe(1);
expect(to.declarations[0].property).toBe('opacity');
expect(to.declarations[0].value).toBe(1);
});
it('parses "from/to" keyframes', () => {
const res = testKeyframesArrayFromCSS(
`@keyframes fade {
from { opacity: 0; }
to { opacity: 1; }
}`,
'fade'
);
expect(res.length).toBe(2);
const [from, to] = res;
expect(from.duration).toBe(0);
expect(from.declarations.length).toBe(1);
expect(from.declarations[0].property).toBe('opacity');
expect(from.declarations[0].value).toBe(0);
expect(to.duration).toBe(1);
expect(to.declarations.length).toBe(1);
expect(to.declarations[0].property).toBe('opacity');
expect(to.declarations[0].value).toBe(1);
});
it('parses "0%" keyframes', () => {
const res = testKeyframesArrayFromCSS(
`@keyframes fade {
0% { opacity: 0; }
}`,
'fade'
);
expect(res.length).toBe(1);
const [from] = res;
expect(from.duration).toBe(0);
expect(from.declarations.length).toBe(1);
expect(from.declarations[0].property).toBe('opacity');
expect(from.declarations[0].value).toBe(0);
});
it('parses "100%" keyframes', () => {
const res = testKeyframesArrayFromCSS(
`@keyframes fade {
100% { opacity: 1; }
}`,
'fade'
);
expect(res.length).toBe(1);
const [to] = res;
expect(to.duration).toBe(1);
expect(to.declarations.length).toBe(1);
expect(to.declarations[0].property).toBe('opacity');
expect(to.declarations[0].value).toBe(1);
});
it('parses "0%/100%" keyframes', () => {
const res = testKeyframesArrayFromCSS(
`@keyframes fade {
0% { opacity: 0; }
100% { opacity: 1; }
}`,
'fade'
);
expect(res.length).toBe(2);
const [from, to] = res;
expect(from.duration).toBe(0);
expect(from.declarations.length).toBe(1);
expect(from.declarations[0].property).toBe('opacity');
expect(from.declarations[0].value).toBe(0);
expect(to.duration).toBe(1);
expect(to.declarations.length).toBe(1);
expect(to.declarations[0].property).toBe('opacity');
expect(to.declarations[0].value).toBe(1);
});
it('parses "via" keyframes', () => {
const res = testKeyframesArrayFromCSS(
`@keyframes fade {
50% { opacity: 0.5; }
}`,
'fade'
);
expect(res.length).toBe(1);
const [via] = res;
expect(via.duration).toBe(0.5);
expect(via.declarations.length).toBe(1);
expect(via.declarations[0].property).toBe('opacity');
expect(via.declarations[0].value).toBe(0.5);
});
it('parses multiple keyframes', () => {
const res = testKeyframesArrayFromCSS(
`@keyframes fade {
0% { opacity: 0; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}`,
'fade'
);
expect(res.length).toBe(3);
const [from, via, to] = res;
expect(from.duration).toBe(0);
expect(from.declarations.length).toBe(1);
expect(from.declarations[0].property).toBe('opacity');
expect(from.declarations[0].value).toBe(0);
expect(via.duration).toBe(0.5);
expect(via.declarations.length).toBe(1);
expect(via.declarations[0].property).toBe('opacity');
expect(via.declarations[0].value).toBe(0.5);
expect(to.duration).toBe(1);
expect(to.declarations.length).toBe(1);
expect(to.declarations[0].property).toBe('opacity');
expect(to.declarations[0].value).toBe(1);
});
it('parses multiple keyframes with mixed stops', () => {
const res = testKeyframesArrayFromCSS(
`@keyframes fade {
from { opacity: 0; }
50% { opacity: 0.5; }
to { opacity: 1; }
}`,
'fade'
);
expect(res.length).toBe(3);
const [from, via, to] = res;
expect(from.duration).toBe(0);
expect(from.declarations.length).toBe(1);
expect(from.declarations[0].property).toBe('opacity');
expect(from.declarations[0].value).toBe(0);
expect(via.duration).toBe(0.5);
expect(via.declarations.length).toBe(1);
expect(via.declarations[0].property).toBe('opacity');
expect(via.declarations[0].value).toBe(0.5);
expect(to.duration).toBe(1);
expect(to.declarations.length).toBe(1);
expect(to.declarations[0].property).toBe('opacity');
expect(to.declarations[0].value).toBe(1);
});
it('parses duplicate keyframes', () => {
const res = testKeyframesArrayFromCSS(
`@keyframes fade {
0% { opacity: 0; }
50% { opacity: 0.5; }
50% { translateX: 100; }
100% { opacity: 1; }
}`,
'fade'
);
expect(res.length).toBe(3);
const [from, via, to] = res;
expect(from.duration).toBe(0);
expect(from.declarations.length).toBe(1);
expect(from.declarations[0].property).toBe('opacity');
expect(from.declarations[0].value).toBe(0);
expect(via.duration).toBe(0.5);
expect(via.declarations.length).toBe(2);
expect(via.declarations[0].property).toBe('opacity');
expect(via.declarations[0].value).toBe(0.5);
expect(via.declarations[1].property).toBe('translateX');
expect(via.declarations[1].value).toBe(100);
expect(to.duration).toBe(1);
expect(to.declarations.length).toBe(1);
expect(to.declarations[0].property).toBe('opacity');
expect(to.declarations[0].value).toBe(1);
});
it('parses timing functions in keyframes', () => {
const res = testKeyframesArrayFromCSS(
`@keyframes fade {
from { opacity: 0; animation-timing-function: ease-in; }
to { opacity: 1; }
}`,
'fade'
);
expect(res.length).toBe(2);
const [from, to] = res;
expect(from.curve).toBe(CoreTypes.AnimationCurve.easeIn);
expect(to.curve).not.toBeDefined();
});
it('sorts multiple keyframes with mixed order', () => {
const res = testKeyframesArrayFromCSS(
`@keyframes fade {
100% { opacity: 1; }
0% { opacity: 0; }
50% { opacity: 0.5; }
}`,
'fade'
);
expect(res.length).toBe(3);
const [from, via, to] = res;
expect(from.duration).toBe(0);
expect(from.declarations.length).toBe(1);
expect(from.declarations[0].property).toBe('opacity');
expect(from.declarations[0].value).toBe(0);
expect(via.duration).toBe(0.5);
expect(via.declarations.length).toBe(1);
expect(via.declarations[0].property).toBe('opacity');
expect(via.declarations[0].value).toBe(0.5);
expect(to.duration).toBe(1);
expect(to.declarations.length).toBe(1);
expect(to.declarations[0].property).toBe('opacity');
expect(to.declarations[0].value).toBe(1);
});
});
});

View File

@ -7,13 +7,13 @@ import { transformConverter } from '../styling/style-properties';
import { cleanupImportantFlags } from './css-utils';
const ANIMATION_PROPERTY_HANDLERS = Object.freeze({
'animation-name': (info: any, value: any) => (info.name = value),
'animation-name': (info: any, value: any) => (info.name = value.replace(/['"]/g, '')),
'animation-duration': (info: any, value: any) => (info.duration = timeConverter(value)),
'animation-delay': (info: any, value: any) => (info.delay = timeConverter(value)),
'animation-timing-function': (info: any, value: any) => (info.curve = animationTimingFunctionConverter(value)),
'animation-iteration-count': (info: any, value: any) => (info.iterations = value === 'infinite' ? Number.POSITIVE_INFINITY : parseFloat(value)),
'animation-direction': (info: any, value: any) => (info.isReverse = value === 'reverse'),
'animation-fill-mode': (info: any, value: any) => (info.isForwards = value === 'forwards'),
'animation-fill-mode': (info: any, value: any) => (info.isForwards = value === 'forwards' || value === 'both'),
});
export class CssAnimationParser {
@ -65,6 +65,7 @@ export class CssAnimationParser {
if (current === undefined) {
current = <KeyframeInfo>{};
current.duration = time;
current.declarations = [];
parsedKeyframes[time] = current;
}
for (const declaration of keyframe.declarations) {
@ -72,7 +73,7 @@ export class CssAnimationParser {
current.curve = animationTimingFunctionConverter(declaration.value);
}
}
current.declarations = declarations;
current.declarations = current.declarations.concat(declarations);
}
}
const array = [];
@ -87,39 +88,97 @@ export class CssAnimationParser {
}
}
function keyframeAnimationsFromCSSProperty(value: any, animations: KeyframeAnimationInfo[]) {
if (typeof value === 'string') {
const values = value.split(/[,]+/);
for (const parsedValue of values) {
const animationInfo = new KeyframeAnimationInfo();
const arr = (<string>parsedValue).trim().split(/[ ]+/);
/**
* @see https://w3c.github.io/csswg-drafts/css-animations/#propdef-animation
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/animation
* @internal - exported for testing
* @param value
* @param animations
*/
export function keyframeAnimationsFromCSSProperty(value: any, animations: KeyframeAnimationInfo[]) {
if (typeof value !== 'string') {
return;
}
if (arr.length > 0) {
animationInfo.name = arr[0];
}
if (arr.length > 1) {
animationInfo.duration = timeConverter(arr[1]);
}
if (arr.length > 2) {
animationInfo.curve = animationTimingFunctionConverter(arr[2]);
}
if (arr.length > 3) {
animationInfo.delay = timeConverter(arr[3]);
}
if (arr.length > 4) {
animationInfo.iterations = parseInt(arr[4]);
}
if (arr.length > 5) {
animationInfo.isReverse = arr[4] === 'reverse';
}
if (arr.length > 6) {
animationInfo.isForwards = arr[5] === 'forwards';
}
if (arr.length > 7) {
throw new Error('Invalid value for animation: ' + value);
}
animations.push(animationInfo);
if (value.trim().length === 0) {
return;
}
/**
* Matches whitespace except if the whitespace is contained in parenthesis - ex. cubic-bezier(1, 1, 1, 1).
*/
const VALUE_SPLIT_RE = /\s(?![^(]*\))/;
/**
* Matches commas except if the comma is contained in parenthesis - ex. cubic-bezier(1, 1, 1, 1).
*/
const MULTIPLE_SPLIT_RE = /,(?![^(]*\))/;
const isTime = (v: string) => !!v.match(/\dm?s$/g);
const isTimingFunction = (v: string) => !!v.match(/ease|linear|ease-in|ease-out|ease-in-out|spring|cubic-bezier/g);
const isIterationCount = (v: string) => !!v.match(/infinite|[\d.]+$/g);
const isDirection = (v: string) => !!v.match(/normal|reverse|alternate|alternate-reverse/g);
const isFillMode = (v: string) => !!v.match(/none|forwards|backwards|both/g);
const isPlayState = (v: string) => !!v.match(/running|paused/g);
const values = value.split(MULTIPLE_SPLIT_RE);
for (const parsedValue of values) {
const animationInfo = new KeyframeAnimationInfo();
const parts = (<string>parsedValue).trim().split(VALUE_SPLIT_RE);
const [duration, delay] = parts.filter(isTime);
const [timing] = parts.filter(isTimingFunction);
const [iterationCount] = parts.filter(isIterationCount);
const [direction] = parts.filter(isDirection);
const [fillMode] = parts.filter(isFillMode);
const [playState] = parts.filter(isPlayState);
const [name] = parts.filter((v) => {
// filter out "consumed" values
return ![duration, delay, timing, iterationCount, direction, fillMode, playState].filter(Boolean).includes(v);
});
// console.log({
// duration,
// delay,
// timing,
// iterationCount,
// direction,
// fillMode,
// playState,
// name,
// });
if (duration) {
ANIMATION_PROPERTY_HANDLERS['animation-duration'](animationInfo, duration);
}
if (delay) {
ANIMATION_PROPERTY_HANDLERS['animation-delay'](animationInfo, delay);
}
if (timing) {
ANIMATION_PROPERTY_HANDLERS['animation-timing-function'](animationInfo, timing);
}
if (iterationCount) {
ANIMATION_PROPERTY_HANDLERS['animation-iteration-count'](animationInfo, iterationCount);
}
if (direction) {
ANIMATION_PROPERTY_HANDLERS['animation-direction'](animationInfo, direction);
}
if (fillMode) {
ANIMATION_PROPERTY_HANDLERS['animation-fill-mode'](animationInfo, fillMode);
}
if (playState) {
// TODO: implement play state? Currently not supported...
}
if (name) {
ANIMATION_PROPERTY_HANDLERS['animation-name'](animationInfo, name);
} else {
// based on the SPEC we should set the name to 'none' if no name is provided
// however we just don't set the name at all.
// perhaps we should set it to 'none' and handle it accordingly.
// animationInfo.name = 'none'
}
animations.push(animationInfo);
}
}
@ -128,9 +187,9 @@ export function parseKeyframeDeclarations(unparsedKeyframeDeclarations: Keyframe
const property = CssAnimationProperty._getByCssName(unparsedProperty);
unparsedValue = cleanupImportantFlags(unparsedValue, property?.cssLocalName);
if (typeof unparsedProperty === 'string' && property && property._valueConverter) {
if (typeof unparsedProperty === 'string' && property?._valueConverter) {
declarations[property.name] = property._valueConverter(<string>unparsedValue);
} else if (typeof unparsedValue === 'string' && unparsedProperty === 'transform') {
} else if (unparsedProperty === 'transform') {
const transformations = transformConverter(unparsedValue);
Object.assign(declarations, transformations);
}