Skip to content

Commit 1ebe375

Browse files
committed
Correct handling of animation names
1 parent 9e0439a commit 1ebe375

File tree

2 files changed

+98
-4
lines changed

2 files changed

+98
-4
lines changed

index.js

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,21 +182,95 @@ function localizeDeclValue(valueNode, context) {
182182
return newValueNode;
183183
}
184184

185-
function localizeDecl(decl, context) {
186-
var valuesNode = Tokenizer.parseValues(decl.value);
187-
var localizeName = /animation(-name)?$/.test(decl.prop);
185+
function localizeAnimationShorthandDeclValueNodes(nodes, context) {
186+
var validIdent = validIdent = /^-?[_a-z][_a-z0-9-]*$/i;
187+
188+
var animationKeywords = {
189+
'alternate': 1,
190+
'alternate-reverse': 1,
191+
'backwards': 1,
192+
'both': 1,
193+
'ease': 1,
194+
'ease-in': 1,
195+
'ease-in-out': 1,
196+
'ease-out': 1,
197+
'forwards': 1,
198+
'infinite': 1,
199+
'linear': 1,
200+
'none': 2,
201+
'normal': 1,
202+
'paused': 1,
203+
'reverse': 1,
204+
'running': 1,
205+
'step-end': 1,
206+
'step-start': 1,
207+
};
208+
209+
var didParseAnimationName = false;
210+
var parsedAnimationKeywords = {};
211+
return nodes.map(function(valueNode) {
212+
var value = valueNode.type === 'item'
213+
? valueNode.name.toLowerCase()
214+
: null;
215+
216+
var shouldParseAnimationName = false;
217+
218+
if (!didParseAnimationName && value && validIdent.test(value)) {
219+
if (value in animationKeywords) {
220+
parsedAnimationKeywords[value] = (value in parsedAnimationKeywords)
221+
? (parsedAnimationKeywords[value] + 1)
222+
: 0;
223+
224+
shouldParseAnimationName = (parsedAnimationKeywords[value] >= animationKeywords[value]);
225+
} else {
226+
shouldParseAnimationName = true;
227+
}
228+
}
229+
230+
var subContext = {
231+
options: context.options,
232+
global: context.global,
233+
localizeNextItem: shouldParseAnimationName && !context.global
234+
};
235+
return localizeDeclNode(valueNode, subContext);
236+
});
237+
}
238+
239+
function localizeAnimationShorthandDeclValues(valuesNode, decl, context) {
240+
var newValuesNode = Object.create(valuesNode);
241+
newValuesNode.nodes = valuesNode.nodes.map(function(valueNode, index) {
242+
var newValueNode = Object.create(valueNode);
243+
newValueNode.nodes = localizeAnimationShorthandDeclValueNodes(valueNode.nodes, context);
244+
return newValueNode;
245+
});
246+
decl.value = Tokenizer.stringifyValues(newValuesNode);
247+
}
248+
249+
function localizeDeclValues(localize, valuesNode, decl, context) {
188250
var newValuesNode = Object.create(valuesNode);
189251
newValuesNode.nodes = valuesNode.nodes.map(function(valueNode) {
190252
var subContext = {
191253
options: context.options,
192254
global: context.global,
193-
localizeNextItem: localizeName && !context.global
255+
localizeNextItem: localize && !context.global
194256
};
195257
return localizeDeclValue(valueNode, subContext);
196258
});
197259
decl.value = Tokenizer.stringifyValues(newValuesNode);
198260
}
199261

262+
function localizeDecl(decl, context) {
263+
var valuesNode = Tokenizer.parseValues(decl.value);
264+
265+
var isAnimation = /animation?$/.test(decl.prop);
266+
if (isAnimation) return localizeAnimationShorthandDeclValues(valuesNode, decl, context);
267+
268+
var isAnimationName = /animation(-name)?$/.test(decl.prop);
269+
if (isAnimationName) return localizeDeclValues(true, valuesNode, decl, context);
270+
271+
return localizeDeclValues(false, valuesNode, decl, context);
272+
}
273+
200274
module.exports = postcss.plugin('postcss-modules-local-by-default', function (options) {
201275
if (typeof options !== 'object') {
202276
options = {}; // If options is undefined or not an object the plugin fails

test.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,26 @@ var tests = [
174174
input: '.foo { animation: foo, bar 5s linear 2s infinite alternate, barfoo 1s; }',
175175
expected: ':local(.foo) { animation: :local(foo), :local(bar) 5s linear 2s infinite alternate, :local(barfoo) 1s; }'
176176
},
177+
{
178+
should: 'handle animations where the first value is not the animation name',
179+
input: '.foo { animation: 1s foo; }',
180+
expected: ':local(.foo) { animation: 1s :local(foo); }'
181+
},
182+
{
183+
should: 'handle animations where the first value is not the animation name whilst also using keywords',
184+
input: '.foo { animation: 1s normal ease-out infinite foo; }',
185+
expected: ':local(.foo) { animation: 1s normal ease-out infinite :local(foo); }'
186+
},
187+
{
188+
should: 'handle animations with custom timing functions',
189+
input: '.foo { animation: 1s normal cubic-bezier(0.25, 0.5, 0.5. 0.75) foo; }',
190+
expected: ':local(.foo) { animation: 1s normal cubic-bezier(0.25, 0.5, 0.5. 0.75) :local(foo); }'
191+
},
192+
{
193+
should: 'handle animations whose names are keywords',
194+
input: '.foo { animation: 1s infinite infinite; }',
195+
expected: ':local(.foo) { animation: 1s infinite :local(infinite); }'
196+
},
177197
{
178198
should: 'default to global when mode provided',
179199
input: '.foo {}',

0 commit comments

Comments
 (0)