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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user