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(
|
||||
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;
|
||||
|
@ -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); });
|
||||
|
|
|
@ -740,10 +740,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) {
|
||||
|
@ -962,17 +962,24 @@ export class TransitionAnimationEngine {
|
|||
}
|
||||
}
|
||||
|
||||
if (!bodyNode || !this.driver.containsElement(bodyNode, element)) {
|
||||
player.destroy();
|
||||
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, nodeIsOrphaned) !;
|
||||
if (instruction.errors && instruction.errors.length) {
|
||||
erroneousTransitions.push(instruction);
|
||||
return;
|
||||
}
|
||||
|
||||
const leaveClassName = leaveNodeMapIds.get(element) !;
|
||||
const enterClassName = enterNodeMapIds.get(element) !;
|
||||
const instruction =
|
||||
this._buildInstruction(entry, subTimelines, enterClassName, leaveClassName) !;
|
||||
if (instruction.errors && instruction.errors.length) {
|
||||
erroneousTransitions.push(instruction);
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
Loading…
Reference in New Issue