Skip to content

Commit

Permalink
feat: add global and local for animations (#76)
Browse files Browse the repository at this point in the history
* add global for animations

* refactor: logic

---------

Co-authored-by: alexander.akait <sheo13666q@gmail.com>
  • Loading branch information
jantimon and alexander-akait authored Nov 2, 2024
1 parent a73b700 commit fde62d7
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 66 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

Transformation examples:

Selectors (mode `local`, by default)::

<!-- prettier-ignore-start -->
```css
.foo { ... } /* => */ :local(.foo) { ... }
Expand All @@ -28,6 +30,30 @@ Transformation examples:
```
<!-- prettier-ignore-end -->

Declarations (mode `local`, by default):

<!-- prettier-ignore-start -->
```css
.foo {
animation-name: fadeInOut, global(moveLeft300px), local(bounce);
}

.bar {
animation: rotate 1s, global(spin) 3s, local(fly) 6s;
}

/* => */

:local(.foo) {
animation-name: :local(fadeInOut), moveLeft300px, :local(bounce);
}

:local(.bar) {
animation: :local(rotate) 1s, spin 3s, :local(fly) 6s;
}
```
<!-- prettier-ignore-end -->

## Building

```bash
Expand Down
142 changes: 76 additions & 66 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,24 +347,20 @@ function localizeDeclarationValues(localize, declaration, context) {
declaration.value = valueNodes.toString();
}

function localizeDeclaration(declaration, context) {
const isAnimation = /animation$/i.test(declaration.prop);

if (isAnimation) {
// letter
// An uppercase letter or a lowercase letter.
//
// ident-start code point
// A letter, a non-ASCII code point, or U+005F LOW LINE (_).
//
// ident code point
// An ident-start code point, a digit, or U+002D HYPHEN-MINUS (-).

// We don't validate `hex digits`, because we don't need it, it is work of linters.
const validIdent =
/^-?([a-z\u0080-\uFFFF_]|(\\[^\r\n\f])|-(?![0-9]))((\\[^\r\n\f])|[a-z\u0080-\uFFFF_0-9-])*$/i;

/*
// letter
// An uppercase letter or a lowercase letter.
//
// ident-start code point
// A letter, a non-ASCII code point, or U+005F LOW LINE (_).
//
// ident code point
// An ident-start code point, a digit, or U+002D HYPHEN-MINUS (-).

// We don't validate `hex digits`, because we don't need it, it is work of linters.
const validIdent =
/^-?([a-z\u0080-\uFFFF_]|(\\[^\r\n\f])|-(?![0-9]))((\\[^\r\n\f])|[a-z\u0080-\uFFFF_0-9-])*$/i;

/*
The spec defines some keywords that you can use to describe properties such as the timing
function. These are still valid animation names, so as long as there is a property that accepts
a keyword, it is given priority. Only when all the properties that can take a keyword are
Expand All @@ -375,48 +371,72 @@ function localizeDeclaration(declaration, context) {
The animation will repeat an infinite number of times from the first argument, and will have an
animation name of infinite from the second.
*/
const animationKeywords = {
// animation-direction
$normal: 1,
$reverse: 1,
$alternate: 1,
"$alternate-reverse": 1,
// animation-fill-mode
$forwards: 1,
$backwards: 1,
$both: 1,
// animation-iteration-count
$infinite: 1,
// animation-play-state
$paused: 1,
$running: 1,
// animation-timing-function
$ease: 1,
"$ease-in": 1,
"$ease-out": 1,
"$ease-in-out": 1,
$linear: 1,
"$step-end": 1,
"$step-start": 1,
// Special
$none: Infinity, // No matter how many times you write none, it will never be an animation name
// Global values
$initial: Infinity,
$inherit: Infinity,
$unset: Infinity,
$revert: Infinity,
"$revert-layer": Infinity,
};
const animationKeywords = {
// animation-direction
$normal: 1,
$reverse: 1,
$alternate: 1,
"$alternate-reverse": 1,
// animation-fill-mode
$forwards: 1,
$backwards: 1,
$both: 1,
// animation-iteration-count
$infinite: 1,
// animation-play-state
$paused: 1,
$running: 1,
// animation-timing-function
$ease: 1,
"$ease-in": 1,
"$ease-out": 1,
"$ease-in-out": 1,
$linear: 1,
"$step-end": 1,
"$step-start": 1,
// Special
$none: Infinity, // No matter how many times you write none, it will never be an animation name
// Global values
$initial: Infinity,
$inherit: Infinity,
$unset: Infinity,
$revert: Infinity,
"$revert-layer": Infinity,
};

function localizeDeclaration(declaration, context) {
const isAnimation = /animation(-name)?$/i.test(declaration.prop);

if (isAnimation) {
let parsedAnimationKeywords = {};
const valueNodes = valueParser(declaration.value).walk((node) => {
// If div-token appeared (represents as comma ','), a possibility of an animation-keywords should be reflesh.
if (node.type === "div") {
parsedAnimationKeywords = {};

return;
}
// Do not handle nested functions
else if (node.type === "function") {
} else if (
node.type === "function" &&
node.value.toLowerCase() === "local" &&
node.nodes.length === 1
) {
node.type = "word";
node.value = node.nodes[0].value;

return localizeDeclNode(node, {
options: context.options,
global: context.global,
localizeNextItem: true,
localAliasMap: context.localAliasMap,
});
} else if (node.type === "function") {
// replace `animation: global(example)` with `animation-name: example`
if (node.value.toLowerCase() === "global" && node.nodes.length === 1) {
node.type = "word";
node.value = node.nodes[0].value;
}

// Do not handle nested functions
return false;
}
// Ignore all except word
Expand All @@ -443,30 +463,20 @@ function localizeDeclaration(declaration, context) {
}
}

const subContext = {
return localizeDeclNode(node, {
options: context.options,
global: context.global,
localizeNextItem: shouldParseAnimationName && !context.global,
localAliasMap: context.localAliasMap,
};

return localizeDeclNode(node, subContext);
});
});

declaration.value = valueNodes.toString();

return;
}

const isAnimationName = /animation(-name)?$/i.test(declaration.prop);

if (isAnimationName) {
return localizeDeclarationValues(true, declaration, context);
}

const hasUrl = /url\(/i.test(declaration.value);

if (hasUrl) {
if (/url\(/i.test(declaration.value)) {
return localizeDeclarationValues(false, declaration, context);
}
}
Expand Down
62 changes: 62 additions & 0 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,11 @@ const tests = [
input: ".foo { animation-name: bar; }",
expected: ":local(.foo) { animation-name: :local(bar); }",
},
{
name: "localize a single animation-name #2",
input: ".foo { animation-name: local(bar); }",
expected: ":local(.foo) { animation-name: :local(bar); }",
},
{
name: "not localize animation-name in a var function",
input: ".foo { animation-name: var(--bar); }",
Expand All @@ -179,6 +184,63 @@ const tests = [
input: ".foo { animation-name: env(bar); }",
expected: ":local(.foo) { animation-name: env(bar); }",
},
{
name: "not localize animation-name in an global function",
input: ".foo { animation-name: global(bar); }",
expected: ":local(.foo) { animation-name: bar; }",
},
{
name: "localize and not localize animation-name in mixed case",
input:
".foo { animation-name: fadeInOut, global(moveLeft300px), local(bounce); }",
expected:
":local(.foo) { animation-name: :local(fadeInOut), moveLeft300px, :local(bounce); }",
},
{
name: "localize and not localize animation-name in mixed case #2",
options: { mode: "global" },
input:
".foo { animation-name: fadeInOut, global(moveLeft300px), local(bounce); }",
expected:
".foo { animation-name: fadeInOut, moveLeft300px, :local(bounce); }",
},
{
name: "localize and not localize animation-name in mixed case #3",
options: { mode: "pure" },
input:
".foo { animation-name: fadeInOut, global(moveLeft300px), local(bounce); }",
expected:
":local(.foo) { animation-name: :local(fadeInOut), moveLeft300px, :local(bounce); }",
},
{
name: "not localize animation in an global function",
input: ".foo { animation: global(bar); }",
expected: ":local(.foo) { animation: bar; }",
},
{
name: "not localize a certain animation in an global function",
input: ".foo { animation: global(bar), foo; }",
expected: ":local(.foo) { animation: bar, :local(foo); }",
},
{
name: "localize and not localize a certain animation in mixed case",
input: ".foo { animation: rotate 1s, global(spin) 3s, local(fly) 6s; }",
expected:
":local(.foo) { animation: :local(rotate) 1s, spin 3s, :local(fly) 6s; }",
},
{
name: "localize and not localize a certain animation in mixed case #2",
options: { mode: "global" },
input: ".foo { animation: rotate 1s, global(spin) 3s, local(fly) 6s; }",
expected: ".foo { animation: rotate 1s, spin 3s, :local(fly) 6s; }",
},
{
name: "localize and not localize a certain animation in mixed case #2",
options: { mode: "pure" },
input: ".foo { animation: rotate 1s, global(spin) 3s, local(fly) 6s; }",
expected:
":local(.foo) { animation: :local(rotate) 1s, spin 3s, :local(fly) 6s; }",
},
{
name: "not localize animation-name in an env function #2",
input: ".foo { animation-name: eNv(bar); }",
Expand Down

0 comments on commit fde62d7

Please sign in to comment.