Compare commits

...

19 Commits

Author SHA1 Message Date
Pavilion
b93bc7c9cf Genericised layout transition event 2026-01-26 14:36:18 +00:00
Pavilion
e9800c7d94 overflow fix 2026-01-26 13:43:09 +00:00
Pavilion
42efba4b56 disabled chart container overflow 2026-01-26 11:08:10 +00:00
Pavilion
d66e93a907 disabling graph animation while sidebar is animating 2026-01-26 10:37:59 +00:00
Pavilion
729736ab4a Fixed lint errors 2026-01-21 15:51:57 +00:00
Pavilion
a0cc36993f Fixed lint error 2026-01-21 15:42:19 +00:00
Pavilion
a9d5facb33 Updated animation speed vars 2026-01-21 15:30:40 +00:00
Pavilion
e318f85756 defer graph redraws on sidebar state change 2026-01-21 14:15:54 +00:00
uptimeZERO_
e1332f29ab Merge branch 'dev' into animate-ha-sidebar 2026-01-21 08:41:43 +00:00
Pavilion
c68c52f37d removed reflect setting 2026-01-16 15:27:03 +00:00
Pavilion
d67df39ad5 removed animation duration setting 2026-01-16 15:22:13 +00:00
uptimeZERO_
2994b176c8 Merge branch 'dev' into animate-ha-sidebar 2026-01-16 15:02:40 +00:00
Pavilion
339aca2749 RTL fixes 2026-01-16 15:02:01 +00:00
Pavilion
c9078d8f4a making animation duration configurable 2026-01-16 14:42:25 +00:00
uptimeZERO_
5f21a48144 Merge branch 'dev' into animate-ha-sidebar 2026-01-16 10:36:48 +00:00
uptimeZERO_
7416972faa Merge branch 'dev' into animate-ha-sidebar 2026-01-16 09:42:07 +00:00
Pavilion
0b4aa38ec5 animating top bar 2026-01-16 09:31:01 +00:00
Pavilion
c66b83d5f6 Merge branch 'dev' into animate-ha-sidebar 2026-01-15 15:43:37 +00:00
Pavilion
37e35a3026 WIP animating ha-sidebar 2026-01-15 11:35:52 +00:00
16 changed files with 234 additions and 32 deletions

View File

@@ -19,6 +19,7 @@ import { styleMap } from "lit/directives/style-map";
import { ensureArray } from "../../common/array/ensure-array";
import { getAllGraphColors } from "../../common/color/colors";
import { fireEvent } from "../../common/dom/fire_event";
import type { HASSDomEvent } from "../../common/dom/fire_event";
import { listenMediaQuery } from "../../common/dom/media_query";
import { themesContext } from "../../data/context";
import type { Themes } from "../../data/ws-themes";
@@ -92,10 +93,16 @@ export class HaChartBase extends LitElement {
private _resizeAnimationDuration?: number;
private _suspendResize = false;
// @ts-ignore
private _resizeController = new ResizeController(this, {
callback: () => {
if (this.chart) {
if (this._suspendResize) {
this._shouldResizeChart = true;
return;
}
if (!this.chart.getZr().animation.isFinished()) {
this._shouldResizeChart = true;
} else {
@@ -181,6 +188,21 @@ export class HaChartBase extends LitElement {
() => window.removeEventListener("keyup", handleKeyUp)
);
}
const handleLayoutTransition: EventListener = (ev) => {
const event = ev as HASSDomEvent<HASSDomEvents["hass-layout-transition"]>;
this._suspendResize = Boolean(event.detail?.active);
if (!this._suspendResize) {
this._resizeChartIfNeeded();
}
};
window.addEventListener("hass-layout-transition", handleLayoutTransition);
this._listeners.push(() =>
window.removeEventListener(
"hass-layout-transition",
handleLayoutTransition
)
);
}
protected firstUpdated() {
@@ -988,19 +1010,29 @@ export class HaChartBase extends LitElement {
}
private _handleChartRenderFinished = () => {
if (this._shouldResizeChart) {
this.chart?.resize({
animation:
this._reducedMotion ||
typeof this._resizeAnimationDuration !== "number"
? undefined
: { duration: this._resizeAnimationDuration },
});
this._shouldResizeChart = false;
this._resizeAnimationDuration = undefined;
}
this._resizeChartIfNeeded();
};
private _resizeChartIfNeeded() {
if (!this.chart || !this._shouldResizeChart) {
return;
}
if (this._suspendResize) {
return;
}
if (!this.chart.getZr().animation.isFinished()) {
return;
}
this.chart.resize({
animation:
this._reducedMotion || typeof this._resizeAnimationDuration !== "number"
? undefined
: { duration: this._resizeAnimationDuration },
});
this._shouldResizeChart = false;
this._resizeAnimationDuration = undefined;
}
private _compareCustomLegendOptions(
oldOptions: ECOption | undefined,
newOptions: ECOption | undefined
@@ -1022,11 +1054,13 @@ export class HaChartBase extends LitElement {
display: block;
position: relative;
letter-spacing: normal;
overflow: visible;
}
.container {
display: flex;
flex-direction: column;
position: relative;
overflow: visible;
}
.container.has-height {
max-height: var(--chart-max-height, 350px);
@@ -1034,6 +1068,7 @@ export class HaChartBase extends LitElement {
.chart-container {
width: 100%;
max-height: var(--chart-max-height, 350px);
overflow: visible;
}
.has-height .chart-container {
flex: 1;

View File

@@ -4,6 +4,18 @@ import type { PropertyValues } from "lit";
import { css } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import type { HASSDomEvent } from "../common/dom/fire_event";
declare global {
interface HASSDomEvents {
"hass-layout-transition": { active: boolean; reason?: string };
}
interface HTMLElementEventMap {
"hass-layout-transition": HASSDomEvent<
HASSDomEvents["hass-layout-transition"]
>;
}
}
const blockingElements = (document as any).$blockingElements;
@@ -15,6 +27,30 @@ export class HaDrawer extends DrawerBase {
private _rtlStyle?: HTMLElement;
private _sidebarTransitionActive = false;
private _handleDrawerTransitionStart = (ev: TransitionEvent) => {
if (ev.propertyName !== "width" || this._sidebarTransitionActive) {
return;
}
this._sidebarTransitionActive = true;
fireEvent(window, "hass-layout-transition", {
active: true,
reason: "sidebar",
});
};
private _handleDrawerTransitionEnd = (ev: TransitionEvent) => {
if (ev.propertyName !== "width" || !this._sidebarTransitionActive) {
return;
}
this._sidebarTransitionActive = false;
fireEvent(window, "hass-layout-transition", {
active: false,
reason: "sidebar",
});
};
protected createAdapter() {
return {
...super.createAdapter(),
@@ -63,6 +99,38 @@ export class HaDrawer extends DrawerBase {
}
}
protected firstUpdated() {
super.firstUpdated();
this.mdcRoot?.addEventListener(
"transitionstart",
this._handleDrawerTransitionStart
);
this.mdcRoot?.addEventListener(
"transitionend",
this._handleDrawerTransitionEnd
);
this.mdcRoot?.addEventListener(
"transitioncancel",
this._handleDrawerTransitionEnd
);
}
public disconnectedCallback() {
super.disconnectedCallback();
this.mdcRoot?.removeEventListener(
"transitionstart",
this._handleDrawerTransitionStart
);
this.mdcRoot?.removeEventListener(
"transitionend",
this._handleDrawerTransitionEnd
);
this.mdcRoot?.removeEventListener(
"transitioncancel",
this._handleDrawerTransitionEnd
);
}
private async _setupSwipe() {
const hammer = await import("../resources/hammer");
this._mc = new hammer.Manager(document, {
@@ -90,6 +158,16 @@ export class HaDrawer extends DrawerBase {
border-color: var(--divider-color, rgba(0, 0, 0, 0.12));
inset-inline-start: 0 !important;
inset-inline-end: initial !important;
transition-property: transform, width;
transition-duration:
var(--mdc-drawer-transition-duration, 0.2s),
var(--ha-animation-duration-normal);
transition-timing-function:
var(
--mdc-drawer-transition-timing-function,
cubic-bezier(0.4, 0, 0.2, 1)
),
ease;
}
.mdc-drawer.mdc-drawer--modal.mdc-drawer--open {
z-index: 200;
@@ -103,6 +181,15 @@ export class HaDrawer extends DrawerBase {
direction: var(--direction);
width: 100%;
box-sizing: border-box;
transition:
padding-left var(--ha-animation-duration-normal) ease,
padding-inline-start var(--ha-animation-duration-normal) ease;
}
@media (prefers-reduced-motion: reduce) {
.mdc-drawer,
.mdc-drawer-app-content {
transition: none;
}
}
`,
];

View File

@@ -472,8 +472,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
@mouseleave=${this._itemMouseLeave}
>
<ha-svg-icon slot="start" .path=${mdiCog}></ha-svg-icon>
${!this.alwaysExpand &&
(this._updatesCount > 0 || this._issuesCount > 0)
${this._updatesCount > 0 || this._issuesCount > 0
? html`
<span class="badge" slot="start">
${this._updatesCount + this._issuesCount}
@@ -483,7 +482,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
<span class="item-text" slot="headline"
>${this.hass.localize("panel.config")}</span
>
${this.alwaysExpand && (this._updatesCount > 0 || this._issuesCount > 0)
${this._updatesCount > 0 || this._issuesCount > 0
? html`
<span class="badge" slot="end"
>${this._updatesCount + this._issuesCount}</span
@@ -508,7 +507,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
type="button"
>
<ha-svg-icon slot="start" .path=${mdiBell}></ha-svg-icon>
${!this.alwaysExpand && notificationCount > 0
${notificationCount > 0
? html`
<span class="badge" slot="start"> ${notificationCount} </span>
`
@@ -516,7 +515,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
<span class="item-text" slot="headline"
>${this.hass.localize("ui.notification_drawer.title")}</span
>
${this.alwaysExpand && notificationCount > 0
${notificationCount > 0
? html`<span class="badge" slot="end">${notificationCount}</span>`
: nothing}
</ha-md-list-item>
@@ -731,6 +730,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
);
font-size: var(--ha-font-size-xl);
align-items: center;
overflow: hidden;
width: calc(56px + var(--safe-area-inset-left, 0px));
padding-left: calc(
var(--ha-space-1) + var(--safe-area-inset-left, 0px)
);
@@ -739,6 +740,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
);
padding-inline-end: initial;
padding-top: var(--safe-area-inset-top, 0px);
transition: width var(--ha-animation-duration-normal) ease;
}
:host([expanded]) .menu {
width: calc(256px + var(--safe-area-inset-left, 0px));
@@ -753,15 +755,22 @@ class HaSidebar extends SubscribeMixin(LitElement) {
margin-left: 3px;
margin-inline-start: 3px;
margin-inline-end: initial;
width: 100%;
display: none;
flex: 1;
min-width: 0;
max-width: 0;
opacity: 0;
transition:
max-width var(--ha-animation-duration-normal) ease,
opacity var(--ha-animation-duration-normal) ease;
}
:host([narrow]) .title {
margin: 0;
padding: 0 var(--ha-space-4);
}
:host([expanded]) .title {
display: initial;
max-width: 100%;
opacity: 1;
transition-delay: 0ms, 80ms;
}
.hidden-panel {
display: none;
@@ -803,6 +812,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
--md-list-item-leading-space: var(--ha-space-3);
--md-list-item-trailing-space: var(--ha-space-3);
--md-list-item-leading-icon-size: var(--ha-space-6);
transition: width var(--ha-animation-duration-normal) ease;
}
:host([expanded]) ha-md-list-item {
width: 248px;
@@ -843,11 +853,22 @@ class HaSidebar extends SubscribeMixin(LitElement) {
}
ha-md-list-item .item-text {
display: none;
display: block;
max-width: 0;
opacity: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: var(--ha-font-size-m);
font-weight: var(--ha-font-weight-medium);
transition:
max-width var(--ha-animation-duration-normal) ease,
opacity var(--ha-animation-duration-normal) ease;
}
:host([expanded]) ha-md-list-item .item-text {
max-width: 100%;
opacity: 1;
transition-delay: 0ms, 80ms;
display: block;
overflow: hidden;
text-overflow: ellipsis;
@@ -875,6 +896,9 @@ class HaSidebar extends SubscribeMixin(LitElement) {
background-color: var(--accent-color);
padding: 2px 6px;
color: var(--text-accent-color, var(--text-primary-color));
transition:
opacity var(--ha-animation-duration-normal) ease,
transform var(--ha-animation-duration-normal) ease;
}
ha-svg-icon + .badge {
@@ -886,6 +910,12 @@ class HaSidebar extends SubscribeMixin(LitElement) {
line-height: var(--ha-line-height-expanded);
padding: 0 var(--ha-space-1);
}
:host([expanded]) .badge[slot="start"],
:host(:not([expanded])) .badge[slot="end"] {
opacity: 0;
transform: scale(0.8);
pointer-events: none;
}
ha-md-list-item.user {
--md-list-item-leading-icon-size: var(--ha-space-10);
@@ -932,6 +962,15 @@ class HaSidebar extends SubscribeMixin(LitElement) {
-webkit-transform: scaleX(var(--scale-direction));
transform: scaleX(var(--scale-direction));
}
@media (prefers-reduced-motion: reduce) {
.menu,
ha-md-list-item,
ha-md-list-item .item-text,
.title {
transition: none;
}
}
`,
];
}

View File

@@ -36,10 +36,19 @@ export class HaTopAppBarFixed extends TopAppBarFixedBase {
);
padding-top: var(--safe-area-inset-top);
padding-right: var(--safe-area-inset-right);
transition:
width var(--ha-animation-duration-normal) ease,
padding-left var(--ha-animation-duration-normal) ease,
padding-right var(--ha-animation-duration-normal) ease;
}
:host([narrow]) .mdc-top-app-bar {
padding-left: var(--safe-area-inset-left);
}
@media (prefers-reduced-motion: reduce) {
.mdc-top-app-bar {
transition: none;
}
}
.mdc-top-app-bar__title {
font-size: var(--ha-font-size-xl);
padding-inline-start: 24px;

View File

@@ -288,10 +288,19 @@ export class TopAppBarBaseBase extends BaseElement {
);
padding-top: var(--safe-area-inset-top);
padding-right: var(--safe-area-inset-right);
transition:
width var(--ha-animation-duration-normal) ease,
padding-left var(--ha-animation-duration-normal) ease,
padding-right var(--ha-animation-duration-normal) ease;
}
:host([narrow]) .mdc-top-app-bar {
padding-left: var(--safe-area-inset-left);
}
@media (prefers-reduced-motion: reduce) {
.mdc-top-app-bar {
transition: none;
}
}
.mdc-top-app-bar--pane.mdc-top-app-bar--fixed-scrolled {
box-shadow: none;
}

View File

@@ -29,11 +29,11 @@
}
}
::view-transition-group(launch-screen) {
animation-duration: var(--ha-animation-base-duration, 350ms);
animation-duration: var(--ha-animation-duration-slow, 350ms);
animation-timing-function: ease-out;
}
::view-transition-old(launch-screen) {
animation: fade-out var(--ha-animation-base-duration, 350ms) ease-out;
animation: fade-out var(--ha-animation-duration-slow, 350ms) ease-out;
}
html {
background-color: var(--primary-background-color, #fafafa);

View File

@@ -186,7 +186,6 @@ class PanelClimate extends LitElement {
);
padding-top: var(--safe-area-inset-top);
z-index: 4;
transition: box-shadow 200ms linear;
display: flex;
flex-direction: row;
-webkit-backdrop-filter: var(--app-header-backdrop-filter, none);

View File

@@ -242,7 +242,7 @@ export class StorageBreakdownChart extends LitElement {
}
.chart-container {
transition: height var(--ha-animation-base-duration) ease;
transition: height var(--ha-animation-duration-slow) ease;
overflow: hidden;
}
@@ -264,7 +264,7 @@ export class StorageBreakdownChart extends LitElement {
ha-segmented-bar,
ha-sunburst-chart {
animation: fade-in var(--ha-animation-base-duration) ease;
animation: fade-in var(--ha-animation-duration-slow) ease;
}
@keyframes fade-in {

View File

@@ -186,7 +186,6 @@ class PanelLight extends LitElement {
);
padding-top: var(--safe-area-inset-top);
z-index: 4;
transition: box-shadow 200ms linear;
display: flex;
flex-direction: row;
-webkit-backdrop-filter: var(--app-header-backdrop-filter, none);

View File

@@ -541,7 +541,7 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
);
pointer-events: none;
opacity: 0;
transition: opacity var(--ha-animation-base-duration) ease-in-out;
transition: opacity var(--ha-animation-duration-slow) ease-in-out;
}
.datepicker-open .backdrop {
opacity: 1;

View File

@@ -1316,7 +1316,6 @@ class HUIRoot extends LitElement {
padding-top: var(--safe-area-inset-top);
padding-right: var(--safe-area-inset-right);
z-index: 4;
transition: box-shadow 200ms linear;
}
.narrow .header {
width: calc(

View File

@@ -565,10 +565,19 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
);
border-top: 1px solid var(--divider-color);
margin-right: var(--safe-area-inset-right);
transition:
width var(--ha-animation-duration-normal) ease,
margin-left var(--ha-animation-duration-normal) ease,
margin-right var(--ha-animation-duration-normal) ease;
}
:host([narrow]) {
margin-left: var(--safe-area-inset-left);
}
@media (prefers-reduced-motion: reduce) {
:host {
transition: none;
}
}
mwc-linear-progress {
width: 100%;

View File

@@ -186,7 +186,6 @@ class PanelSecurity extends LitElement {
);
padding-top: var(--safe-area-inset-top);
z-index: 4;
transition: box-shadow 200ms linear;
display: flex;
flex-direction: row;
-webkit-backdrop-filter: var(--app-header-backdrop-filter, none);

View File

@@ -34,6 +34,20 @@ export const haStyle = css`
margin-inline-end: initial;
}
.header {
transition:
box-shadow 200ms linear,
width var(--ha-animation-duration-normal) ease,
padding-left var(--ha-animation-duration-normal) ease,
padding-right var(--ha-animation-duration-normal) ease;
}
@media (prefers-reduced-motion: reduce) {
.header {
transition: box-shadow 200ms linear;
}
}
h1 {
font-family: var(--ha-font-family-heading);
-webkit-font-smoothing: var(--ha-font-smoothing);

View File

@@ -55,12 +55,16 @@ export const coreStyles = css`
--ha-shadow-spread-md: 0;
--ha-shadow-spread-lg: 0;
--ha-animation-base-duration: 350ms;
--ha-animation-duration-fast: 150ms;
--ha-animation-duration-normal: 250ms;
--ha-animation-duration-slow: 350ms;
}
@media (prefers-reduced-motion: reduce) {
html {
--ha-animation-base-duration: 0ms;
--ha-animation-duration-fast: 0ms;
--ha-animation-duration-normal: 0ms;
--ha-animation-duration-slow: 0ms;
}
}
`;

View File

@@ -18,7 +18,7 @@ export const removeLaunchScreen = () => {
launchScreenElement.classList.add("removing");
const durationFromCss = getComputedStyle(document.documentElement)
.getPropertyValue("--ha-animation-base-duration")
.getPropertyValue("--ha-animation-duration-slow")
.trim();
setTimeout(() => {