fix(animations): retain state styling for nodes that are moved around (#23686)
PR Close #23686
This commit is contained in:
parent
4ddeb030e7
commit
05aa5e0179
@ -28,13 +28,15 @@ const STAR_SELECTOR = '.ng-star-inserted';
|
|||||||
const EMPTY_PLAYER_ARRAY: TransitionAnimationPlayer[] = [];
|
const EMPTY_PLAYER_ARRAY: TransitionAnimationPlayer[] = [];
|
||||||
const NULL_REMOVAL_STATE: ElementAnimationState = {
|
const NULL_REMOVAL_STATE: ElementAnimationState = {
|
||||||
namespaceId: '',
|
namespaceId: '',
|
||||||
setForRemoval: null,
|
setForRemoval: false,
|
||||||
|
setForMove: false,
|
||||||
hasAnimation: false,
|
hasAnimation: false,
|
||||||
removedBeforeQueried: false
|
removedBeforeQueried: false
|
||||||
};
|
};
|
||||||
const NULL_REMOVED_QUERIED_STATE: ElementAnimationState = {
|
const NULL_REMOVED_QUERIED_STATE: ElementAnimationState = {
|
||||||
namespaceId: '',
|
namespaceId: '',
|
||||||
setForRemoval: null,
|
setForMove: false,
|
||||||
|
setForRemoval: false,
|
||||||
hasAnimation: false,
|
hasAnimation: false,
|
||||||
removedBeforeQueried: true
|
removedBeforeQueried: true
|
||||||
};
|
};
|
||||||
@ -58,7 +60,8 @@ export interface QueueInstruction {
|
|||||||
export const REMOVAL_FLAG = '__ng_removed';
|
export const REMOVAL_FLAG = '__ng_removed';
|
||||||
|
|
||||||
export interface ElementAnimationState {
|
export interface ElementAnimationState {
|
||||||
setForRemoval: any;
|
setForRemoval: boolean;
|
||||||
|
setForMove: boolean;
|
||||||
hasAnimation: boolean;
|
hasAnimation: boolean;
|
||||||
namespaceId: string;
|
namespaceId: string;
|
||||||
removedBeforeQueried: boolean;
|
removedBeforeQueried: boolean;
|
||||||
@ -660,6 +663,11 @@ export class TransitionAnimationEngine {
|
|||||||
const details = element[REMOVAL_FLAG] as ElementAnimationState;
|
const details = element[REMOVAL_FLAG] as ElementAnimationState;
|
||||||
if (details && details.setForRemoval) {
|
if (details && details.setForRemoval) {
|
||||||
details.setForRemoval = false;
|
details.setForRemoval = false;
|
||||||
|
details.setForMove = true;
|
||||||
|
const index = this.collectedLeaveElements.indexOf(element);
|
||||||
|
if (index >= 0) {
|
||||||
|
this.collectedLeaveElements.splice(index, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// in the event that the namespaceId is blank then the caller
|
// in the event that the namespaceId is blank then the caller
|
||||||
@ -946,9 +954,18 @@ export class TransitionAnimationEngine {
|
|||||||
const ns = this._namespaceList[i];
|
const ns = this._namespaceList[i];
|
||||||
ns.drainQueuedTransitions(microtaskId).forEach(entry => {
|
ns.drainQueuedTransitions(microtaskId).forEach(entry => {
|
||||||
const player = entry.player;
|
const player = entry.player;
|
||||||
|
const element = entry.element;
|
||||||
allPlayers.push(player);
|
allPlayers.push(player);
|
||||||
|
|
||||||
const element = entry.element;
|
if (this.collectedEnterElements.length) {
|
||||||
|
const details = element[REMOVAL_FLAG] as ElementAnimationState;
|
||||||
|
// move animations are currently not supported...
|
||||||
|
if (details && details.setForMove) {
|
||||||
|
player.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!bodyNode || !this.driver.containsElement(bodyNode, element)) {
|
if (!bodyNode || !this.driver.containsElement(bodyNode, element)) {
|
||||||
player.destroy();
|
player.destroy();
|
||||||
return;
|
return;
|
||||||
|
@ -1453,6 +1453,117 @@ const DEFAULT_COMPONENT_ID = '1';
|
|||||||
.toBeTruthy();
|
.toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should retain state styles when the underlying DOM structure changes even if there are no insert/remove animations',
|
||||||
|
() => {
|
||||||
|
@Component({
|
||||||
|
selector: 'ani-cmp',
|
||||||
|
template: `
|
||||||
|
<div class="item" *ngFor="let item of items" [@color]="colorExp">
|
||||||
|
{{ item }}
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
animations: [trigger('color', [state('green', style({backgroundColor: 'green'}))])]
|
||||||
|
})
|
||||||
|
class Cmp {
|
||||||
|
public colorExp = 'green';
|
||||||
|
public items = [0, 1, 2, 3];
|
||||||
|
|
||||||
|
reorder() {
|
||||||
|
const temp = this.items[0];
|
||||||
|
this.items[0] = this.items[1];
|
||||||
|
this.items[1] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||||
|
|
||||||
|
const fixture = TestBed.createComponent(Cmp);
|
||||||
|
const cmp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let elements: HTMLElement[] = fixture.nativeElement.querySelectorAll('.item');
|
||||||
|
assertBackgroundColor(elements[0], 'green');
|
||||||
|
assertBackgroundColor(elements[1], 'green');
|
||||||
|
assertBackgroundColor(elements[2], 'green');
|
||||||
|
assertBackgroundColor(elements[3], 'green');
|
||||||
|
|
||||||
|
elements[0].title = '0a';
|
||||||
|
elements[1].title = '1a';
|
||||||
|
|
||||||
|
cmp.reorder();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
elements = fixture.nativeElement.querySelectorAll('.item');
|
||||||
|
assertBackgroundColor(elements[0], 'green');
|
||||||
|
assertBackgroundColor(elements[1], 'green');
|
||||||
|
assertBackgroundColor(elements[2], 'green');
|
||||||
|
assertBackgroundColor(elements[3], 'green');
|
||||||
|
|
||||||
|
function assertBackgroundColor(element: HTMLElement, color: string) {
|
||||||
|
expect(element.style.getPropertyValue('background-color')).toEqual(color);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retain state styles when the underlying DOM structure changes even if there are insert/remove animations',
|
||||||
|
() => {
|
||||||
|
@Component({
|
||||||
|
selector: 'ani-cmp',
|
||||||
|
template: `
|
||||||
|
<div class="item" *ngFor="let item of items" [@color]="colorExp">
|
||||||
|
{{ item }}
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
animations: [trigger(
|
||||||
|
'color',
|
||||||
|
[
|
||||||
|
transition('* => *', animate(500)),
|
||||||
|
state('green', style({backgroundColor: 'green'}))
|
||||||
|
])]
|
||||||
|
})
|
||||||
|
class Cmp {
|
||||||
|
public colorExp = 'green';
|
||||||
|
public items = [0, 1, 2, 3];
|
||||||
|
|
||||||
|
reorder() {
|
||||||
|
const temp = this.items[0];
|
||||||
|
this.items[0] = this.items[1];
|
||||||
|
this.items[1] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||||
|
|
||||||
|
const fixture = TestBed.createComponent(Cmp);
|
||||||
|
const cmp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
getLog().forEach(p => p.finish());
|
||||||
|
|
||||||
|
let elements: HTMLElement[] = fixture.nativeElement.querySelectorAll('.item');
|
||||||
|
assertBackgroundColor(elements[0], 'green');
|
||||||
|
assertBackgroundColor(elements[1], 'green');
|
||||||
|
assertBackgroundColor(elements[2], 'green');
|
||||||
|
assertBackgroundColor(elements[3], 'green');
|
||||||
|
|
||||||
|
elements[0].title = '0a';
|
||||||
|
elements[1].title = '1a';
|
||||||
|
|
||||||
|
cmp.reorder();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
getLog().forEach(p => p.finish());
|
||||||
|
|
||||||
|
elements = fixture.nativeElement.querySelectorAll('.item');
|
||||||
|
assertBackgroundColor(elements[0], 'green');
|
||||||
|
assertBackgroundColor(elements[1], 'green');
|
||||||
|
assertBackgroundColor(elements[2], 'green');
|
||||||
|
assertBackgroundColor(elements[3], 'green');
|
||||||
|
|
||||||
|
function assertBackgroundColor(element: HTMLElement, color: string) {
|
||||||
|
expect(element.style.getPropertyValue('background-color')).toEqual(color);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('should animate removals of nodes to the `void` state for each animation trigger, but treat all auto styles as pre styles',
|
it('should animate removals of nodes to the `void` state for each animation trigger, but treat all auto styles as pre styles',
|
||||||
() => {
|
() => {
|
||||||
@Component({
|
@Component({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user