chore(field): make animations synchronous

PiperOrigin-RevId: 511617262
This commit is contained in:
Elizabeth Mitchell 2023-02-22 15:44:18 -08:00 committed by Copybara-Service
parent 510a867f0d
commit d9f911ad17

View File

@ -5,10 +5,10 @@
*/
import {html, LitElement, nothing, PropertyValues, TemplateResult} from 'lit';
import {property, queryAsync, state} from 'lit/decorators.js';
import {property, query, state} from 'lit/decorators.js';
import {classMap} from 'lit/directives/class-map.js';
import {createAnimationSignal, EASING} from '../../motion/animation.js';
import {EASING} from '../../motion/animation.js';
/**
* A field component.
@ -32,11 +32,9 @@ export class Field extends LitElement {
@property({type: Boolean}) hasEnd = false;
@state() private isAnimating = false;
private readonly labelAnimationSignal = createAnimationSignal();
@queryAsync('.label.floating')
private readonly floatingLabelEl!: Promise<HTMLElement>;
@queryAsync('.label.resting')
private readonly restingLabelEl!: Promise<HTMLElement>;
private labelAnimation?: Animation;
@query('.label.floating') private readonly floatingLabelEl!: HTMLElement|null;
@query('.label.resting') private readonly restingLabelEl!: HTMLElement|null;
protected override update(props: PropertyValues<Field>) {
// Client-side property updates
@ -136,7 +134,7 @@ export class Field extends LitElement {
`;
}
private async animateLabelIfNeeded({wasFocused, wasPopulated}: {
private animateLabelIfNeeded({wasFocused, wasPopulated}: {
wasFocused?: boolean,
wasPopulated?: boolean
}) {
@ -153,20 +151,13 @@ export class Field extends LitElement {
}
this.isAnimating = true;
const signal = this.labelAnimationSignal.start();
this.labelAnimation?.cancel();
// Only one label is visible at a time for clearer text rendering.
// The resting label is visible and used during animation. At the end of the
// animation, it will either remain visible (if resting) or hide and the
// floating label will be shown.
const labelEl = await this.restingLabelEl;
const keyframes = await this.getLabelKeyframes();
if (signal.aborted) {
// Don't animate if this animation was requested to stop while getting
// the label element or calculating keyframes
return;
}
//
// We don't use forward filling because if the dimensions of the text field
// change (leading icon removed, density changes, etc), then the animation
// will be inaccurate.
@ -174,24 +165,21 @@ export class Field extends LitElement {
// Re-calculating the animation each time will prevent any visual glitches
// from appearing.
// TODO(b/241113345): use animation tokens
const animation =
labelEl.animate(keyframes, {duration: 150, easing: EASING.STANDARD});
this.labelAnimation = this.restingLabelEl?.animate(
this.getLabelKeyframes(), {duration: 150, easing: EASING.STANDARD});
signal.addEventListener('abort', () => {
// Cancel if requested (another animation starts playing).
animation.cancel();
});
animation.addEventListener('finish', () => {
this.labelAnimation?.addEventListener('finish', () => {
// At the end of the animation, update the visible label.
this.isAnimating = false;
this.labelAnimationSignal.finish();
});
}
private async getLabelKeyframes() {
const floatingLabelEl = await this.floatingLabelEl;
const restingLabelEl = await this.restingLabelEl;
private getLabelKeyframes() {
const {floatingLabelEl, restingLabelEl} = this;
if (!floatingLabelEl || !restingLabelEl) {
return [];
}
const {
x: floatingX,
y: floatingY,