Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(animations): always render end-state styles for orphaned DOM nodes #24236

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -38,8 +38,8 @@ export class AnimationTransitionFactory {
build(
driver: AnimationDriver, element: any, currentState: any, nextState: any,
enterClassName: string, leaveClassName: string, currentOptions?: AnimationOptions,
nextOptions?: AnimationOptions,
subInstructions?: ElementInstructionMap): AnimationTransitionInstruction {
nextOptions?: AnimationOptions, subInstructions?: ElementInstructionMap,
skipAstBuild?: boolean): AnimationTransitionInstruction {
const errors: any[] = [];

const transitionAnimationParams = this.ast.options && this.ast.options.params || EMPTY_OBJECT;
Expand All @@ -55,9 +55,10 @@ export class AnimationTransitionFactory {

const animationOptions = {params: {...transitionAnimationParams, ...nextAnimationParams}};

const timelines = buildAnimationTimelines(
driver, element, this.ast.animation, enterClassName, leaveClassName, currentStateStyles,
nextStateStyles, animationOptions, subInstructions, errors);
const timelines = skipAstBuild ? [] : buildAnimationTimelines(
driver, element, this.ast.animation, enterClassName,
leaveClassName, currentStateStyles, nextStateStyles,
animationOptions, subInstructions, errors);

let totalTime = 0;
timelines.forEach(tl => { totalTime = Math.max(tl.duration + tl.delay, totalTime); });
Expand Down
Expand Up @@ -743,10 +743,10 @@ export class TransitionAnimationEngine {

private _buildInstruction(
entry: QueueInstruction, subTimelines: ElementInstructionMap, enterClassName: string,
leaveClassName: string) {
leaveClassName: string, skipBuildAst?: boolean) {
return entry.transition.build(
this.driver, entry.element, entry.fromState.value, entry.toState.value, enterClassName,
leaveClassName, entry.fromState.options, entry.toState.options, subTimelines);
leaveClassName, entry.fromState.options, entry.toState.options, subTimelines, skipBuildAst);
}

destroyInnerAnimations(containerElement: any) {
Expand Down Expand Up @@ -969,20 +969,27 @@ export class TransitionAnimationEngine {
}
}

if (!bodyNode || !this.driver.containsElement(bodyNode, element)) {
player.destroy();
return;
}

const nodeIsOrphaned = !bodyNode || !this.driver.containsElement(bodyNode, element);
const leaveClassName = leaveNodeMapIds.get(element) !;
const enterClassName = enterNodeMapIds.get(element) !;
const instruction =
this._buildInstruction(entry, subTimelines, enterClassName, leaveClassName) !;
const instruction = this._buildInstruction(
entry, subTimelines, enterClassName, leaveClassName, nodeIsOrphaned) !;
if (instruction.errors && instruction.errors.length) {
erroneousTransitions.push(instruction);
return;
}

// even though the element may not be apart of the DOM, it may
// still be added at a later point (due to the mechanics of content
// projection and/or dynamic component insertion) therefore it's
// important we still style the element.
if (nodeIsOrphaned) {
player.onStart(() => eraseStyles(element, instruction.fromStyles));
player.onDestroy(() => setStyles(element, instruction.toStyles));
skippedPlayers.push(player);
return;
}

// if a unmatched transition is queued to go then it SHOULD NOT render
// an animation and cancel the previously running animations.
if (entry.isFallbackTransition) {
Expand Down
Expand Up @@ -623,6 +623,23 @@ const DEFAULT_NAMESPACE_ID = 'id';
const TRIGGER = 'fooTrigger';
expect(() => { engine.trigger(ID, element, TRIGGER, 'something'); }).not.toThrow();
});

it('should still apply state-styling to an element even if it is not yet inserted into the DOM',
() => {
const engine = makeEngine();
const orphanElement = document.createElement('div');
orphanElement.classList.add('orphan');

registerTrigger(
orphanElement, engine, trigger('trig', [
state('go', style({opacity: 0.5})), transition('* => go', animate(1000))
]));

setProperty(orphanElement, engine, 'trig', 'go');
engine.flush();
expect(engine.players.length).toEqual(0);
expect(orphanElement.style.opacity).toEqual('0.5');
});
});
});
})();
Expand Down