From 81c27090d27173f0e6eb1c050aab699c30052c46 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 13 Nov 2025 15:32:15 +0000 Subject: [PATCH] Create withViewTransition wrapper function (#27918) * Create withViewTransition wrapper function * Add missing space * Remove function, check for view transition, add param * Document --- src/common/util/view-transition.ts | 30 +++++++++++++++++++ src/resources/theme/core.globals.ts | 1 + src/util/launch-screen.ts | 45 +++++++++++------------------ 3 files changed, 48 insertions(+), 28 deletions(-) create mode 100644 src/common/util/view-transition.ts diff --git a/src/common/util/view-transition.ts b/src/common/util/view-transition.ts new file mode 100644 index 0000000000..757e0c7806 --- /dev/null +++ b/src/common/util/view-transition.ts @@ -0,0 +1,30 @@ +/** + * Executes a callback within a View Transition if supported, otherwise runs it directly. + * + * @param callback - Function to execute. Can be synchronous or return a Promise. The callback will be passed a boolean indicating whether the view transition is available. + * @returns Promise that resolves when the transition completes (or immediately if not supported) + * + * @example + * ```typescript + * // Synchronous callback + * withViewTransition(() => { + * this.large = !this.large; + * }); + * + * // Async callback + * await withViewTransition(async () => { + * await this.updateData(); + * }); + * ``` + */ +export const withViewTransition = ( + callback: (viewTransitionAvailable: boolean) => void | Promise +): Promise => { + if (document.startViewTransition) { + return document.startViewTransition(() => callback(true)).finished; + } + + // Fallback: Execute callback directly without transition + const result = callback(false); + return result instanceof Promise ? result : Promise.resolve(); +}; diff --git a/src/resources/theme/core.globals.ts b/src/resources/theme/core.globals.ts index 0f787544bc..a196a866be 100644 --- a/src/resources/theme/core.globals.ts +++ b/src/resources/theme/core.globals.ts @@ -55,6 +55,7 @@ export const coreStyles = css` --ha-shadow-spread-sm: 0; --ha-shadow-spread-md: 0; --ha-shadow-spread-lg: 0; + --ha-animation-base-duration: 350ms; } diff --git a/src/util/launch-screen.ts b/src/util/launch-screen.ts index 7898d97ee6..1abc5b9e9f 100644 --- a/src/util/launch-screen.ts +++ b/src/util/launch-screen.ts @@ -1,26 +1,7 @@ import type { TemplateResult } from "lit"; import { render } from "lit"; import { parseAnimationDuration } from "../common/util/parse-animation-duration"; - -const removeElement = ( - launchScreenElement: HTMLElement, - skipAnimation: boolean -) => { - if (skipAnimation) { - launchScreenElement.parentElement?.removeChild(launchScreenElement); - return; - } - - launchScreenElement.classList.add("removing"); - - const durationFromCss = getComputedStyle(document.documentElement) - .getPropertyValue("--ha-animation-base-duration") - .trim(); - - setTimeout(() => { - launchScreenElement.parentElement?.removeChild(launchScreenElement); - }, parseAnimationDuration(durationFromCss)); -}; +import { withViewTransition } from "../common/util/view-transition"; export const removeLaunchScreen = () => { const launchScreenElement = document.getElementById("ha-launch-screen"); @@ -28,14 +9,22 @@ export const removeLaunchScreen = () => { return; } - if (document.startViewTransition) { - document.startViewTransition(() => { - removeElement(launchScreenElement, false); - }); - } else { - // Fallback: Direct removal without transition - removeElement(launchScreenElement, true); - } + withViewTransition((viewTransitionAvailable: boolean) => { + if (!viewTransitionAvailable) { + launchScreenElement.parentElement?.removeChild(launchScreenElement); + return; + } + + launchScreenElement.classList.add("removing"); + + const durationFromCss = getComputedStyle(document.documentElement) + .getPropertyValue("--ha-animation-base-duration") + .trim(); + + setTimeout(() => { + launchScreenElement.parentElement?.removeChild(launchScreenElement); + }, parseAnimationDuration(durationFromCss)); + }); }; export const renderLaunchScreenInfoBox = (content: TemplateResult) => {