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

Commit 776972e

Browse files
Narretzpetebacondarwin
authored andcommitted
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 1358b3c commit 776972e

File tree

2 files changed

+75
-4
lines changed

2 files changed

+75
-4
lines changed

Diff for: 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',

Diff for: test/ngAnimate/integrationSpec.js

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

388388
dealoc(element);
389389
}));
390+
391+
392+
it("should remove a class when the same class is currently being added by a joined class-based animation",
393+
inject(function($animate, $animateCss, $rootScope, $document, $rootElement, $$rAF) {
394+
395+
ss.addRule('.hide', 'opacity: 0');
396+
ss.addRule('.hide-add, .hide-remove', 'transition: 1s linear all');
397+
398+
jqLite($document[0].body).append($rootElement);
399+
element = jqLite('<div></div>');
400+
$rootElement.append(element);
401+
402+
// These animations will be joined together
403+
$animate.addClass(element, 'red');
404+
$animate.addClass(element, 'hide');
405+
$rootScope.$digest();
406+
407+
expect(element).toHaveClass('red-add');
408+
expect(element).toHaveClass('hide-add');
409+
410+
// When a digest has passed, but no $rAF has been issued yet, .hide hasn't been added to
411+
// the element yet
412+
$animate.removeClass(element, 'hide');
413+
$rootScope.$digest();
414+
$$rAF.flush();
415+
416+
expect(element).not.toHaveClass('hide-add hide-add-active');
417+
expect(element).toHaveClass('hide-remove hide-remove-active');
418+
419+
//End the animation process
420+
browserTrigger(element, 'transitionend',
421+
{ timeStamp: Date.now() + 1000, elapsedTime: 2 });
422+
$animate.flush();
423+
424+
expect(element).not.toHaveClass('hide-add-active red-add-active');
425+
expect(element).toHaveClass('red');
426+
expect(element).not.toHaveClass('hide');
427+
}));
428+
390429
});
391430

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

0 commit comments

Comments
 (0)