diff --git a/src/elements/clock/__snapshots__/clock.snapshot.spec.snap.js b/src/elements/clock/__snapshots__/clock.snapshot.spec.snap.js index 259b9de222..501cc93cf9 100644 --- a/src/elements/clock/__snapshots__/clock.snapshot.spec.snap.js +++ b/src/elements/clock/__snapshots__/clock.snapshot.spec.snap.js @@ -35,7 +35,7 @@ snapshots["sbb-clock renders with fixed time DOM"] = ` `; diff --git a/src/elements/clock/clock.ts b/src/elements/clock/clock.ts index 957102055d..9a76d1fa7d 100644 --- a/src/elements/clock/clock.ts +++ b/src/elements/clock/clock.ts @@ -86,20 +86,28 @@ export class SbbClockElement extends LitElement { /** Move the minutes hand every minute. */ private _handMovement?: ReturnType; - protected override willUpdate(changedProperties: PropertyValues): void { + protected override async willUpdate(changedProperties: PropertyValues): Promise { super.willUpdate(changedProperties); if (!isServer && changedProperties.has('now')) { - if (this.now) { - this._removeSecondsAnimationStyles(); - this._removeHoursAnimationStyles(); - this._stopClock(); - } else { - this._startClock(); - } + await this._startOrConfigureClock(); } } + protected override async firstUpdated(changedProperties: PropertyValues): Promise { + super.firstUpdated(changedProperties); + + if (!isServer) { + document.addEventListener('visibilitychange', this._handlePageVisibilityChange, false); + await this._startOrConfigureClock(); + } + } + + public override disconnectedCallback(): void { + super.disconnectedCallback(); + this._removeEventListeners(); + } + private _handlePageVisibilityChange = async (): Promise => { if (document.visibilityState === 'hidden') { await this._stopClock(); @@ -108,40 +116,51 @@ export class SbbClockElement extends LitElement { } }; - private _addEventListeners(): void { - document.addEventListener('visibilitychange', this._handlePageVisibilityChange, false); + private async _startOrConfigureClock(): Promise { + if (this.now) { + await this._stopClock(); + this._resetSecondsHandAnimation(); + this._setHandsStartingPosition(); + } else { + await this._startClock(); + } } - private _removeEventListeners(): void { - document?.removeEventListener('visibilitychange', this._handlePageVisibilityChange); - this._clockHandHours?.removeEventListener('animationend', this._moveHoursHandFn); - this._clockHandSeconds?.removeEventListener('animationend', this._moveMinutesHandFn); - clearInterval(this._handMovement); - } + /** Starts the clock by defining the hands starting position then starting the animations. */ + private async _startClock(): Promise { + this._clockHandHours?.addEventListener( + 'animationend', + this._moveHoursHandFn, + ADD_EVENT_LISTENER_OPTIONS, + ); + this._clockHandSeconds?.addEventListener( + 'animationend', + this._moveMinutesHandFn, + ADD_EVENT_LISTENER_OPTIONS, + ); - private _removeHoursAnimationStyles(): void { - this._clockHandHours?.classList.remove('sbb-clock__hand-hours--initial-hour'); - this.style.removeProperty('--sbb-clock-hours-animation-start-angle'); - this.style.removeProperty('--sbb-clock-hours-animation-duration'); - } + await new Promise(() => + setTimeout(() => { + this._setHandsStartingPosition(); - private _removeSecondsAnimationStyles(): void { - this._clockHandSeconds?.classList.remove('sbb-clock__hand-seconds--initial-minute'); - this._clockHandMinutes?.classList.remove('sbb-clock__hand-minutes--no-transition'); - this.style.removeProperty('--sbb-clock-seconds-animation-start-angle'); - this.style.removeProperty('--sbb-clock-seconds-animation-duration'); + this.style?.setProperty('--sbb-clock-animation-play-state', 'running'); + }, INITIAL_TIMEOUT_DURATION), + ); } - /** Given the current date, calculates the hh/mm/ss values and the hh/mm/ss left to the next midnight. */ - private _assignCurrentTime(): void { - const date = this.now ? null : new Date(); - const [hours, minutes, seconds] = date - ? [date.getHours(), date.getMinutes(), date.getSeconds()] - : this.now!.split(':').map((p) => +p); + /** Stops the clock by removing all the animations. */ + private async _stopClock(): Promise { + clearInterval(this._handMovement); - this._hours = hours % 12; - this._minutes = minutes; - this._seconds = seconds; + this._removeSecondsAnimationStyles(); + this._removeHoursAnimationStyles(); + + this._clockHandHours?.removeEventListener('animationend', this._moveHoursHandFn); + this._clockHandSeconds?.removeEventListener('animationend', this._moveMinutesHandFn); + + this._clockHandMinutes?.classList.add('sbb-clock__hand-minutes--no-transition'); + + this.style?.setProperty('--sbb-clock-animation-play-state', 'paused'); } /** Set the starting position for the three hands on the clock face. */ @@ -185,11 +204,22 @@ export class SbbClockElement extends LitElement { this._clockHandSeconds?.classList.add('sbb-clock__hand-seconds--initial-minute'); this._clockHandHours?.classList.add('sbb-clock__hand-hours--initial-hour'); - this.style?.setProperty('--sbb-clock-animation-play-state', 'running'); this.toggleAttribute('data-initialized', true); } + /** Given the current date, calculates the hh/mm/ss values and the hh/mm/ss left to the next midnight. */ + private _assignCurrentTime(): void { + const date = this.now ? null : new Date(); + const [hours, minutes, seconds] = date + ? [date.getHours(), date.getMinutes(), date.getSeconds()] + : this.now!.split(':').map((p) => +p); + + this._hours = hours % 12; + this._minutes = minutes; + this._seconds = seconds; + } + /** Set the starting position for the minutes hand. */ private _setMinutesHand(): void { this._clockHandMinutes?.style.setProperty( @@ -230,67 +260,39 @@ export class SbbClockElement extends LitElement { this._setMinutesHand(); } - /** Stops the clock by removing all the animations. */ - private async _stopClock(): Promise { - clearInterval(this._handMovement); - - if (this.now) { - this._setHandsStartingPosition(); - - // Wait a tick to before animation is added. Otherwise, the animation gets not completely - // removed which can lead to a mispositioned seconds hand. - await new Promise((resolve) => setTimeout(resolve)); - - this._clockHandSeconds?.classList.add('sbb-clock__hand-seconds--initial-minute'); - this._clockHandHours?.classList.add('sbb-clock__hand-hours--initial-hour'); - } else { - this._removeSecondsAnimationStyles(); - this._removeHoursAnimationStyles(); + /** + * Removing animation by overriding with empty string, + * then triggering a reflow and re add original animation by removing override. + * @private + */ + private _resetSecondsHandAnimation(): void { + if (!this._clockHandSeconds) { + return; } + this._clockHandSeconds.style.animation = ''; + // Hack to trigger reflow + this._clockHandSeconds.offsetHeight; + this._clockHandSeconds.style.removeProperty('animation'); + } + private _removeEventListeners(): void { + document?.removeEventListener('visibilitychange', this._handlePageVisibilityChange); this._clockHandHours?.removeEventListener('animationend', this._moveHoursHandFn); this._clockHandSeconds?.removeEventListener('animationend', this._moveMinutesHandFn); - - this._clockHandMinutes?.classList.add('sbb-clock__hand-minutes--no-transition'); - - this.style?.setProperty('--sbb-clock-animation-play-state', 'paused'); - } - - /** Starts the clock by defining the hands starting position then starting the animations. */ - private async _startClock(): Promise { - this._clockHandHours?.addEventListener( - 'animationend', - this._moveHoursHandFn, - ADD_EVENT_LISTENER_OPTIONS, - ); - this._clockHandSeconds?.addEventListener( - 'animationend', - this._moveMinutesHandFn, - ADD_EVENT_LISTENER_OPTIONS, - ); - - await new Promise(() => - setTimeout(() => this._setHandsStartingPosition(), INITIAL_TIMEOUT_DURATION), - ); + clearInterval(this._handMovement); } - protected override async firstUpdated(changedProperties: PropertyValues): Promise { - super.firstUpdated(changedProperties); - - if (!isServer) { - this._addEventListeners(); - - if (this.now) { - await this._stopClock(); - } else { - await this._startClock(); - } - } + private _removeHoursAnimationStyles(): void { + this._clockHandHours?.classList.remove('sbb-clock__hand-hours--initial-hour'); + this.style.removeProperty('--sbb-clock-hours-animation-start-angle'); + this.style.removeProperty('--sbb-clock-hours-animation-duration'); } - public override disconnectedCallback(): void { - super.disconnectedCallback(); - this._removeEventListeners(); + private _removeSecondsAnimationStyles(): void { + this._clockHandSeconds?.classList.remove('sbb-clock__hand-seconds--initial-minute'); + this._clockHandMinutes?.classList.remove('sbb-clock__hand-minutes--no-transition'); + this.style.removeProperty('--sbb-clock-seconds-animation-start-angle'); + this.style.removeProperty('--sbb-clock-seconds-animation-duration'); } protected override render(): TemplateResult {