Skip to content

Commit 5509077

Browse files
committed
feat: support animation and animation-name (with @Keyframes)
1 parent 2f86c0d commit 5509077

4 files changed

Lines changed: 341 additions & 128 deletions

File tree

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"dependencies": {
6363
"css": "^3.0.0",
6464
"css-mediaquery": "^0.1.2",
65-
"@cssxjs/css-to-react-native": "^3.2.0-0"
65+
"@cssxjs/css-to-react-native": "3.2.0-1"
6666
},
6767
"repository": {
6868
"type": "git",

‎src/index.js‎

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,54 @@ const shorthandBorderProps = [
2525
"border-style",
2626
];
2727

28-
const transformDecls = (styles, declarations, result) => {
28+
/**
29+
* Extracts @keyframes from CSS and returns the cleaned CSS and keyframes object
30+
* @param {string} css - The input CSS string
31+
* @returns {{css: string, keyframes: Object}} - Cleaned CSS and keyframes object
32+
*/
33+
const extractKeyframes = (css) => {
34+
const keyframes = {};
35+
let cleanedCss = css;
36+
37+
// Find @keyframes by manually parsing to handle nested braces
38+
let index = 0;
39+
while (index < css.length) {
40+
const keyframesMatch = css.slice(index).match(/@keyframes\s+([^\s{]+)\s*{/);
41+
if (!keyframesMatch) break;
42+
43+
const startIndex = index + keyframesMatch.index;
44+
const nameEndIndex = startIndex + keyframesMatch[0].length;
45+
const name = keyframesMatch[1];
46+
47+
// Find the matching closing brace
48+
let braceCount = 1;
49+
let currentIndex = nameEndIndex;
50+
while (currentIndex < css.length && braceCount > 0) {
51+
if (css[currentIndex] === '{') {
52+
braceCount++;
53+
} else if (css[currentIndex] === '}') {
54+
braceCount--;
55+
}
56+
currentIndex++;
57+
}
58+
59+
if (braceCount === 0) {
60+
// Extract the body (without the outer braces)
61+
const body = css.slice(nameEndIndex, currentIndex - 1).trim();
62+
keyframes[name] = body;
63+
64+
// Mark for removal by replacing with spaces to maintain positions
65+
const fullKeyframe = css.slice(startIndex, currentIndex);
66+
cleanedCss = cleanedCss.replace(fullKeyframe, '');
67+
}
68+
69+
index = currentIndex;
70+
}
71+
72+
return { css: cleanedCss, keyframes };
73+
};
74+
75+
const transformDecls = (styles, declarations, result, keyframes) => {
2976
for (const d in declarations) {
3077
const declaration = declarations[d];
3178
if (declaration.type !== "declaration") continue;
@@ -64,13 +111,30 @@ const transformDecls = (styles, declarations, result) => {
64111
} else {
65112
Object.assign(styles, transformed);
66113
}
114+
} else if (['animation', 'animation-name'].includes(property) && keyframes && Object.keys(keyframes).length > 0) {
115+
// Pass all keyframes to transformCSS - it will figure out which ones to use
116+
const keyframeDeclarations = [];
117+
for (const name in keyframes) {
118+
keyframeDeclarations.push(['@keyframes ' + name, keyframes[name]]);
119+
}
120+
const transformed = transformCSS([
121+
...keyframeDeclarations,
122+
[property, value]
123+
]);
124+
Object.assign(styles, transformed);
67125
} else {
68126
Object.assign(styles, transformCSS([[property, value]]));
69127
}
70128
}
71129
};
72130

73131
const transform = (css, options) => {
132+
// Extract keyframes and store them separately, before parsing (remove them from css)
133+
let keyframes;
134+
if (options?.parseKeyframes) {
135+
({ css, keyframes } = extractKeyframes(css));
136+
}
137+
74138
const { stylesheet } = parseCSS(css);
75139
const rules = sortRules(stylesheet.rules);
76140

@@ -126,7 +190,7 @@ const transform = (css, options) => {
126190

127191
const selector = rule.selectors[s].replace(/^\./, "");
128192
const styles = (result[selector] = result[selector] || {});
129-
transformDecls(styles, rule.declarations, result);
193+
transformDecls(styles, rule.declarations, result, keyframes);
130194
}
131195

132196
if (
@@ -179,14 +243,18 @@ const transform = (css, options) => {
179243
const selector = ruleRule.selectors[s].replace(/^\./, "");
180244
const mediaStyles = (result[media][selector] =
181245
result[media][selector] || {});
182-
transformDecls(mediaStyles, ruleRule.declarations, result);
246+
transformDecls(mediaStyles, ruleRule.declarations, result, keyframes);
183247
}
184248
}
185249
}
186250
}
187251

188252
if (result.__exportProps) {
189-
Object.assign(result, result.__exportProps);
253+
if (Object.keys(result.__exportProps).length === 0) {
254+
delete result.__exportProps;
255+
} else {
256+
Object.assign(result, result.__exportProps);
257+
}
190258
}
191259

192260
return result;

0 commit comments

Comments
 (0)