Skip to content

Commit fde62d7

Browse files
feat: add global and local for animations (#76)
* add global for animations * refactor: logic --------- Co-authored-by: alexander.akait <[email protected]>
1 parent a73b700 commit fde62d7

File tree

3 files changed

+164
-66
lines changed

3 files changed

+164
-66
lines changed

README.md

+26
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
Transformation examples:
66

7+
Selectors (mode `local`, by default)::
8+
79
<!-- prettier-ignore-start -->
810
```css
911
.foo { ... } /* => */ :local(.foo) { ... }
@@ -28,6 +30,30 @@ Transformation examples:
2830
```
2931
<!-- prettier-ignore-end -->
3032

33+
Declarations (mode `local`, by default):
34+
35+
<!-- prettier-ignore-start -->
36+
```css
37+
.foo {
38+
animation-name: fadeInOut, global(moveLeft300px), local(bounce);
39+
}
40+
41+
.bar {
42+
animation: rotate 1s, global(spin) 3s, local(fly) 6s;
43+
}
44+
45+
/* => */
46+
47+
:local(.foo) {
48+
animation-name: :local(fadeInOut), moveLeft300px, :local(bounce);
49+
}
50+
51+
:local(.bar) {
52+
animation: :local(rotate) 1s, spin 3s, :local(fly) 6s;
53+
}
54+
```
55+
<!-- prettier-ignore-end -->
56+
3157
## Building
3258

3359
```bash

src/index.js

+76-66
Original file line numberDiff line numberDiff line change
@@ -347,24 +347,20 @@ function localizeDeclarationValues(localize, declaration, context) {
347347
declaration.value = valueNodes.toString();
348348
}
349349

350-
function localizeDeclaration(declaration, context) {
351-
const isAnimation = /animation$/i.test(declaration.prop);
352-
353-
if (isAnimation) {
354-
// letter
355-
// An uppercase letter or a lowercase letter.
356-
//
357-
// ident-start code point
358-
// A letter, a non-ASCII code point, or U+005F LOW LINE (_).
359-
//
360-
// ident code point
361-
// An ident-start code point, a digit, or U+002D HYPHEN-MINUS (-).
362-
363-
// We don't validate `hex digits`, because we don't need it, it is work of linters.
364-
const validIdent =
365-
/^-?([a-z\u0080-\uFFFF_]|(\\[^\r\n\f])|-(?![0-9]))((\\[^\r\n\f])|[a-z\u0080-\uFFFF_0-9-])*$/i;
366-
367-
/*
350+
// letter
351+
// An uppercase letter or a lowercase letter.
352+
//
353+
// ident-start code point
354+
// A letter, a non-ASCII code point, or U+005F LOW LINE (_).
355+
//
356+
// ident code point
357+
// An ident-start code point, a digit, or U+002D HYPHEN-MINUS (-).
358+
359+
// We don't validate `hex digits`, because we don't need it, it is work of linters.
360+
const validIdent =
361+
/^-?([a-z\u0080-\uFFFF_]|(\\[^\r\n\f])|-(?![0-9]))((\\[^\r\n\f])|[a-z\u0080-\uFFFF_0-9-])*$/i;
362+
363+
/*
368364
The spec defines some keywords that you can use to describe properties such as the timing
369365
function. These are still valid animation names, so as long as there is a property that accepts
370366
a keyword, it is given priority. Only when all the properties that can take a keyword are
@@ -375,48 +371,72 @@ function localizeDeclaration(declaration, context) {
375371
The animation will repeat an infinite number of times from the first argument, and will have an
376372
animation name of infinite from the second.
377373
*/
378-
const animationKeywords = {
379-
// animation-direction
380-
$normal: 1,
381-
$reverse: 1,
382-
$alternate: 1,
383-
"$alternate-reverse": 1,
384-
// animation-fill-mode
385-
$forwards: 1,
386-
$backwards: 1,
387-
$both: 1,
388-
// animation-iteration-count
389-
$infinite: 1,
390-
// animation-play-state
391-
$paused: 1,
392-
$running: 1,
393-
// animation-timing-function
394-
$ease: 1,
395-
"$ease-in": 1,
396-
"$ease-out": 1,
397-
"$ease-in-out": 1,
398-
$linear: 1,
399-
"$step-end": 1,
400-
"$step-start": 1,
401-
// Special
402-
$none: Infinity, // No matter how many times you write none, it will never be an animation name
403-
// Global values
404-
$initial: Infinity,
405-
$inherit: Infinity,
406-
$unset: Infinity,
407-
$revert: Infinity,
408-
"$revert-layer": Infinity,
409-
};
374+
const animationKeywords = {
375+
// animation-direction
376+
$normal: 1,
377+
$reverse: 1,
378+
$alternate: 1,
379+
"$alternate-reverse": 1,
380+
// animation-fill-mode
381+
$forwards: 1,
382+
$backwards: 1,
383+
$both: 1,
384+
// animation-iteration-count
385+
$infinite: 1,
386+
// animation-play-state
387+
$paused: 1,
388+
$running: 1,
389+
// animation-timing-function
390+
$ease: 1,
391+
"$ease-in": 1,
392+
"$ease-out": 1,
393+
"$ease-in-out": 1,
394+
$linear: 1,
395+
"$step-end": 1,
396+
"$step-start": 1,
397+
// Special
398+
$none: Infinity, // No matter how many times you write none, it will never be an animation name
399+
// Global values
400+
$initial: Infinity,
401+
$inherit: Infinity,
402+
$unset: Infinity,
403+
$revert: Infinity,
404+
"$revert-layer": Infinity,
405+
};
406+
407+
function localizeDeclaration(declaration, context) {
408+
const isAnimation = /animation(-name)?$/i.test(declaration.prop);
409+
410+
if (isAnimation) {
410411
let parsedAnimationKeywords = {};
411412
const valueNodes = valueParser(declaration.value).walk((node) => {
412413
// If div-token appeared (represents as comma ','), a possibility of an animation-keywords should be reflesh.
413414
if (node.type === "div") {
414415
parsedAnimationKeywords = {};
415416

416417
return;
417-
}
418-
// Do not handle nested functions
419-
else if (node.type === "function") {
418+
} else if (
419+
node.type === "function" &&
420+
node.value.toLowerCase() === "local" &&
421+
node.nodes.length === 1
422+
) {
423+
node.type = "word";
424+
node.value = node.nodes[0].value;
425+
426+
return localizeDeclNode(node, {
427+
options: context.options,
428+
global: context.global,
429+
localizeNextItem: true,
430+
localAliasMap: context.localAliasMap,
431+
});
432+
} else if (node.type === "function") {
433+
// replace `animation: global(example)` with `animation-name: example`
434+
if (node.value.toLowerCase() === "global" && node.nodes.length === 1) {
435+
node.type = "word";
436+
node.value = node.nodes[0].value;
437+
}
438+
439+
// Do not handle nested functions
420440
return false;
421441
}
422442
// Ignore all except word
@@ -443,30 +463,20 @@ function localizeDeclaration(declaration, context) {
443463
}
444464
}
445465

446-
const subContext = {
466+
return localizeDeclNode(node, {
447467
options: context.options,
448468
global: context.global,
449469
localizeNextItem: shouldParseAnimationName && !context.global,
450470
localAliasMap: context.localAliasMap,
451-
};
452-
453-
return localizeDeclNode(node, subContext);
471+
});
454472
});
455473

456474
declaration.value = valueNodes.toString();
457475

458476
return;
459477
}
460478

461-
const isAnimationName = /animation(-name)?$/i.test(declaration.prop);
462-
463-
if (isAnimationName) {
464-
return localizeDeclarationValues(true, declaration, context);
465-
}
466-
467-
const hasUrl = /url\(/i.test(declaration.value);
468-
469-
if (hasUrl) {
479+
if (/url\(/i.test(declaration.value)) {
470480
return localizeDeclarationValues(false, declaration, context);
471481
}
472482
}

test/index.test.js

+62
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,11 @@ const tests = [
164164
input: ".foo { animation-name: bar; }",
165165
expected: ":local(.foo) { animation-name: :local(bar); }",
166166
},
167+
{
168+
name: "localize a single animation-name #2",
169+
input: ".foo { animation-name: local(bar); }",
170+
expected: ":local(.foo) { animation-name: :local(bar); }",
171+
},
167172
{
168173
name: "not localize animation-name in a var function",
169174
input: ".foo { animation-name: var(--bar); }",
@@ -179,6 +184,63 @@ const tests = [
179184
input: ".foo { animation-name: env(bar); }",
180185
expected: ":local(.foo) { animation-name: env(bar); }",
181186
},
187+
{
188+
name: "not localize animation-name in an global function",
189+
input: ".foo { animation-name: global(bar); }",
190+
expected: ":local(.foo) { animation-name: bar; }",
191+
},
192+
{
193+
name: "localize and not localize animation-name in mixed case",
194+
input:
195+
".foo { animation-name: fadeInOut, global(moveLeft300px), local(bounce); }",
196+
expected:
197+
":local(.foo) { animation-name: :local(fadeInOut), moveLeft300px, :local(bounce); }",
198+
},
199+
{
200+
name: "localize and not localize animation-name in mixed case #2",
201+
options: { mode: "global" },
202+
input:
203+
".foo { animation-name: fadeInOut, global(moveLeft300px), local(bounce); }",
204+
expected:
205+
".foo { animation-name: fadeInOut, moveLeft300px, :local(bounce); }",
206+
},
207+
{
208+
name: "localize and not localize animation-name in mixed case #3",
209+
options: { mode: "pure" },
210+
input:
211+
".foo { animation-name: fadeInOut, global(moveLeft300px), local(bounce); }",
212+
expected:
213+
":local(.foo) { animation-name: :local(fadeInOut), moveLeft300px, :local(bounce); }",
214+
},
215+
{
216+
name: "not localize animation in an global function",
217+
input: ".foo { animation: global(bar); }",
218+
expected: ":local(.foo) { animation: bar; }",
219+
},
220+
{
221+
name: "not localize a certain animation in an global function",
222+
input: ".foo { animation: global(bar), foo; }",
223+
expected: ":local(.foo) { animation: bar, :local(foo); }",
224+
},
225+
{
226+
name: "localize and not localize a certain animation in mixed case",
227+
input: ".foo { animation: rotate 1s, global(spin) 3s, local(fly) 6s; }",
228+
expected:
229+
":local(.foo) { animation: :local(rotate) 1s, spin 3s, :local(fly) 6s; }",
230+
},
231+
{
232+
name: "localize and not localize a certain animation in mixed case #2",
233+
options: { mode: "global" },
234+
input: ".foo { animation: rotate 1s, global(spin) 3s, local(fly) 6s; }",
235+
expected: ".foo { animation: rotate 1s, spin 3s, :local(fly) 6s; }",
236+
},
237+
{
238+
name: "localize and not localize a certain animation in mixed case #2",
239+
options: { mode: "pure" },
240+
input: ".foo { animation: rotate 1s, global(spin) 3s, local(fly) 6s; }",
241+
expected:
242+
":local(.foo) { animation: :local(rotate) 1s, spin 3s, :local(fly) 6s; }",
243+
},
182244
{
183245
name: "not localize animation-name in an env function #2",
184246
input: ".foo { animation-name: eNv(bar); }",

0 commit comments

Comments
 (0)