@@ -11,7 +11,7 @@ import { File, knownFolders, path } from '../../file-system';
1111import { Application , CssChangedEventData , LoadAppCSSEventData } from '../../application' ;
1212import { profile } from './styling-profile' ;
1313
14- import { Keyframes , KeyframeAnimationInfo , KeyframeAnimation } from '../animation/keyframe-animation' ;
14+ import { Keyframes , KeyframeAnimationInfo , KeyframeAnimation , KeyframeInfo } from '../animation/keyframe-animation' ;
1515
1616import { CssAnimationParser } from './css-animation-parser' ;
1717import { sanitizeModuleName } from '../../utils/common' ;
@@ -66,6 +66,39 @@ const tagToScopeTag: Map<string | number, string> = new Map();
6666let currentScopeTag : string = null ;
6767
6868const animationsSymbol = Symbol ( 'animations' ) ;
69+ // Set on a ruleset whose animation declarations use var(): its animations are resolved per view.
70+ const animationVariablesSymbol = Symbol ( 'animationVariables' ) ;
71+ // Set on an animation whose keyframes use var(): the raw keyframes, resolved per view.
72+ const keyframeVariablesSymbol = Symbol ( 'keyframeVariables' ) ;
73+
74+ function isAnimationDeclaration ( declaration : { property : string } ) : boolean {
75+ return declaration . property . startsWith ( 'animation' ) ;
76+ }
77+
78+ function keyframesUseVariables ( keyframes : Keyframes ) : boolean {
79+ return keyframes . keyframes . some ( ( keyframe ) => keyframe . declarations . some ( ( declaration ) => typeof declaration . value === 'string' && isCssVariableExpression ( declaration . value ) ) ) ;
80+ }
81+
82+ /**
83+ * Convert keyframes, or null if a value cannot be: one bad keyframe must not stop the stylesheet.
84+ */
85+ function keyframesArrayFromCSS ( keyframes : Keyframes , view ?: ViewBase ) : KeyframeInfo [ ] | null {
86+ let unparsed = keyframes . keyframes ;
87+ if ( view ) {
88+ unparsed = unparsed . map ( ( keyframe ) => ( {
89+ values : keyframe . values ,
90+ declarations : keyframe . declarations . map ( ( { property, value } ) => ( { property, value : _evaluateCssVariableExpression ( view , property , value ) } ) ) ,
91+ } ) ) ;
92+ }
93+
94+ try {
95+ return CssAnimationParser . keyframesArrayFromCSS ( unparsed ) ;
96+ } catch ( e ) {
97+ Trace . write ( `Failed to parse keyframes [${ keyframes . name } ]${ view ? ` for ${ view } ` : '' } . ${ e } ` , Trace . categories . Animation , Trace . messageType . warn ) ;
98+
99+ return null ;
100+ }
101+ }
69102const kebabCasePattern = / - ( [ a - z ] ) / g;
70103const kebabCaseReplacementFunc = ( g : string ) => g [ 1 ] . toUpperCase ( ) ;
71104const pattern = / ( ' | " ) ( .* ?) \1/ ;
@@ -373,6 +406,9 @@ class CSSSource {
373406 if ( rulesets && rulesets . length ) {
374407 rulesets . forEach ( ( rule ) => {
375408 rule [ animationsSymbol ] = CssAnimationParser . keyframeAnimationsFromCSSDeclarations ( rule . declarations ) ;
409+ if ( rule [ animationsSymbol ] && rule . declarations . some ( ( declaration ) => isAnimationDeclaration ( declaration ) && isCssVariableExpression ( declaration . value ) ) ) {
410+ rule [ animationVariablesSymbol ] = true ;
411+ }
376412 } ) ;
377413 }
378414
@@ -798,9 +834,16 @@ export class CssState {
798834 const animations : KeyframeAnimation [ ] = [ ] ;
799835
800836 matchingSelectors . forEach ( ( selector ) => {
801- const ruleAnimations : KeyframeAnimationInfo [ ] = selector . ruleset ?. [ animationsSymbol ] ;
837+ const ruleset = selector . ruleset ;
838+ let ruleAnimations : KeyframeAnimationInfo [ ] = ruleset ?. [ animationsSymbol ] ;
839+ if ( ruleAnimations && ruleset [ animationVariablesSymbol ] ) {
840+ ruleAnimations = this . resolveAnimationVariables ( ruleset ) ;
841+ }
802842 if ( ruleAnimations ) {
803- for ( const animationInfo of ruleAnimations ) {
843+ for ( let animationInfo of ruleAnimations ) {
844+ if ( animationInfo [ keyframeVariablesSymbol ] ) {
845+ animationInfo = this . resolveKeyframeVariables ( animationInfo ) ;
846+ }
804847 const animation = KeyframeAnimation . keyframeAnimationFromInfo ( animationInfo ) ;
805848 if ( animation ) {
806849 animations . push ( animation ) ;
@@ -823,6 +866,38 @@ export class CssState {
823866 }
824867 }
825868
869+ /**
870+ * The ruleset's animations, with the variables in their declarations resolved for this view.
871+ */
872+ private resolveAnimationVariables ( ruleset : RuleSet ) : KeyframeAnimationInfo [ ] {
873+ const view = this . viewRef . get ( ) ;
874+ const scope = view ?. _styleScope ;
875+ if ( ! scope ) {
876+ return null ;
877+ }
878+
879+ const declarations = ruleset . declarations . filter ( isAnimationDeclaration ) . map ( ( { property, value } ) => ( { property, value : _evaluateCssVariableExpression ( view , property , value ) } ) ) ;
880+ const animations = CssAnimationParser . keyframeAnimationsFromCSSDeclarations ( declarations ) ;
881+ if ( animations ) {
882+ for ( const animation of animations ) {
883+ scope . _applyKeyframes ( animation ) ;
884+ }
885+ }
886+
887+ return animations ;
888+ }
889+
890+ /**
891+ * A copy of the animation with its keyframes' variables resolved for this view.
892+ */
893+ private resolveKeyframeVariables ( animation : KeyframeAnimationInfo ) : KeyframeAnimationInfo {
894+ const view = this . viewRef . get ( ) ;
895+ const resolved = Object . assign ( new KeyframeAnimationInfo ( ) , animation ) ;
896+ resolved . keyframes = view ? keyframesArrayFromCSS ( animation [ keyframeVariablesSymbol ] , view ) : null ;
897+
898+ return resolved ;
899+ }
900+
826901 private stopKeyframeAnimations ( ) : void {
827902 if ( ! this . _playsKeyframeAnimations ) {
828903 return ;
@@ -1168,7 +1243,7 @@ export class StyleScope {
11681243 const keyframeRule = this . findKeyframeRule ( animationName ) ;
11691244
11701245 const animation = new KeyframeAnimationInfo ( ) ;
1171- animation . keyframes = keyframeRule ? CssAnimationParser . keyframesArrayFromCSS ( keyframeRule . keyframes ) : null ;
1246+ animation . keyframes = keyframeRule ? keyframesArrayFromCSS ( keyframeRule ) : null ;
11721247
11731248 return animation ;
11741249 }
@@ -1292,13 +1367,28 @@ export class StyleScope {
12921367
12931368 if ( animations != null && animations . length ) {
12941369 for ( const animation of animations ) {
1295- const keyframeRule = this . findKeyframeRule ( animation . name ) ;
1296- animation . keyframes = keyframeRule ? CssAnimationParser . keyframesArrayFromCSS ( keyframeRule . keyframes ) : null ;
1370+ this . _applyKeyframes ( animation ) ;
12971371 }
12981372 }
12991373 }
13001374 }
13011375
1376+ /**
1377+ * Give the animation the keyframes of its name. Keyframes that use variables are kept
1378+ * unparsed, for each view to resolve.
1379+ * @private
1380+ */
1381+ public _applyKeyframes ( animation : KeyframeAnimationInfo ) : void {
1382+ const keyframeRule = this . findKeyframeRule ( animation . name ) ;
1383+ if ( keyframeRule && keyframesUseVariables ( keyframeRule ) ) {
1384+ animation [ keyframeVariablesSymbol ] = keyframeRule ;
1385+ animation . keyframes = null ;
1386+ } else {
1387+ animation [ keyframeVariablesSymbol ] = undefined ;
1388+ animation . keyframes = keyframeRule ? keyframesArrayFromCSS ( keyframeRule ) : null ;
1389+ }
1390+ }
1391+
13021392 public getAnimations ( ruleset : RuleSet ) : KeyframeAnimationInfo [ ] {
13031393 return ruleset [ animationsSymbol ] ;
13041394 }
0 commit comments