Skip to content

Commit

Permalink
fix(makeStyles): fix insertion of keyframes (#20095)
Browse files Browse the repository at this point in the history
* fix insertion of keyframes

* Change files

* fix snapshot

* fix more UTs

* add an example with multiple animations

* fix multiple animations
  • Loading branch information
layershifter committed Oct 5, 2021
1 parent 32414b7 commit af79644
Show file tree
Hide file tree
Showing 11 changed files with 231 additions and 89 deletions.
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "fix insertion of keyframes",
"packageName": "@fluentui/make-styles",
"email": "olfedias@microsoft.com",
"dependentChangeType": "patch"
}
@@ -0,0 +1,7 @@
{
"type": "none",
"comment": "add section about keyframes to README",
"packageName": "@fluentui/react-make-styles",
"email": "olfedias@microsoft.com",
"dependentChangeType": "none"
}
28 changes: 28 additions & 0 deletions packages/make-styles/src/makeStyles.test.ts
Expand Up @@ -129,6 +129,34 @@ describe('makeStyles', () => {
transform: rotate(-360deg);
}
}
@keyframes f1q8eu9e {
from {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes f55c0se {
from {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(-360deg);
-moz-transform: rotate(-360deg);
-ms-transform: rotate(-360deg);
transform: rotate(-360deg);
}
}
.f1g6ul6r {
-webkit-animation-name: f1q8eu9e;
animation-name: f1q8eu9e;
Expand Down
Expand Up @@ -3,7 +3,7 @@ import { MakeStylesRenderer, StyleBucketName } from '../types';
// Regexps to extract names of classes and animations
// https://github.com/styletron/styletron/blob/e0fcae826744eb00ce679ac613a1b10d44256660/packages/styletron-engine-atomic/src/client/client.js#L8
// eslint-disable-next-line @fluentui/max-len
const KEYFRAMES_HYDRATOR = /@-webkit-keyframes ([^{]+){((?:(?:from|to|(?:\d+\.?\d*%))\{(?:[^}])*})*)}@keyframes ([^{]+){((?:(?:from|to|(?:\d+\.?\d*%))\{(?:[^}])*})*)}/g;
const KEYFRAMES_HYDRATOR = /@(-webkit-)?keyframes ([^{]+){((?:(?:from|to|(?:\d+\.?\d*%))\{(?:[^}])*})*)}/g;
const AT_RULES_HYDRATOR = /@(media|supports)[^{]+\{([\s\S]+?})\s*}/g;
const STYLES_HYDRATOR = /\.([^{:]+)(:[^{]+)?{(?:[^}]*;)?([^}]*?)}/g;

Expand Down
40 changes: 40 additions & 0 deletions packages/make-styles/src/runtime/compileKeyframeCSS.test.ts
@@ -0,0 +1,40 @@
import { compileKeyframeRule, compileKeyframesCSS } from './compileKeyframeCSS';
import { MakeStyles } from '../types';

describe('compileKeyframeRule', () => {
it('stringifies an object with keyframes', () => {
const keyframes: MakeStyles = {
from: {
transform: 'rotate(0deg)',
},
to: {
transform: 'rotate(360deg)',
},
};
const result = compileKeyframeRule(keyframes);

expect(result).toMatchInlineSnapshot(`"from{transform:rotate(0deg);}to{transform:rotate(360deg);}"`);
});
});

describe('compileKeyframeCSS', () => {
it('creates CSS from strings with keyframes', () => {
const keyframes: MakeStyles = {
from: {
height: '10px',
},
to: {
height: '50px',
},
};
const keyframesCSS = compileKeyframeRule(keyframes);
const result = compileKeyframesCSS('foo', keyframesCSS);

expect(result).toMatchInlineSnapshot(`
Array [
"@-webkit-keyframes foo{from{height:10px;}to{height:50px;}}",
"@keyframes foo{from{height:10px;}to{height:50px;}}",
]
`);
});
});
32 changes: 25 additions & 7 deletions packages/make-styles/src/runtime/compileKeyframeCSS.ts
@@ -1,19 +1,37 @@
import { MakeStyles } from '../types';
import { compile, middleware, serialize, stringify, prefixer } from 'stylis';
import { compile, middleware, serialize, rulesheet, stringify, prefixer } from 'stylis';
import { cssifyObject } from './utils/cssifyObject';

export function compileKeyframeRule(frames: MakeStyles): string {
export function compileKeyframeRule(keyframeObject: MakeStyles): string {
let css: string = '';

// eslint-disable-next-line guard-for-in
for (const percentage in frames) {
css += `${percentage}{${cssifyObject(frames[percentage])}}`;
for (const percentage in keyframeObject) {
css += `${percentage}{${cssifyObject(keyframeObject[percentage])}}`;
}

return css;
}

export function compileKeyframesCSS(animationName: string, framesCSS: string): string {
const cssRule = `@keyframes ${animationName} {${framesCSS}}`;
return serialize(compile(cssRule), middleware([prefixer, stringify]));
/**
* Creates CSS rules for insertion from passed CSS.
*/
export function compileKeyframesCSS(keyframeName: string, keyframeCSS: string): string[] {
const cssRule = `@keyframes ${keyframeName} {${keyframeCSS}}`;
const rules: string[] = [];

serialize(
compile(cssRule),
middleware([
prefixer,
stringify,

// 💡 we are using `.insertRule()` API for DOM operations, which does not support
// insertion of multiple CSS rules in a single call. `rulesheet` plugin extracts
// individual rules to be used with this API
rulesheet(rule => rules.push(rule)),
]),
);

return rules;
}
84 changes: 42 additions & 42 deletions packages/make-styles/src/runtime/resolveStyleRules.test.ts
Expand Up @@ -524,32 +524,32 @@ describe('resolveStyleRules', () => {
transform: rotate(360deg);
}
}
@keyframes f1q8eu9e {
@-webkit-keyframes f55c0se {
from {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
-webkit-transform: rotate(-360deg);
-moz-transform: rotate(-360deg);
-ms-transform: rotate(-360deg);
transform: rotate(-360deg);
}
}
@-webkit-keyframes f55c0se {
@keyframes f1q8eu9e {
from {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(-360deg);
-moz-transform: rotate(-360deg);
-ms-transform: rotate(-360deg);
transform: rotate(-360deg);
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes f55c0se {
Expand Down Expand Up @@ -624,48 +624,32 @@ describe('resolveStyleRules', () => {
transform: rotate(360deg);
}
}
@keyframes f1q8eu9e {
@-webkit-keyframes f55c0se {
from {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-webkit-keyframes f5j8bii {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes f5j8bii {
from {
opacity: 0;
}
to {
opacity: 1;
-webkit-transform: rotate(-360deg);
-moz-transform: rotate(-360deg);
-ms-transform: rotate(-360deg);
transform: rotate(-360deg);
}
}
@-webkit-keyframes f55c0se {
@keyframes f1q8eu9e {
from {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(-360deg);
-moz-transform: rotate(-360deg);
-ms-transform: rotate(-360deg);
transform: rotate(-360deg);
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes f55c0se {
Expand All @@ -682,13 +666,29 @@ describe('resolveStyleRules', () => {
transform: rotate(-360deg);
}
}
.f1al5ov7 {
-webkit-animation-name: f1q8eu9e f5j8bii;
animation-name: f1q8eu9e f5j8bii;
@-webkit-keyframes f5j8bii {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes f5j8bii {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fng7zue {
-webkit-animation-name: f1q8eu9e, f5j8bii;
animation-name: f1q8eu9e, f5j8bii;
}
.f1yfduy3 {
-webkit-animation-name: f55c0se f5j8bii;
animation-name: f55c0se f5j8bii;
.f12eevt1 {
-webkit-animation-name: f55c0se, f5j8bii;
animation-name: f55c0se, f5j8bii;
}
.f1cpbl36 {
-webkit-animation-iteration-count: infinite;
Expand Down
57 changes: 30 additions & 27 deletions packages/make-styles/src/runtime/resolveStyleRules.ts
Expand Up @@ -107,49 +107,52 @@ function resolveStyleRulesInner(
pushToClassesMap(cssClassesMap, key, className, rtlClassName);
pushToCSSRules(cssRulesByBucket, styleBucketName, ltrCSS, rtlCSS);
} else if (property === 'animationName') {
const animationNames = Array.isArray(value) ? value : [value];
let keyframeCSS = '';
let keyframeRtlCSS = '';
const animationNameValue = Array.isArray(value) ? value : [value];

const names = [];
const namesRtl = [];
const animationNames: string[] = [];
const rtlAnimationNames: string[] = [];

for (const val of animationNames) {
const keyframe = compileKeyframeRule(val);
const name = HASH_PREFIX + hashString(keyframe);
for (const keyframeObject of animationNameValue) {
const keyframeCSS = compileKeyframeRule(keyframeObject);
const rtlKeyframeCSS = compileKeyframeRule(convert(keyframeObject));

keyframeCSS += compileKeyframesCSS(name, keyframe);
names.push(name);
const animationName = HASH_PREFIX + hashString(keyframeCSS);
let rtlAnimationName: string;

const rtlKeyframe = compileKeyframeRule(convert(val));
const keyframeRules = compileKeyframesCSS(animationName, keyframeCSS);
let rtlKeyframeRules: string[] = [];

if (keyframe !== rtlKeyframe) {
const nameRtl = HASH_PREFIX + hashString(rtlKeyframe);
keyframeRtlCSS += compileKeyframesCSS(nameRtl, rtlKeyframe);
namesRtl.push(nameRtl);
if (keyframeCSS === rtlKeyframeCSS) {
// If CSS for LTR & RTL are same we will re-use animationName from LTR to avoid duplication of rules in output
rtlAnimationName = animationName;
} else {
namesRtl.push(name);
rtlAnimationName = HASH_PREFIX + hashString(rtlKeyframeCSS);
rtlKeyframeRules = compileKeyframesCSS(rtlAnimationName, rtlKeyframeCSS);
}
}

const animationName = names.join(' ');
const animationNameRtl = namesRtl.join(' ');
for (let i = 0; i < keyframeRules.length; i++) {
pushToCSSRules(
cssRulesByBucket,
// keyframes styles should be inserted into own bucket
'k',
keyframeRules[i],
rtlKeyframeRules[i],
);
}

animationNames.push(animationName);
rtlAnimationNames.push(rtlAnimationName);
}

pushToCSSRules(
cssRulesByBucket,
'k', // keyframes styles should be inserted into own bucket
keyframeCSS,
keyframeRtlCSS || undefined,
);
resolveStyleRulesInner(
{ animationName },
{ animationName: animationNames.join(', ') },
unstable_cssPriority,
pseudo,
media,
support,
cssClassesMap,
cssRulesByBucket,
animationNameRtl,
rtlAnimationNames.join(', '),
);
} else if (isObject(value)) {
if (isNestedSelector(property)) {
Expand Down

0 comments on commit af79644

Please sign in to comment.