fix(animations): properly collect :enter nodes in a partially updated collection
This PR fixes an issue where `query(':enter')` will only collect elements up until it an element that is found that isn't apart of the `:enter` query. Closes #17440
This commit is contained in:
parent
185075d870
commit
6ca46929fa
|
@ -1249,6 +1249,17 @@ function cloakElement(element: any, value?: string) {
|
||||||
return oldValue;
|
return oldValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. start from the root, find the first matching child
|
||||||
|
a) if not found then check to see if a previously stopped node was set in the stack
|
||||||
|
-> if so then use that as the nextCursor
|
||||||
|
b) if no queried item and no parent then stop completely
|
||||||
|
c) if no queried item and yes parent then jump to the parent and restart loop
|
||||||
|
2. visit the next node, check if matches
|
||||||
|
a) if doesn't exist then set that as the cursor and repeat
|
||||||
|
-> add to the previous cursor stack when the inner queries return nothing
|
||||||
|
b) if matches then add it and continue
|
||||||
|
*/
|
||||||
function filterNodeClasses(
|
function filterNodeClasses(
|
||||||
driver: AnimationDriver, rootElement: any | null, selector: string): any[] {
|
driver: AnimationDriver, rootElement: any | null, selector: string): any[] {
|
||||||
const rootElements: any[] = [];
|
const rootElements: any[] = [];
|
||||||
|
@ -1256,23 +1267,53 @@ function filterNodeClasses(
|
||||||
|
|
||||||
let cursor: any = rootElement;
|
let cursor: any = rootElement;
|
||||||
let nextCursor: any = {};
|
let nextCursor: any = {};
|
||||||
|
let potentialCursorStack: any[] = [];
|
||||||
do {
|
do {
|
||||||
nextCursor = driver.query(cursor, selector, false)[0];
|
// 1. query from root
|
||||||
|
nextCursor = cursor ? driver.query(cursor, selector, false)[0] : null;
|
||||||
|
|
||||||
|
// this is used to avoid the extra matchesElement call when we
|
||||||
|
// know that the element does match based it on being queried
|
||||||
|
let justQueried = !!nextCursor;
|
||||||
|
|
||||||
if (!nextCursor) {
|
if (!nextCursor) {
|
||||||
cursor = cursor.parentElement;
|
const nextPotentialCursor = potentialCursorStack.pop();
|
||||||
if (!cursor) break;
|
if (nextPotentialCursor) {
|
||||||
nextCursor = cursor = cursor.nextElementSibling;
|
// 1a)
|
||||||
} else {
|
nextCursor = nextPotentialCursor;
|
||||||
while (nextCursor && driver.matchesElement(nextCursor, selector)) {
|
} else {
|
||||||
rootElements.push(nextCursor);
|
cursor = cursor.parentElement;
|
||||||
nextCursor = nextCursor.nextElementSibling;
|
// 1b)
|
||||||
if (nextCursor) {
|
if (!cursor) break;
|
||||||
cursor = nextCursor;
|
// 1c)
|
||||||
} else {
|
nextCursor = cursor = cursor.nextElementSibling;
|
||||||
cursor = cursor.parentElement;
|
continue;
|
||||||
if (!cursor) break;
|
}
|
||||||
nextCursor = cursor = cursor.nextElementSibling;
|
}
|
||||||
}
|
|
||||||
|
// 2. visit the next node
|
||||||
|
while (nextCursor) {
|
||||||
|
const matches = justQueried || driver.matchesElement(nextCursor, selector);
|
||||||
|
justQueried = false;
|
||||||
|
|
||||||
|
const nextPotentialCursor = nextCursor.nextElementSibling;
|
||||||
|
|
||||||
|
// 2a)
|
||||||
|
if (!matches) {
|
||||||
|
potentialCursorStack.push(nextPotentialCursor);
|
||||||
|
cursor = nextCursor;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2b)
|
||||||
|
rootElements.push(nextCursor);
|
||||||
|
nextCursor = nextPotentialCursor;
|
||||||
|
if (nextCursor) {
|
||||||
|
cursor = nextCursor;
|
||||||
|
} else {
|
||||||
|
cursor = cursor.parentElement;
|
||||||
|
if (!cursor) break;
|
||||||
|
nextCursor = cursor = cursor.nextElementSibling;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (nextCursor && nextCursor !== rootElement);
|
} while (nextCursor && nextCursor !== rootElement);
|
||||||
|
|
|
@ -735,6 +735,56 @@ export function main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should find :enter nodes that have been inserted around non enter nodes', () => {
|
||||||
|
@Component({
|
||||||
|
selector: 'ani-cmp',
|
||||||
|
template: `
|
||||||
|
<div [@myAnimation]="exp" class="parent">
|
||||||
|
<div *ngFor="let item of items" class="child">
|
||||||
|
{{ item }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
animations: [trigger(
|
||||||
|
'myAnimation',
|
||||||
|
[
|
||||||
|
transition(
|
||||||
|
'* => go',
|
||||||
|
[query(':enter', [style({opacity: 0}), animate(1000, style({opacity: 1}))])]),
|
||||||
|
])]
|
||||||
|
})
|
||||||
|
class Cmp {
|
||||||
|
public exp: any;
|
||||||
|
public items: any[] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||||
|
|
||||||
|
const engine = TestBed.get(ɵAnimationEngine);
|
||||||
|
const fixture = TestBed.createComponent(Cmp);
|
||||||
|
const cmp = fixture.componentInstance;
|
||||||
|
|
||||||
|
cmp.exp = 'no';
|
||||||
|
cmp.items = [2];
|
||||||
|
fixture.detectChanges();
|
||||||
|
engine.flush();
|
||||||
|
resetLog();
|
||||||
|
|
||||||
|
cmp.exp = 'go';
|
||||||
|
cmp.items = [0, 1, 2, 3, 4];
|
||||||
|
fixture.detectChanges();
|
||||||
|
engine.flush();
|
||||||
|
|
||||||
|
const players = getLog();
|
||||||
|
expect(players.length).toEqual(4);
|
||||||
|
|
||||||
|
const [p1, p2, p3, p4] = players;
|
||||||
|
expect(p1.element.innerText.trim()).toEqual('0');
|
||||||
|
expect(p2.element.innerText.trim()).toEqual('1');
|
||||||
|
expect(p3.element.innerText.trim()).toEqual('3');
|
||||||
|
expect(p4.element.innerText.trim()).toEqual('4');
|
||||||
|
});
|
||||||
|
|
||||||
it('should properly cancel items that were queried into a former animation', () => {
|
it('should properly cancel items that were queried into a former animation', () => {
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ani-cmp',
|
selector: 'ani-cmp',
|
||||||
|
|
Loading…
Reference in New Issue