fix(animations): always render end-state styles for orphaned DOM nodes (#24236)
This patch ensures that any destination animation styling (state values) are always applied even if the DOM node is not apart of the DOM. PR Close #24236
This commit is contained in:
parent
8aa70c2477
commit
dc4a3d00d0
|
@ -38,8 +38,8 @@ export class AnimationTransitionFactory {
|
||||||
build(
|
build(
|
||||||
driver: AnimationDriver, element: any, currentState: any, nextState: any,
|
driver: AnimationDriver, element: any, currentState: any, nextState: any,
|
||||||
enterClassName: string, leaveClassName: string, currentOptions?: AnimationOptions,
|
enterClassName: string, leaveClassName: string, currentOptions?: AnimationOptions,
|
||||||
nextOptions?: AnimationOptions,
|
nextOptions?: AnimationOptions, subInstructions?: ElementInstructionMap,
|
||||||
subInstructions?: ElementInstructionMap): AnimationTransitionInstruction {
|
skipAstBuild?: boolean): AnimationTransitionInstruction {
|
||||||
const errors: any[] = [];
|
const errors: any[] = [];
|
||||||
|
|
||||||
const transitionAnimationParams = this.ast.options && this.ast.options.params || EMPTY_OBJECT;
|
const transitionAnimationParams = this.ast.options && this.ast.options.params || EMPTY_OBJECT;
|
||||||
|
@ -55,9 +55,10 @@ export class AnimationTransitionFactory {
|
||||||
|
|
||||||
const animationOptions = {params: {...transitionAnimationParams, ...nextAnimationParams}};
|
const animationOptions = {params: {...transitionAnimationParams, ...nextAnimationParams}};
|
||||||
|
|
||||||
const timelines = buildAnimationTimelines(
|
const timelines = skipAstBuild ? [] : buildAnimationTimelines(
|
||||||
driver, element, this.ast.animation, enterClassName, leaveClassName, currentStateStyles,
|
driver, element, this.ast.animation, enterClassName,
|
||||||
nextStateStyles, animationOptions, subInstructions, errors);
|
leaveClassName, currentStateStyles, nextStateStyles,
|
||||||
|
animationOptions, subInstructions, errors);
|
||||||
|
|
||||||
let totalTime = 0;
|
let totalTime = 0;
|
||||||
timelines.forEach(tl => { totalTime = Math.max(tl.duration + tl.delay, totalTime); });
|
timelines.forEach(tl => { totalTime = Math.max(tl.duration + tl.delay, totalTime); });
|
||||||
|
|
|
@ -740,10 +740,10 @@ export class TransitionAnimationEngine {
|
||||||
|
|
||||||
private _buildInstruction(
|
private _buildInstruction(
|
||||||
entry: QueueInstruction, subTimelines: ElementInstructionMap, enterClassName: string,
|
entry: QueueInstruction, subTimelines: ElementInstructionMap, enterClassName: string,
|
||||||
leaveClassName: string) {
|
leaveClassName: string, skipBuildAst?: boolean) {
|
||||||
return entry.transition.build(
|
return entry.transition.build(
|
||||||
this.driver, entry.element, entry.fromState.value, entry.toState.value, enterClassName,
|
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) {
|
destroyInnerAnimations(containerElement: any) {
|
||||||
|
@ -962,17 +962,24 @@ export class TransitionAnimationEngine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bodyNode || !this.driver.containsElement(bodyNode, element)) {
|
const nodeIsOrphaned = !bodyNode || !this.driver.containsElement(bodyNode, element);
|
||||||
player.destroy();
|
const leaveClassName = leaveNodeMapIds.get(element) !;
|
||||||
|
const enterClassName = enterNodeMapIds.get(element) !;
|
||||||
|
const instruction = this._buildInstruction(
|
||||||
|
entry, subTimelines, enterClassName, leaveClassName, nodeIsOrphaned) !;
|
||||||
|
if (instruction.errors && instruction.errors.length) {
|
||||||
|
erroneousTransitions.push(instruction);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const leaveClassName = leaveNodeMapIds.get(element) !;
|
// even though the element may not be apart of the DOM, it may
|
||||||
const enterClassName = enterNodeMapIds.get(element) !;
|
// still be added at a later point (due to the mechanics of content
|
||||||
const instruction =
|
// projection and/or dynamic component insertion) therefore it's
|
||||||
this._buildInstruction(entry, subTimelines, enterClassName, leaveClassName) !;
|
// important we still style the element.
|
||||||
if (instruction.errors && instruction.errors.length) {
|
if (nodeIsOrphaned) {
|
||||||
erroneousTransitions.push(instruction);
|
player.onStart(() => eraseStyles(element, instruction.fromStyles));
|
||||||
|
player.onDestroy(() => setStyles(element, instruction.toStyles));
|
||||||
|
skippedPlayers.push(player);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -623,6 +623,23 @@ const DEFAULT_NAMESPACE_ID = 'id';
|
||||||
const TRIGGER = 'fooTrigger';
|
const TRIGGER = 'fooTrigger';
|
||||||
expect(() => { engine.trigger(ID, element, TRIGGER, 'something'); }).not.toThrow();
|
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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
Loading…
Reference in New Issue