mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-16 23:30:23 +00:00
fix(ngAnimate): ensure that delays are always considered before an animation closes
Closes #4028
This commit is contained in:
parent
acc2fb8486
commit
0a63adce68
2 changed files with 95 additions and 45 deletions
|
|
@ -560,6 +560,7 @@ angular.module('ngAnimate', ['ng'])
|
|||
|
||||
var durationKey = 'Duration',
|
||||
propertyKey = 'Property',
|
||||
delayKey = 'Delay',
|
||||
animationIterationCountKey = 'IterationCount',
|
||||
ELEMENT_NODE = 1;
|
||||
|
||||
|
|
@ -593,6 +594,12 @@ angular.module('ngAnimate', ['ng'])
|
|||
if (element.nodeType == ELEMENT_NODE) {
|
||||
var elementStyles = $window.getComputedStyle(element) || {};
|
||||
|
||||
var transitionDelay = Math.max(parseMaxTime(elementStyles[w3cTransitionProp + delayKey]),
|
||||
parseMaxTime(elementStyles[vendorTransitionProp + delayKey]));
|
||||
|
||||
var animationDelay = Math.max(parseMaxTime(elementStyles[w3cAnimationProp + delayKey]),
|
||||
parseMaxTime(elementStyles[vendorAnimationProp + delayKey]));
|
||||
|
||||
var transitionDuration = Math.max(parseMaxTime(elementStyles[w3cTransitionProp + durationKey]),
|
||||
parseMaxTime(elementStyles[vendorTransitionProp + durationKey]));
|
||||
|
||||
|
|
@ -605,17 +612,18 @@ angular.module('ngAnimate', ['ng'])
|
|||
1);
|
||||
}
|
||||
|
||||
transitionTime = Math.max(transitionDuration, transitionTime);
|
||||
animationTime = Math.max(animationDuration, animationTime);
|
||||
transitionTime = Math.max(transitionDelay + transitionDuration, transitionTime);
|
||||
animationTime = Math.max(animationDelay + animationDuration, animationTime);
|
||||
}
|
||||
});
|
||||
|
||||
/* there is no point in performing a reflow if the animation
|
||||
timeout is empty (this would cause a flicker bug normally
|
||||
in the page */
|
||||
var totalTime = Math.max(transitionTime,animationTime);
|
||||
if(totalTime > 0) {
|
||||
var node = element[0];
|
||||
var maxTime = Math.max(transitionTime,animationTime) * 1000;
|
||||
if(maxTime > 0) {
|
||||
var node = element[0],
|
||||
startTime = Date.now();
|
||||
|
||||
//temporarily disable the transition so that the enter styles
|
||||
//don't animate twice (this is here to avoid a bug in Chrome/FF).
|
||||
|
|
@ -659,7 +667,14 @@ angular.module('ngAnimate', ['ng'])
|
|||
}
|
||||
|
||||
function onAnimationProgress(event) {
|
||||
(event.elapsedTime || (event.originalEvent && event.originalEvent.elapsedTime)) >= totalTime && done();
|
||||
event.stopPropagation();
|
||||
var ev = event.originalEvent || event;
|
||||
/* $manualTimeStamp is a mocked timeStamp value which is set
|
||||
* within browserTrigger(). This is only here so that tests can
|
||||
* mock animations properly. Real events fallback to event.timeStamp. */
|
||||
if((ev.$manualTimeStamp || ev.timeStamp) - startTime >= maxTime) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
function parseMaxTime(str) {
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ describe("ngAnimate", function() {
|
|||
if($sniffer.transitions) {
|
||||
expect(child.hasClass('ng-enter')).toBe(true);
|
||||
expect(child.hasClass('ng-enter-active')).toBe(true);
|
||||
browserTrigger(element, 'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(element, 'transitionend', { timeStamp: Date.now() + 1000 });
|
||||
}
|
||||
|
||||
expect(element.contents().length).toBe(1);
|
||||
|
|
@ -154,7 +154,7 @@ describe("ngAnimate", function() {
|
|||
if($sniffer.transitions) {
|
||||
expect(child.hasClass('ng-leave')).toBe(true);
|
||||
expect(child.hasClass('ng-leave-active')).toBe(true);
|
||||
browserTrigger(child,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 });
|
||||
}
|
||||
|
||||
expect(element.contents().length).toBe(0);
|
||||
|
|
@ -186,7 +186,7 @@ describe("ngAnimate", function() {
|
|||
if($sniffer.transitions) {
|
||||
expect(child.hasClass('ng-hide-remove')).toBe(true);
|
||||
expect(child.hasClass('ng-hide-remove-active')).toBe(true);
|
||||
browserTrigger(child,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 });
|
||||
}
|
||||
expect(child.hasClass('ng-hide-remove')).toBe(false);
|
||||
expect(child.hasClass('ng-hide-remove-active')).toBe(false);
|
||||
|
|
@ -202,7 +202,7 @@ describe("ngAnimate", function() {
|
|||
if($sniffer.transitions) {
|
||||
expect(child.hasClass('ng-hide-add')).toBe(true);
|
||||
expect(child.hasClass('ng-hide-add-active')).toBe(true);
|
||||
browserTrigger(child,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 });
|
||||
}
|
||||
expect(child).toBeHidden();
|
||||
}));
|
||||
|
|
@ -221,7 +221,7 @@ describe("ngAnimate", function() {
|
|||
|
||||
expect(child.attr('class')).toContain('ng-enter');
|
||||
expect(child.attr('class')).toContain('ng-enter-active');
|
||||
browserTrigger(child,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 });
|
||||
|
||||
//move
|
||||
element.append(after);
|
||||
|
|
@ -230,26 +230,26 @@ describe("ngAnimate", function() {
|
|||
|
||||
expect(child.attr('class')).toContain('ng-move');
|
||||
expect(child.attr('class')).toContain('ng-move-active');
|
||||
browserTrigger(child,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 });
|
||||
|
||||
//hide
|
||||
$animate.addClass(child, 'ng-hide');
|
||||
expect(child.attr('class')).toContain('ng-hide-add');
|
||||
expect(child.attr('class')).toContain('ng-hide-add-active');
|
||||
browserTrigger(child,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 });
|
||||
|
||||
//show
|
||||
$animate.removeClass(child, 'ng-hide');
|
||||
expect(child.attr('class')).toContain('ng-hide-remove');
|
||||
expect(child.attr('class')).toContain('ng-hide-remove-active');
|
||||
browserTrigger(child,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 });
|
||||
|
||||
//leave
|
||||
$animate.leave(child);
|
||||
$rootScope.$digest();
|
||||
expect(child.attr('class')).toContain('ng-leave');
|
||||
expect(child.attr('class')).toContain('ng-leave-active');
|
||||
browserTrigger(child,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 });
|
||||
}));
|
||||
|
||||
it("should not run if animations are disabled",
|
||||
|
|
@ -292,7 +292,7 @@ describe("ngAnimate", function() {
|
|||
|
||||
if($sniffer.transitions) {
|
||||
expect(element.children().length).toBe(1); //still animating
|
||||
browserTrigger(child,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 });
|
||||
}
|
||||
$timeout.flush(2000);
|
||||
$timeout.flush(2000);
|
||||
|
|
@ -309,7 +309,7 @@ describe("ngAnimate", function() {
|
|||
child.addClass('custom-delay ng-hide');
|
||||
$animate.removeClass(child, 'ng-hide');
|
||||
if($sniffer.transitions) {
|
||||
browserTrigger(child,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 });
|
||||
}
|
||||
$timeout.flush(2000);
|
||||
|
||||
|
|
@ -373,7 +373,7 @@ describe("ngAnimate", function() {
|
|||
$animate.removeClass(element, 'ng-hide');
|
||||
|
||||
if($sniffer.transitions) {
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000 });
|
||||
}
|
||||
$timeout.flush(2000);
|
||||
$timeout.flush(20000);
|
||||
|
|
@ -416,7 +416,7 @@ describe("ngAnimate", function() {
|
|||
|
||||
$animate.removeClass(element, 'ng-hide');
|
||||
if ($sniffer.animations) {
|
||||
browserTrigger(element,'animationend', { elapsedTime: 4 });
|
||||
browserTrigger(element,'animationend', { timeStamp: Date.now() + 4000 });
|
||||
}
|
||||
expect(element).toBeShown();
|
||||
}));
|
||||
|
|
@ -439,7 +439,7 @@ describe("ngAnimate", function() {
|
|||
|
||||
$animate.removeClass(element, 'ng-hide');
|
||||
if ($sniffer.animations) {
|
||||
browserTrigger(element,'animationend', { elapsedTime: 6 });
|
||||
browserTrigger(element,'animationend', { timeStamp: Date.now() + 6000 });
|
||||
}
|
||||
expect(element).toBeShown();
|
||||
}));
|
||||
|
|
@ -462,7 +462,7 @@ describe("ngAnimate", function() {
|
|||
|
||||
$animate.removeClass(element, 'ng-hide');
|
||||
if ($sniffer.animations) {
|
||||
browserTrigger(element,'animationend', { elapsedTime: 2 });
|
||||
browserTrigger(element,'animationend', { timeStamp: Date.now() + 2000 });
|
||||
}
|
||||
expect(element).toBeShown();
|
||||
}));
|
||||
|
|
@ -487,7 +487,7 @@ describe("ngAnimate", function() {
|
|||
|
||||
$animate.removeClass(element, 'ng-hide');
|
||||
if ($sniffer.transitions) {
|
||||
browserTrigger(element,'animationend', { elapsedTime: 10 });
|
||||
browserTrigger(element,'animationend', { timeStamp : Date.now() + 20000 });
|
||||
}
|
||||
expect(element).toBeShown();
|
||||
}));
|
||||
|
|
@ -533,7 +533,7 @@ describe("ngAnimate", function() {
|
|||
if($sniffer.animations) { //cleanup some pending animations
|
||||
expect(element.hasClass('ng-hide-add')).toBe(true);
|
||||
expect(element.hasClass('ng-hide-add-active')).toBe(true);
|
||||
browserTrigger(element,'animationend', { elapsedTime: 2 });
|
||||
browserTrigger(element,'animationend', { timeStamp: Date.now() + 2000 });
|
||||
}
|
||||
|
||||
expect(element.hasClass('ng-hide-remove-active')).toBe(false);
|
||||
|
|
@ -566,7 +566,7 @@ describe("ngAnimate", function() {
|
|||
|
||||
$animate.removeClass(element, 'ng-hide');
|
||||
if ($sniffer.transitions) {
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000 });
|
||||
}
|
||||
expect(element).toBeShown();
|
||||
}));
|
||||
|
|
@ -587,9 +587,10 @@ describe("ngAnimate", function() {
|
|||
$animate.removeClass(element, 'ng-hide');
|
||||
if ($sniffer.transitions) {
|
||||
expect(element).toBeHidden();
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 2 });
|
||||
var now = Date.now();
|
||||
browserTrigger(element,'transitionend', { timeStamp: now + 1000 });
|
||||
browserTrigger(element,'transitionend', { timeStamp: now + 1000 });
|
||||
browserTrigger(element,'transitionend', { timeStamp: now + 2000 });
|
||||
}
|
||||
expect(element).toBeShown();
|
||||
}));
|
||||
|
|
@ -620,9 +621,10 @@ describe("ngAnimate", function() {
|
|||
|
||||
$animate.removeClass(element, 'ng-hide');
|
||||
if ($sniffer.transitions) {
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 0 });
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 1 });
|
||||
var now = Date.now();
|
||||
browserTrigger(element,'transitionend', { timeStamp: now + 1000 });
|
||||
browserTrigger(element,'transitionend', { timeStamp: now + 3000 });
|
||||
browserTrigger(element,'transitionend', { timeStamp: now + 3000 });
|
||||
}
|
||||
expect(element).toBeShown();
|
||||
}));
|
||||
|
|
@ -644,7 +646,7 @@ describe("ngAnimate", function() {
|
|||
|
||||
$animate.removeClass(element, 'ng-hide');
|
||||
if ($sniffer.transitions) {
|
||||
browserTrigger(element,'animationend', { elapsedTime: 10 });
|
||||
browserTrigger(element,'animationend', { timeStamp: Date.now() + 11000 });
|
||||
}
|
||||
expect(element).toBeShown();
|
||||
}));
|
||||
|
|
@ -664,7 +666,7 @@ describe("ngAnimate", function() {
|
|||
if($sniffer.transitions) {
|
||||
expect(element.hasClass('ng-hide-remove')).toBe(true);
|
||||
expect(element.hasClass('ng-hide-remove-active')).toBe(true);
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000 });
|
||||
}
|
||||
expect(element.hasClass('ng-hide-remove')).toBe(false);
|
||||
expect(element.hasClass('ng-hide-remove-active')).toBe(false);
|
||||
|
|
@ -700,7 +702,7 @@ describe("ngAnimate", function() {
|
|||
if ($sniffer.transitions) {
|
||||
expect(element.hasClass('abc ng-enter')).toBe(true);
|
||||
expect(element.hasClass('abc ng-enter ng-enter-active')).toBe(true);
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 22 });
|
||||
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 22000 });
|
||||
}
|
||||
expect(element.hasClass('abc')).toBe(true);
|
||||
|
||||
|
|
@ -711,7 +713,7 @@ describe("ngAnimate", function() {
|
|||
if ($sniffer.transitions) {
|
||||
expect(element.hasClass('xyz')).toBe(true);
|
||||
expect(element.hasClass('xyz ng-enter ng-enter-active')).toBe(true);
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 11 });
|
||||
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 11000 });
|
||||
}
|
||||
expect(element.hasClass('xyz')).toBe(true);
|
||||
}));
|
||||
|
|
@ -738,7 +740,7 @@ describe("ngAnimate", function() {
|
|||
expect(element.hasClass('one two ng-enter ng-enter-active')).toBe(true);
|
||||
expect(element.hasClass('one-active')).toBe(false);
|
||||
expect(element.hasClass('two-active')).toBe(false);
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 3 });
|
||||
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 3000 });
|
||||
}
|
||||
|
||||
expect(element.hasClass('one two')).toBe(true);
|
||||
|
|
@ -887,7 +889,7 @@ describe("ngAnimate", function() {
|
|||
});
|
||||
|
||||
if($sniffer.transitions) {
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000 });
|
||||
}
|
||||
$timeout.flush();
|
||||
expect(flag).toBe(true);
|
||||
|
|
@ -1025,7 +1027,7 @@ describe("ngAnimate", function() {
|
|||
expect(element.hasClass('klass')).toBe(false);
|
||||
expect(element.hasClass('klass-add')).toBe(true);
|
||||
expect(element.hasClass('klass-add-active')).toBe(true);
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 3 });
|
||||
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 3000 });
|
||||
}
|
||||
|
||||
//this cancels out the older animation
|
||||
|
|
@ -1039,7 +1041,7 @@ describe("ngAnimate", function() {
|
|||
expect(element.hasClass('klass-add-active')).toBe(false);
|
||||
|
||||
expect(element.hasClass('klass-remove')).toBe(true);
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 3 });
|
||||
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 3000 });
|
||||
}
|
||||
$timeout.flush();
|
||||
|
||||
|
|
@ -1097,7 +1099,7 @@ describe("ngAnimate", function() {
|
|||
if($sniffer.transitions) {
|
||||
expect(element.hasClass('klass-add')).toBe(true);
|
||||
expect(element.hasClass('klass-add-active')).toBe(true);
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 11 });
|
||||
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 11000 });
|
||||
expect(element.hasClass('klass-add')).toBe(false);
|
||||
expect(element.hasClass('klass-add-active')).toBe(false);
|
||||
}
|
||||
|
|
@ -1111,7 +1113,7 @@ describe("ngAnimate", function() {
|
|||
if($sniffer.transitions) {
|
||||
expect(element.hasClass('klass-remove')).toBe(true);
|
||||
expect(element.hasClass('klass-remove-active')).toBe(true);
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 11 });
|
||||
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 11000 });
|
||||
expect(element.hasClass('klass-remove')).toBe(false);
|
||||
expect(element.hasClass('klass-remove-active')).toBe(false);
|
||||
}
|
||||
|
|
@ -1146,7 +1148,7 @@ describe("ngAnimate", function() {
|
|||
|
||||
expect(element.hasClass('one-add-active')).toBe(true);
|
||||
expect(element.hasClass('two-add-active')).toBe(true);
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 7 });
|
||||
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 7000 });
|
||||
|
||||
expect(element.hasClass('one-add')).toBe(false);
|
||||
expect(element.hasClass('one-add-active')).toBe(false);
|
||||
|
|
@ -1190,7 +1192,7 @@ describe("ngAnimate", function() {
|
|||
|
||||
expect(element.hasClass('one-remove-active')).toBe(true);
|
||||
expect(element.hasClass('two-remove-active')).toBe(true);
|
||||
browserTrigger(element,'transitionend', { elapsedTime: 9 });
|
||||
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 9000 });
|
||||
|
||||
expect(element.hasClass('one-remove')).toBe(false);
|
||||
expect(element.hasClass('one-remove-active')).toBe(false);
|
||||
|
|
@ -1240,7 +1242,7 @@ describe("ngAnimate", function() {
|
|||
if($sniffer.transitions) {
|
||||
expect(child.hasClass('ng-enter')).toBe(true);
|
||||
expect(child.hasClass('ng-enter-active')).toBe(true);
|
||||
browserTrigger(child,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 });
|
||||
}
|
||||
|
||||
expect(child.hasClass('ng-enter')).toBe(false);
|
||||
|
|
@ -1262,7 +1264,7 @@ describe("ngAnimate", function() {
|
|||
if($sniffer.transitions) {
|
||||
expect(child.hasClass('ng-enter')).toBe(true);
|
||||
expect(child.hasClass('ng-enter-active')).toBe(true);
|
||||
browserTrigger(child,'transitionend', { elapsedTime: 8 });
|
||||
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 9000 });
|
||||
}
|
||||
expect(child.hasClass('ng-enter')).toBe(false);
|
||||
expect(child.hasClass('ng-enter-active')).toBe(false);
|
||||
|
|
@ -1314,7 +1316,7 @@ describe("ngAnimate", function() {
|
|||
$timeout.flush(10);
|
||||
|
||||
if($sniffer.transitions) {
|
||||
browserTrigger(child,'transitionend', { elapsedTime: 1 });
|
||||
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000 });
|
||||
}
|
||||
|
||||
expect(child.hasClass('i-was-animated')).toBe(true);
|
||||
|
|
@ -1522,4 +1524,37 @@ describe("ngAnimate", function() {
|
|||
});
|
||||
});
|
||||
|
||||
it("should wait until both the duration and delay are complete to close off the animation",
|
||||
inject(function($compile, $rootScope, $animate, $timeout, $sniffer) {
|
||||
|
||||
if(!$sniffer.transitions) return;
|
||||
|
||||
var element = html($compile('<div class="animated parent"></div>')($rootScope));
|
||||
var child = html($compile('<div class="animated child"></div>')($rootScope));
|
||||
|
||||
ss.addRule('.animated.ng-enter', 'transition: width 1s, background 1s 1s;' +
|
||||
vendorPrefix + 'transition: width 1s, background 1s 1s;');
|
||||
|
||||
$rootElement.append(element);
|
||||
jqLite(document.body).append($rootElement);
|
||||
|
||||
$animate.enter(child, element);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(child.hasClass('ng-enter')).toBe(true);
|
||||
expect(child.hasClass('ng-enter-active')).toBe(true);
|
||||
|
||||
browserTrigger(child, 'transitionend', { timeStamp: Date.now() + 1000 });
|
||||
|
||||
expect(child.hasClass('ng-enter')).toBe(true);
|
||||
expect(child.hasClass('ng-enter-active')).toBe(true);
|
||||
|
||||
browserTrigger(child, 'transitionend', { timeStamp: Date.now() + 2000 });
|
||||
|
||||
expect(child.hasClass('ng-enter')).toBe(false);
|
||||
expect(child.hasClass('ng-enter-active')).toBe(false);
|
||||
|
||||
expect(element.contents().length).toBe(1);
|
||||
}));
|
||||
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue