Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 6c4581f

Browse files
committed
fix(ngAnimate): allow removing classes that are added by a running animation
This allows follow-up animations to remove a class that is currently being added. Fixes #13339 Fixes #13380 Closes #13414 Closes #13472 Closes #13678
1 parent 620a20d commit 6c4581f

File tree

2 files changed

+75
-4
lines changed

2 files changed

+75
-4
lines changed

src/ngAnimate/animateQueue.js

+36-4
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,37 @@ var NG_ANIMATE_PIN_DATA = '$ngAnimatePin';
55
var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
66
var PRE_DIGEST_STATE = 1;
77
var RUNNING_STATE = 2;
8+
var ONE_SPACE = ' ';
89

910
var rules = this.rules = {
1011
skip: [],
1112
cancel: [],
1213
join: []
1314
};
1415

16+
function makeTruthyCssClassMap(classString) {
17+
if (!classString) {
18+
return null;
19+
}
20+
21+
var keys = classString.split(ONE_SPACE);
22+
var map = Object.create(null);
23+
24+
forEach(keys, function(key) {
25+
map[key] = true;
26+
});
27+
return map;
28+
}
29+
30+
function hasMatchingClasses(newClassString, currentClassString) {
31+
if (newClassString && currentClassString) {
32+
var currentClassMap = makeTruthyCssClassMap(currentClassString);
33+
return newClassString.split(ONE_SPACE).some(function(className) {
34+
return currentClassMap[className];
35+
});
36+
}
37+
}
38+
1539
function isAllowed(ruleType, element, currentAnimation, previousAnimation) {
1640
return rules[ruleType].some(function(fn) {
1741
return fn(element, currentAnimation, previousAnimation);
@@ -59,11 +83,19 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
5983
});
6084

6185
rules.cancel.push(function(element, newAnimation, currentAnimation) {
62-
var nO = newAnimation.options;
63-
var cO = currentAnimation.options;
6486

65-
// if the exact same CSS class is added/removed then it's safe to cancel it
66-
return (nO.addClass && nO.addClass === cO.removeClass) || (nO.removeClass && nO.removeClass === cO.addClass);
87+
88+
var nA = newAnimation.options.addClass;
89+
var nR = newAnimation.options.removeClass;
90+
var cA = currentAnimation.options.addClass;
91+
var cR = currentAnimation.options.removeClass;
92+
93+
// early detection to save the global CPU shortage :)
94+
if ((isUndefined(nA) && isUndefined(nR)) || (isUndefined(cA) && isUndefined(cR))) {
95+
return false;
96+
}
97+
98+
return (hasMatchingClasses(nA, cR)) || (hasMatchingClasses(nR, cA));
6799
});
68100

69101
this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',

test/ngAnimate/integrationSpec.js

+39
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,45 @@ describe('ngAnimate integration tests', function() {
351351

352352
dealoc(element);
353353
}));
354+
355+
356+
it("should remove a class when the same class is currently being added by a joined class-based animation",
357+
inject(function($animate, $animateCss, $rootScope, $document, $rootElement, $$rAF) {
358+
359+
ss.addRule('.hide', 'opacity: 0');
360+
ss.addRule('.hide-add, .hide-remove', 'transition: 1s linear all');
361+
362+
jqLite($document[0].body).append($rootElement);
363+
element = jqLite('<div></div>');
364+
$rootElement.append(element);
365+
366+
// These animations will be joined together
367+
$animate.addClass(element, 'red');
368+
$animate.addClass(element, 'hide');
369+
$rootScope.$digest();
370+
371+
expect(element).toHaveClass('red-add');
372+
expect(element).toHaveClass('hide-add');
373+
374+
// When a digest has passed, but no $rAF has been issued yet, .hide hasn't been added to
375+
// the element yet
376+
$animate.removeClass(element, 'hide');
377+
$rootScope.$digest();
378+
$$rAF.flush();
379+
380+
expect(element).not.toHaveClass('hide-add hide-add-active');
381+
expect(element).toHaveClass('hide-remove hide-remove-active');
382+
383+
//End the animation process
384+
browserTrigger(element, 'transitionend',
385+
{ timeStamp: Date.now() + 1000, elapsedTime: 2 });
386+
$animate.flush();
387+
388+
expect(element).not.toHaveClass('hide-add-active red-add-active');
389+
expect(element).toHaveClass('red');
390+
expect(element).not.toHaveClass('hide');
391+
}));
392+
354393
});
355394

356395
describe('JS animations', function() {

0 commit comments

Comments
 (0)